package org.keycloak.testsuite.performance.web;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import freemarker.cache.ClassTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.keycloak.adapters.AdapterConstants;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.util.Time;

/**
 * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
 */
public class PerfAppServlet extends HttpServlet {

    private Template indexTemplate;
    private OAuthClient oauthClient;

    @Override
    public void init() throws ServletException {
        try {
            Configuration cfg = new Configuration();
            cfg.setTemplateLoader(new ClassTemplateLoader(getClass(), "/"));
            indexTemplate = cfg.getTemplate("perf-app-resources/index.ftl");

            oauthClient = new OAuthClient();
        } catch (IOException ioe) {
            throw new ServletException(ioe);
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        String action = req.getParameter("action");
        String actionDone = null;

        if (action != null) {
            if (action.equals("code")) {
                keycloakLoginRedirect(req, resp);
                return;
            } else if (action.equals("exchangeCode")) {
                exchangeCodeForToken(req, resp);
                actionDone = "Token retrieved";
            } else if (action.equals("refresh")) {
                refreshToken(req, resp);
                actionDone = "Token refreshed";
            } else if (action.equals("logout")) {
                logoutRedirect(req, resp);
                return;
            }
        }

        String code = req.getParameter("code");
        if (code != null) {
            req.getSession().setAttribute("code", code);
            actionDone = "Code retrieved";
        }

        String freemarkerRedirect = freemarkerRedirect(req, resp, actionDone);
        resp.getWriter().println(freemarkerRedirect);
        resp.getWriter().flush();
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        if (req.getRequestURI().endsWith(AdapterConstants.K_LOGOUT)) {
            // System.out.println("Logout callback triggered");
            resp.setStatus(204);
        }
    }

    protected void keycloakLoginRedirect(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String loginUrl = oauthClient.getLoginFormUrl();
        resp.sendRedirect(loginUrl);
    }

    protected void exchangeCodeForToken(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String code = (String)req.getSession().getAttribute("code");
        OAuthClient.AccessTokenResponse atResponse = oauthClient.doAccessTokenRequest(code, "password");

        updateTokensInSession(req, atResponse);
    }

    protected void refreshToken(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String refreshToken = (String)req.getSession().getAttribute("refreshToken");
        OAuthClient.AccessTokenResponse atResponse = oauthClient.doRefreshTokenRequest(refreshToken, "password");

        updateTokensInSession(req, atResponse);
    }

    private void updateTokensInSession(HttpServletRequest req, OAuthClient.AccessTokenResponse atResponse) {
        String accessToken = atResponse.getAccessToken();
        String refreshToken = atResponse.getRefreshToken();
        AccessToken accessTokenParsed = oauthClient.verifyToken(accessToken);
        RefreshToken refreshTokenParsed = oauthClient.verifyRefreshToken(refreshToken);
        req.getSession().setAttribute("accessToken", accessToken);
        req.getSession().setAttribute("refreshToken", refreshToken);
        req.getSession().setAttribute("accessTokenParsed", accessTokenParsed);
        req.getSession().setAttribute("refreshTokenParsed", refreshTokenParsed);
    }

    protected void logoutRedirect(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String sessionState = null;
        AccessToken accessTokenParsed = (AccessToken)req.getSession().getAttribute("accessTokenParsed");
        if (accessTokenParsed != null) {
            sessionState = accessTokenParsed.getSessionState();
        }

        // Invalidate http session
        req.getSession(false).invalidate();

        String logoutURL = oauthClient.getLogoutUrl(oauthClient.getRedirectUri(), sessionState);
        resp.sendRedirect(logoutURL);
    }

    private String freemarkerRedirect(HttpServletRequest req, HttpServletResponse resp, String actionDone) throws ServletException, IOException {
        AccessToken accessTokenParsed = (AccessToken)req.getSession().getAttribute("accessTokenParsed");
        RefreshToken refreshTokenParsed = (RefreshToken)req.getSession().getAttribute("refreshTokenParsed");

        Map<String, Object> attributes = new HashMap<String, Object>();
        attributes.put("requestURI", req.getRequestURI());
        attributes.put("code",  req.getSession().getAttribute("code"));
        attributes.put("accessToken",  req.getSession().getAttribute("accessToken"));
        attributes.put("refreshToken",  req.getSession().getAttribute("refreshToken"));
        attributes.put("accessTokenParsed",  accessTokenParsed);
        attributes.put("refreshTokenParsed",  refreshTokenParsed);
        attributes.put("actionDone", actionDone);

        if (accessTokenParsed != null) {
            attributes.put("accessTokenExpiration", Time.toDate(accessTokenParsed.getExpiration()).toString());
        }
        if (refreshTokenParsed != null) {
            attributes.put("refreshTokenExpiration", Time.toDate(refreshTokenParsed.getExpiration()).toString());
        }

        try {
            Writer out = new StringWriter();
            indexTemplate.process(attributes, out);
            return out.toString();
        } catch (TemplateException te) {
            throw new ServletException(te);
        }
    }
}
