package com.mzh.library.filter;

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

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

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class AuthorizationFilter implements Filter {
    private static final Logger LOGGER = Logger.getLogger(AuthorizationFilter.class.getName());
    private static final String UNAUTHORIZED_JSP = "/WEB-INF/jsp/auth/unauthorized.jsp";
    private static final List<PathRule> RULES = Arrays.asList(
        new PathRule("/admin/system-logs", Permission.VIEW_SYSTEM_LOGS),
        new PathRule("/reports", Permission.VIEW_REPORTS),
        new PathRule("/borrowing", Permission.MANAGE_BORROWING),
        new PathRule("/book-categories", Permission.MANAGE_BOOKS),
        new PathRule("/books", Permission.MANAGE_BOOKS),
        new PathRule("/readers", Permission.MANAGE_READERS),
        new PathRule("/catalog", Permission.VIEW_CATALOG),
        new PathRule("/admin", Permission.MANAGE_USERS),
        new PathRule("/librarian", Permission.MANAGE_BORROWING),
        new PathRule("/reader/loans", Permission.BORROW_BOOKS, Role.READER),
        new PathRule("/reader", Permission.VIEW_CATALOG)
    );

    private final AuthService authService = new AuthServiceImpl(new JdbcUserDao());

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        String path = relativePath(httpRequest);
        PathRule requiredRule = requiredRule(path);

        if (requiredRule == null) {
            chain.doFilter(request, response);
            return;
        }

        AuthenticatedUser user = currentUser(httpRequest.getSession(false));
        if (requiredRule.allows(authService, user)) {
            chain.doFilter(request, response);
            return;
        }

        logDeniedAccess(user, requiredRule, path);
        httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
        request.setAttribute("errorMessage", "您无权访问此页面。");
        request.getRequestDispatcher(UNAUTHORIZED_JSP).forward(request, response);
    }

    private void logDeniedAccess(AuthenticatedUser user, PathRule requiredRule, String path) {
        String actor = user == null
            ? "anonymous"
            : "userId=" + user.getId() + " role=" + user.getRole().getCode();
        LOGGER.warning("Permission denied path=" + path
            + " requiredPermission=" + requiredRule.permission.getCode()
            + " requiredRole=" + requiredRule.requiredRoleCode()
            + " actor=" + actor);
    }

    private PathRule requiredRule(String path) {
        for (PathRule rule : RULES) {
            if (path.equals(rule.prefix) || path.startsWith(rule.prefix + "/")) {
                return rule;
            }
        }
        return null;
    }

    private AuthenticatedUser currentUser(HttpSession session) {
        if (session == null) {
            return null;
        }
        Object value = session.getAttribute(SessionAttributes.AUTHENTICATED_USER);
        return value instanceof AuthenticatedUser ? (AuthenticatedUser) value : null;
    }

    private String relativePath(HttpServletRequest request) {
        String contextPath = request.getContextPath();
        String requestUri = request.getRequestURI();
        return requestUri.substring(contextPath.length());
    }

    private static final class PathRule {
        private final String prefix;
        private final Permission permission;
        private final Role requiredRole;

        private PathRule(String prefix, Permission permission) {
            this(prefix, permission, null);
        }

        private PathRule(String prefix, Permission permission, Role requiredRole) {
            this.prefix = prefix;
            this.permission = permission;
            this.requiredRole = requiredRole;
        }

        private boolean allows(AuthService authService, AuthenticatedUser user) {
            boolean roleAllowed = requiredRole == null || (user != null && user.getRole() == requiredRole);
            return roleAllowed && authService.hasPermission(user, permission);
        }

        private String requiredRoleCode() {
            return requiredRole == null ? "any" : requiredRole.getCode();
        }
    }
}
