package com.erp.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Map;
import java.util.Set;

/**
 * 크로스-테넌트 요청 차단.
 *
 * 검증:
 *   JWT 가 있는 요청 && Host 가 테넌트 서브도메인 → JWT.company_code == tenant.company_code 여야 함.
 *
 * 동작:
 *   1. OPTIONS (CORS preflight) → 통과
 *   2. /api/** 가 아닌 요청 → 통과
 *   3. JWT 없는 요청 → 통과 (로그인/회원가입 등)
 *   4. JWT 있음 + 서브도메인 있음 + resolve 실패 → 거부 (악의적 요청 의심)
 *   5. JWT 있음 + tenant attribute 없음 (메인 도메인) → 통과 (SUPER_ADMIN 전역 API 등)
 *   6. JWT.company_code == tenant.company_code → 통과
 *   7. JWT.company_code == "*" → 제한된 SUPER_ADMIN pre-switch 경로만 통과
 *   8. 나머지 → 거부
 *
 * 필터 순서: SubdomainResolverFilter → JwtAuthenticationFilter → **여기** → ForcePasswordChangeGuardFilter
 */
@Slf4j
@RequiredArgsConstructor
public class TenantConsistencyGuardFilter extends OncePerRequestFilter {

    private final JwtTokenProvider jwtTokenProvider;

    /**
     * SUPER_ADMIN 가 company_code="*" 로 로그인한 직후, 실제 회사로 switch 하기 전에
     * 테넌트 서브도메인에서 허용해야 하는 최소 경로들.
     * 여기에 실제 업무 API 를 넣지 말 것 — "*" 는 어떤 회사 DB 도 대표하지 않는 상태.
     */
    private static final Set<String> STAR_ALLOWED_PATHS = Set.of(
            "/api/auth/switch-company",
            "/api/auth/me",
            "/api/auth/status",
            "/api/auth/logout",
            "/api/auth/refresh"
    );

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            chain.doFilter(request, response);
            return;
        }
        String path = request.getRequestURI();
        if (path == null || !path.startsWith("/api/")) {
            chain.doFilter(request, response);
            return;
        }

        String token = resolveToken(request);
        if (!StringUtils.hasText(token) || !jwtTokenProvider.validateToken(token)) {
            // 로그인/회원가입 등 JWT 없는 요청은 통과
            chain.doFilter(request, response);
            return;
        }

        String jwtCompanyCode = jwtTokenProvider.getCompanyCode(token);
        String tenantCompanyCode = (String) request.getAttribute("tenant_company_code");
        Boolean attempted = (Boolean) request.getAttribute("tenant_resolve_attempted");

        // (4) SUPER_ADMIN pre-switch escape — "*" 토큰 + 허용 경로는 tenant resolve 결과와 무관하게 통과.
        //     미등록/stale 서브도메인에 잘못 붙어도 switch-company / logout 으로 회복할 수 있어야 함.
        if ("*".equals(jwtCompanyCode) && STAR_ALLOWED_PATHS.contains(path)) {
            chain.doFilter(request, response);
            return;
        }

        // (5) 서브도메인은 있는데 resolve 실패 + JWT 있음 → 의심스러움. 거부.
        if (Boolean.TRUE.equals(attempted) && tenantCompanyCode == null) {
            deny(response, path, "TENANT_NOT_RESOLVED",
                    "등록되지 않은 서브도메인입니다.");
            return;
        }

        // (6) tenant attribute 자체가 없음 → 메인 도메인. 이 가드는 스킵.
        if (tenantCompanyCode == null) {
            chain.doFilter(request, response);
            return;
        }

        // (7) 정상 일치
        if (tenantCompanyCode.equals(jwtCompanyCode)) {
            chain.doFilter(request, response);
            return;
        }

        // (8) 거부
        log.warn("[TenantGuard] cross-tenant blocked: path={}, jwt={}, tenant={}",
                path, jwtCompanyCode, tenantCompanyCode);
        deny(response, path, "CROSS_TENANT_REJECTED",
                "로그인한 회사와 접속 도메인이 일치하지 않습니다.");
    }

    private void deny(HttpServletResponse response, String path, String errorCode, String message) throws IOException {
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.setContentType("application/json;charset=UTF-8");
        response.setHeader("Cache-Control", "no-store");
        new ObjectMapper().writeValue(response.getOutputStream(), Map.of(
                "success", false,
                "errorCode", errorCode,
                "message", message
        ));
    }

    private String resolveToken(HttpServletRequest request) {
        String bearer = request.getHeader("Authorization");
        if (StringUtils.hasText(bearer) && bearer.startsWith("Bearer ")) {
            return bearer.substring(7);
        }
        return null;
    }
}
