package io.hawt.web.auth.keycloak;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import io.hawt.web.servlets.ConfigurationServlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import io.hawt.util.IOHelper;
import io.hawt.util.Strings;
import io.hawt.web.ServletHelpers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Servlet, which aims to return:
 * - whether keycloak is enabled (true/false) if path '/enabled' is used
 * - keycloak.json to be used by keycloak JS adapter on frontend if path '/client-config' is used
 * - validate if current JAAS logged subject is same like SSO user logged through keycloak
 * if path '/validate-subject-matches' is used
 */
public class KeycloakServlet extends ConfigurationServlet {

    private static final Logger LOG = LoggerFactory.getLogger(KeycloakServlet.class);

    /**
     * Keycloak config file used for frontend. Will use default location if not provided.
     */
    public static final String KEYCLOAK_CLIENT_CONFIG = "keycloakClientConfig";

    public static final String HAWTIO_KEYCLOAK_CLIENT_CONFIG = "hawtio." + KEYCLOAK_CLIENT_CONFIG;

    private String keycloakConfig = null;
    private boolean keycloakEnabled = false;

    @Override
    protected String getDefaultPath() {
        return "client-config";
    }

    @Override
    public void init() throws ServletException {
        super.init();

        keycloakEnabled = authConfiguration.isKeycloakEnabled();
        LOG.info("Keycloak integration is {}", keycloakEnabled ? "enabled" : "disabled");
        if (!keycloakEnabled) {
            return;
        }

        String keycloakConfigFile = configManager.get(KEYCLOAK_CLIENT_CONFIG).orElse(null);

        // JVM system properties can override always
        if (System.getProperty(HAWTIO_KEYCLOAK_CLIENT_CONFIG) != null) {
            keycloakConfigFile = System.getProperty(HAWTIO_KEYCLOAK_CLIENT_CONFIG);
        }

        if (Strings.isBlank(keycloakConfigFile)) {
            keycloakConfigFile = defaultKeycloakConfigLocation();
        }

        LOG.info("Looking for Keycloak configuration file in: {}", keycloakConfigFile);

        InputStream is = ServletHelpers.loadFile(keycloakConfigFile);
        if (is != null) {
            LOG.info("Reading Keycloak client configuration.");
            try {
                BufferedReader reader = new BufferedReader(new InputStreamReader(is));
                String keycloakConfig = IOHelper.readFully(reader);

                // Minify for perf purposes
                this.keycloakConfig = keycloakConfig.replaceAll(" ", "").replaceAll(System.lineSeparator(), "");
            } catch (IOException ioe) {
                LOG.warn("Couldn't read keycloak configuration file", ioe);
            } finally {
                IOHelper.close(is, "keycloakInputStream", LOG);
            }
        }
    }

    /**
     * Will try to guess the config location based on the server where hawtio is running.
     * Used just if keycloakClientConfig is not provided
     *
     * @return config to be used by default
     */
    protected String defaultKeycloakConfigLocation() {
        String karafBase = System.getProperty("karaf.base");
        if (karafBase != null) {
            return karafBase + "/etc/keycloak.json";
        }

        String jettyHome = System.getProperty("jetty.home");
        if (jettyHome != null) {
            return jettyHome + "/etc/keycloak.json";
        }

        String tomcatHome = System.getProperty("catalina.home");
        if (tomcatHome != null) {
            return tomcatHome + "/conf/keycloak.json";
        }

        String jbossHome = System.getProperty("jboss.server.config.dir");
        if (jbossHome != null) {
            return jbossHome + "/keycloak.json";
        }

        // Fallback to classpath inside hawtio.war
        return "classpath:keycloak.json";
    }

    @Override
    protected void handleGet(String path, HttpServletRequest request, HttpServletResponse response) throws IOException {
        switch (path) {
            case "enabled":
                ServletHelpers.sendJSONResponse(response, keycloakEnabled);
                break;
            case "client-config":
                if (keycloakConfig == null) {
                    response.sendError(404, "Keycloak client configuration not found");
                } else {
                    ServletHelpers.sendJSONResponse(response, keycloakConfig);
                }
                break;
            case "validate-subject-matches":
                String keycloakUser = request.getParameter("keycloakUser");
                if (Strings.isBlank(keycloakUser)) {
                    LOG.warn("Parameter 'keycloakUser' not found");
                    response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                    break;
                }
                boolean valid = validateKeycloakUser(request, keycloakUser);
                ServletHelpers.sendJSONResponse(response, valid);
                break;
            default:
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
        }
    }

    protected boolean validateKeycloakUser(HttpServletRequest request, String keycloakUser) {
        HttpSession session = request.getSession(false);

        // No session available. No existing subject logged
        if (session == null) {
            return true;
        }

        String username = (String) session.getAttribute("user");
        if (username != null && !username.equals(keycloakUser)) {
            LOG.debug("No matching username found. JAAS username: {}, keycloakUsername: {}. Invalidating session", username, keycloakUser);
            session.invalidate();
            return false;
        } else {
            return true;
        }
    }

}
