/*
 * Copyright 2017 Red Hat, Inc. and/or its affiliates
 * and other contributors as indicated by the @author tags.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.keycloak.authentication.actiontoken;

import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.common.ClientConnection;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.*;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.sessions.AuthenticationSessionModel;
import java.util.function.Function;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilderException;
import javax.ws.rs.core.UriInfo;
import org.jboss.resteasy.spi.HttpRequest;

/**
 *
 * @author hmlnarik
 */
public class ActionTokenContext<T extends JsonWebToken> {

    @FunctionalInterface
    public interface ProcessAuthenticateFlow {
        Response processFlow(boolean action, String execution, AuthenticationSessionModel authSession, String flowPath, AuthenticationFlowModel flow, String errorMessage, AuthenticationProcessor processor);
    };

    @FunctionalInterface
    public interface ProcessBrokerFlow {
        Response brokerLoginFlow(String code, String execution, String flowPath);
    };

    private final KeycloakSession session;
    private final RealmModel realm;
    private final UriInfo uriInfo;
    private final ClientConnection clientConnection;
    private final HttpRequest request;
    private EventBuilder event;
    private final ActionTokenHandler<T> handler;
    private AuthenticationSessionModel authenticationSession;
    private boolean authenticationSessionFresh;
    private String executionId;
    private final ProcessAuthenticateFlow processAuthenticateFlow;
    private final ProcessBrokerFlow processBrokerFlow;

    public ActionTokenContext(KeycloakSession session, RealmModel realm, UriInfo uriInfo,
      ClientConnection clientConnection, HttpRequest request,
      EventBuilder event, ActionTokenHandler<T> handler, String executionId,
      ProcessAuthenticateFlow processFlow, ProcessBrokerFlow processBrokerFlow) {
        this.session = session;
        this.realm = realm;
        this.uriInfo = uriInfo;
        this.clientConnection = clientConnection;
        this.request = request;
        this.event = event;
        this.handler = handler;
        this.executionId = executionId;
        this.processAuthenticateFlow = processFlow;
        this.processBrokerFlow = processBrokerFlow;
    }

    public EventBuilder getEvent() {
        return event;
    }

    public void setEvent(EventBuilder event) {
        this.event = event;
    }

    public KeycloakSession getSession() {
        return session;
    }

    public RealmModel getRealm() {
        return realm;
    }

    public UriInfo getUriInfo() {
        return uriInfo;
    }

    public ClientConnection getClientConnection() {
        return clientConnection;
    }

    public HttpRequest getRequest() {
        return request;
    }

    public AuthenticationSessionModel createAuthenticationSessionForClient(String clientId)
      throws UriBuilderException, IllegalArgumentException {
        AuthenticationSessionModel authSession;

        // set up the account service as the endpoint to call.
        ClientModel client = realm.getClientByClientId(clientId == null ? Constants.ACCOUNT_MANAGEMENT_CLIENT_ID : clientId);
        
        authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, client, true);
        authSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
        authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
        String redirectUri = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName()).toString();
        authSession.setRedirectUri(redirectUri);
        authSession.setClientNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUri);
        authSession.setClientNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, OAuth2Constants.CODE);
        authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));

        return authSession;
    }

    public boolean isAuthenticationSessionFresh() {
        return authenticationSessionFresh;
    }

    public AuthenticationSessionModel getAuthenticationSession() {
        return authenticationSession;
    }

    public void setAuthenticationSession(AuthenticationSessionModel authenticationSession, boolean isFresh) {
        this.authenticationSession = authenticationSession;
        this.authenticationSessionFresh = isFresh;
        if (this.event != null) {
            ClientModel client = authenticationSession == null ? null : authenticationSession.getClient();
            this.event.client((String) (client == null ? null : client.getClientId()));
        }
    }

    public ActionTokenHandler<T> getHandler() {
        return handler;
    }

    public String getExecutionId() {
        return executionId;
    }

    public void setExecutionId(String executionId) {
        this.executionId = executionId;
    }

    public Response processFlow(boolean action, String flowPath, AuthenticationFlowModel flow, String errorMessage, AuthenticationProcessor processor) {
        return processAuthenticateFlow.processFlow(action, getExecutionId(), getAuthenticationSession(), flowPath, flow, errorMessage, processor);
    }

    public Response brokerFlow(String code, String flowPath) {
        return processBrokerFlow.brokerLoginFlow(code, getExecutionId(), flowPath);
    }
}
