package org.gcube.keycloak.event;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.Map;

import org.gcube.event.publisher.Event;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.keycloak.events.admin.AdminEvent;

public class KeycloakEvent extends Event {

    private static final long serialVersionUID = 4072256389444123291L;

    public static final String TYPE = "keycloak";

    // Common fields
    public static final String REALM = "realm";

    // Admin event fields
    public static final String OPERATION = "operation";
    public static final String REPRESENTATION = "representation";
    public static final String RESOURCE = "resource";
    public static final String RESOURCE_TYPE = "resource-type";

    // Event fields
    public static final String CLIENT = "client";
    public static final String ERROR = "error";
    public static final String SESSION = "session";
    public static final String REMOTE_ADDRESS = "remote-address";
    public static final String USER = "user";

    public static final String EVENT_NAME_PREFIX = "keycloak_";
    public static final String ADMIN_NAME = "admin";

    public static String HOST_NAME;

    static {
        try {
            HOST_NAME = InetAddress.getLocalHost().getCanonicalHostName();
        } catch (UnknownHostException e) {
            // Almost impossible
            e.printStackTrace();
        }
    }

    private KeycloakEvent(String name, String realm, long time) {
        this(name, realm, time, null);
    }

    private KeycloakEvent(String name, String realm, long time, Map<String, String> data) {
        super(EVENT_NAME_PREFIX + name, TYPE, HOST_NAME, data);

        // Overriding the timestamp setting it equal to the event time
        setTimestamp(convertEventDate(time));
        setRealm(realm);
    }

    public static KeycloakEvent newKeycloakAdminEvent(AdminEvent adminEvent, boolean includeRepresentation) {
        KeycloakEvent keycloakEvent = new KeycloakEvent(ADMIN_NAME, adminEvent.getRealmId(), adminEvent.getTime());

        keycloakEvent.setOperation(adminEvent.getOperationType().name());
        if (includeRepresentation) {
            keycloakEvent.setRepresentation(adminEvent.getRepresentation());
        }
        keycloakEvent.setResource(adminEvent.getResourcePath());
        keycloakEvent.setResourceType(adminEvent.getResourceTypeAsString());
        return keycloakEvent;
    }

    public static KeycloakEvent newKeycloakEvent(org.keycloak.events.Event event) {
        KeycloakEvent keycloakEvent = new KeycloakEvent(event.getType().name().toLowerCase(), event.getRealmId(),
                event.getTime(), event.getDetails());

        keycloakEvent.setError(event.getError());
        keycloakEvent.setClient(event.getClientId());
        keycloakEvent.setRemoteAddress(event.getIpAddress());
        keycloakEvent.setSession(event.getSessionId());
        keycloakEvent.setUser(event.getUserId());
        return keycloakEvent;
    }

    private static OffsetDateTime convertEventDate(long millis) {
        OrchestratorEventPublisherProvider.logger.debugf("Creating offset date time from millis %d -> %t", millis, millis);
        return Instant.ofEpochMilli(millis).atZone(ZoneOffset.systemDefault()).toOffsetDateTime();
    }

    public void setRealm(String realm) {
        set(REALM, realm);
    }

    public String getRealm() {
        return (String) get(REALM);
    }

    @SuppressWarnings("unchecked")
    public void setRepresentation(String representation) {
        try {
            put(REPRESENTATION, new JSONParser().parse(representation));
        } catch (ParseException e) {
            e.printStackTrace();
            set(REPRESENTATION, representation);
        }
    }

    public String getRepresentation() {
        return ((JSONObject) get(REPRESENTATION)).toString();
    }

    public void setResource(String resource) {
        set(RESOURCE, resource);
    }

    public String getResource() {
        return (String) get(RESOURCE);
    }

    public void setResourceType(String resourceType) {
        set(RESOURCE_TYPE, resourceType);
    }

    public String getResourceType() {
        return (String) get(RESOURCE_TYPE);
    }

    public void setClient(String client) {
        set(CLIENT, client);
    }

    public String getClient() {
        return (String) get(CLIENT);
    }

    public void setError(String error) {
        set(ERROR, error);
    }

    public String getError() {
        return (String) get(ERROR);
    }

    public void setOperation(String operation) {
        set(OPERATION, operation);
    }

    public String getOperation() {
        return (String) get(OPERATION);
    }

    public void setRemoteAddress(String remoteAddress) {
        set(REMOTE_ADDRESS, remoteAddress);
    }

    public String getRemoteAddress() {
        return (String) get(REMOTE_ADDRESS);
    }

    public void setSession(String session) {
        set(SESSION, session);
    }

    public String getSession() {
        return (String) get(SESSION);
    }

    public void setUser(String user) {
        set(USER, user);
    }

    public String getUser() {
        return (String) get(USER);
    }

}
