package org.gcube.keycloak.event;

import java.net.MalformedURLException;
import java.net.URL;

import org.jboss.logging.Logger;
import org.keycloak.Config.Scope;
import org.keycloak.events.EventListenerProviderFactory;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;

/**
 * @author <a href="mailto:marco.lettere@nubisware.com">Marco Lettere</a>
 * @author <a href="mailto:mauro.mugnaini@nubisware.com">Mauro Mugnaini</a>
 */
public class OrchestratorEventPublisherProviderFactory implements EventListenerProviderFactory {

    private static final Logger logger = Logger.getLogger(OrchestratorEventPublisherProviderFactory.class);

    public static final String MASTER_REALM_NAME = "master";
    public static final String ORCHESTRATOR_CLIENT_ID = "orchestrator";
    public static final String ORCHESTRATOR_AUDIENCE_ID = "conductor-server";
    
    public static final String KEYCLOAK_CLIENT_ID = "keycloak-client";

    private static final int CHECK_DELAY = 60 * 1000; // One minute

    public static URL ORCHESTRATOR_ENDPOINT;
    public static URL KEYCLOAK_ENDPOINT;
    public static String KEYCLOAK_CLIENT_SECRET;

    protected Long lastEndpointCheck = new Long(0);
    protected OrchestratorEventPublisherProvider oepp;

    public OrchestratorEventPublisherProviderFactory() {
        logger.info("New OrchestratorEventPublisherProviderFactory has been created");
    }

    @Override
    public void close() {
    }

    @Override
    public synchronized OrchestratorEventPublisherProvider create(KeycloakSession keycloakSession) {
        Long now = System.currentTimeMillis();
        Long elapsed = now - lastEndpointCheck;
        if (oepp == null || elapsed > CHECK_DELAY) {
            lastEndpointCheck = now;
            ClientModel orchestratorClient = getClientInActualOrMasterRealm(keycloakSession, ORCHESTRATOR_CLIENT_ID);
            URL newOrchestratorEndpoint;
            if (orchestratorClient != null) {
                logger.trace("Getting configured orchestrator endpoint address from client's base URL");
                try {
                    newOrchestratorEndpoint = new URL(orchestratorClient.getBaseUrl());
                } catch (MalformedURLException e) {
                    logger.errorf("Can't create new orchestrator endpoint address: %s", orchestratorClient.getBaseUrl());
                    oepp = new NoOpEventPublisherProvider();
                    return oepp;
                }
            } else {
                logger.debugf("Can't go ahead without a configured '%f' client", ORCHESTRATOR_CLIENT_ID);
                oepp = null;
                return oepp;
            }
            ClientModel keycloakClient = getClientInActualOrMasterRealm(keycloakSession, KEYCLOAK_CLIENT_ID);
            URL newKeycloakEndpoint;
            String keycloakClientSecret;
            if (keycloakClient != null) {
                try {
                    logger.debug("Getting configured keycloak endpoint address from client's base URL");
                    newKeycloakEndpoint = new URL(keycloakClient.getBaseUrl());
                    // Only do it if URL has been configured properly
                    logger.debug("Getting configured keycloak client client-secret from client");
                    keycloakClientSecret = keycloakClient.getSecret();
                } catch (MalformedURLException e) {
                    logger.errorf("Can't create new keycloak token address: %s", keycloakClient.getBaseUrl(), e);
                    oepp = new NoOpEventPublisherProvider();
                    return oepp;
                }
            } else {
                logger.debugf("Can't go ahead without a configured '%f' client", KEYCLOAK_CLIENT_ID);
                oepp = new NoOpEventPublisherProvider();
                return oepp;
            }

            if (oepp == null || !newOrchestratorEndpoint.equals(ORCHESTRATOR_ENDPOINT)
                    || !newKeycloakEndpoint.equals(KEYCLOAK_ENDPOINT)
                    || !keycloakClientSecret.equals(KEYCLOAK_CLIENT_SECRET)) {

                logger.info("Creating new orchestrator event publisher provider");
                // Address and other fileds will be then read from static fields in this class by 
                // the createEventSender() called by the superclass' constructor, overridden in the impl.
                ORCHESTRATOR_ENDPOINT = newOrchestratorEndpoint;
                KEYCLOAK_ENDPOINT = newKeycloakEndpoint;
                KEYCLOAK_CLIENT_SECRET = keycloakClientSecret;
                oepp = new OrchestratorEventPublisherProvider();
            }

        } else {
            logger.tracef("Next check is in %d millis", CHECK_DELAY - elapsed);
        }
        return oepp;
    }

    protected ClientModel getClientInActualOrMasterRealm(KeycloakSession keycloakSession, String clientId) {
        logger.debug("Getting actual realm from session's context");
        RealmModel realm = keycloakSession.getContext().getRealm();
        logger.debugf("Trying getting '%s' client in current realm: '%s'", clientId, realm.getName());
        ClientModel client = realm.getClientByClientId(clientId);
        if (client == null) {
            if (!MASTER_REALM_NAME.equals(realm.getName())) {
                logger.debugf("Not found. Now trying getting '%s' in '%s' realm", clientId, MASTER_REALM_NAME);
                realm = keycloakSession.realms().getRealmByName(MASTER_REALM_NAME);
                client = realm.getClientByClientId(clientId);
                if (client == null) {
                    logger.warnf("Cannot find '%s' client not even in '%s' realm", clientId, realm.getName(),
                            MASTER_REALM_NAME);
    
                    return null;
                }
            } else {
                logger.trace("Not found.");
            }
        }
        logger.debugf("Client '%s' found", clientId);
        return client;
    }

    @Override
    public String getId() {
        return "orchestrator-event-publisher";
    }

    @Override
    public void init(Scope scope) {
    }

    @Override
    public void postInit(KeycloakSessionFactory keycloakSessionFactory) {
    }

}
