package com.mzh.library.controller;

import com.mzh.library.dao.impl.JdbcUserDao;
import com.mzh.library.entity.AuthenticatedUser;
import com.mzh.library.service.AuthService;
import com.mzh.library.service.AuthenticationResult;
import com.mzh.library.service.impl.AuthServiceImpl;
import com.mzh.library.util.SessionAttributes;

import java.io.IOException;
import java.util.logging.Logger;

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

public class LoginServlet extends HttpServlet {
    private static final Logger LOGGER = Logger.getLogger(LoginServlet.class.getName());
    private static final String LOGIN_JSP = "/WEB-INF/jsp/auth/login.jsp";
    private static final String DASHBOARD_PATH = "/dashboard";
    private static final int SESSION_TIMEOUT_SECONDS = 30 * 60;

    private AuthService authService;

    @Override
    public void init() {
        this.authService = new AuthServiceImpl(new JdbcUserDao());
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        if (isAuthenticated(request)) {
            response.sendRedirect(request.getContextPath() + DASHBOARD_PATH);
            return;
        }

        request.setAttribute("redirect", safeRedirect(request.getParameter("redirect")));
        request.getRequestDispatcher(LOGIN_JSP).forward(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String submittedUsername = request.getParameter("username");
        String username = trim(submittedUsername);
        String password = request.getParameter("password");
        String submittedRedirect = request.getParameter("redirect");
        String redirect = safeRedirect(submittedRedirect);

        logLoginPost(request, submittedUsername, username, password, submittedRedirect, redirect);

        AuthenticationResult result = authService.authenticate(username, password);
        if (!result.isAuthenticated()) {
            request.setAttribute("errorMessage", result.getMessage());
            request.setAttribute("username", username);
            request.setAttribute("redirect", redirect);
            request.getRequestDispatcher(LOGIN_JSP).forward(request, response);
            return;
        }

        resetSession(request, result.getUser());
        response.sendRedirect(resolveRedirect(request, redirect));
    }

    private void logLoginPost(
        HttpServletRequest request,
        String submittedUsername,
        String username,
        String password,
        String submittedRedirect,
        String redirect
    ) {
        LOGGER.info("Login POST reached"
            + " remoteAddr=" + safeLogValue(request.getRemoteAddr())
            + " contextPath=" + safeLogValue(request.getContextPath())
            + " redirectSubmitted=" + !trim(submittedRedirect).isEmpty()
            + " redirectAccepted=" + !redirect.isEmpty()
            + " usernameSubmitted=" + (submittedUsername != null)
            + " usernameLength=" + length(submittedUsername)
            + " normalizedUsernameLength=" + username.length()
            + " usernameNormalizedChanged=" + !username.equals(nullToEmpty(submittedUsername))
            + " passwordSubmitted=" + (password != null));
    }

    private boolean isAuthenticated(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        return session != null && session.getAttribute(SessionAttributes.AUTHENTICATED_USER) != null;
    }

    private void resetSession(HttpServletRequest request, AuthenticatedUser user) {
        HttpSession existingSession = request.getSession(false);
        if (existingSession != null) {
            existingSession.invalidate();
        }

        HttpSession session = request.getSession(true);
        session.setMaxInactiveInterval(SESSION_TIMEOUT_SECONDS);
        session.setAttribute(SessionAttributes.AUTHENTICATED_USER, user);
        session.setAttribute(SessionAttributes.USER_ROLE, user.getRole().getCode());
        session.setAttribute(SessionAttributes.USER_PERMISSIONS, user.getPermissionCodes());
    }

    private String resolveRedirect(HttpServletRequest request, String redirect) {
        if (redirect.isEmpty() || "/login".equals(redirect) || "/logout".equals(redirect)) {
            return request.getContextPath() + DASHBOARD_PATH;
        }

        return request.getContextPath() + redirect;
    }

    private String safeRedirect(String value) {
        String redirect = trim(value);
        if (redirect.startsWith("/")
            && !redirect.startsWith("//")
            && !redirect.contains("\r")
            && !redirect.contains("\n")) {
            return redirect;
        }
        return "";
    }

    private String trim(String value) {
        return value == null ? "" : value.trim();
    }

    private int length(String value) {
        return value == null ? 0 : value.length();
    }

    private String nullToEmpty(String value) {
        return value == null ? "" : value;
    }

    private String safeLogValue(String value) {
        if (value == null) {
            return "";
        }

        StringBuilder builder = new StringBuilder();
        int limit = Math.min(value.length(), 120);
        for (int i = 0; i < limit; i++) {
            char current = value.charAt(i);
            builder.append(Character.isISOControl(current) ? '?' : current);
        }
        if (value.length() > limit) {
            builder.append("...");
        }
        return builder.toString();
    }
}
