package com.zioinfo.kintex.tenant;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.zioinfo.kintex.auth.JwtService;
import com.zioinfo.kintex.auth.KintexPrincipal;
import com.zioinfo.kintex.common.ApiResponse;
import com.zioinfo.kintex.common.ApiResponse.ApiError;
import com.zioinfo.kintex.common.error.ErrorCode;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

/**
 * 테넌트 컨텍스트 해소 필터 (PLANNING §1A-3 · §8-2 필터 계층).
 *
 * <p><b>2단계(JWT tid 권위)</b> — 인증 요청은 JWT {@code tid} 클레임을 신뢰 원천으로 삼는다(§1A-3-2):
 * <ul>
 *   <li><b>인증 요청</b>(유효 Bearer): 테넌트 = JWT {@code tid}. 헤더 {@code X-Tenant-Id}가 tid와
 *       <b>다른</b> 테넌트를 명시하면 → 플랫폼 관리자(전역 ADMIN 또는 홀매니저)만 대상 테넌트로 스위치 허용
 *       (감사 대상 크로스-테넌트), 그 외 주체는 <b>403</b>(오접속/하이재킹 차단, fail-closed). 스위치 대상이
 *       미존재 테넌트면 <b>400</b>.</li>
 *   <li><b>미인증 요청</b>(공개 경로): 1단계 해소 유지 — ① {@code X-Tenant-Id}(미존재 시 400) →
 *       ② Host 도메인 매칭 → ③ 기준 테넌트('KINTEX').</li>
 * </ul>
 *
 * <p>회귀 0: 기존 인증 클라이언트는 tid=KINTEX 토큰 + 헤더 미전송 → 테넌트 KINTEX(동일). tid 부재 레거시
 * 토큰도 {@link JwtService#verify}가 KINTEX로 정규화 → 동일. 공개 경로는 1단계와 동일 해소.
 *
 * <p>필터 순서 비의존: JWT를 자체 파싱하므로 {@code JwtAuthenticationFilter} 와의 등록 순서에 무관하게 동작한다.
 */
public class TenantContextFilter extends OncePerRequestFilter {

    private static final String HEADER = "X-Tenant-Id";
    private static final String BEARER = "Bearer ";

    private final TenantResolver resolver;
    private final ObjectMapper objectMapper;
    private final JwtService jwtService;

    public TenantContextFilter(TenantResolver resolver, ObjectMapper objectMapper, JwtService jwtService) {
        this.resolver = resolver;
        this.objectMapper = objectMapper;
        this.jwtService = jwtService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {
        String headerTenant = trimToNull(request.getHeader(HEADER));
        KintexPrincipal principal = tryPrincipal(request);

        String tenantId;
        if (principal != null) {
            // 인증 요청 — JWT tid 권위(§1A-3-2).
            String jwtTenant = principal.tenantId(); // 이미 KINTEX 기본 정규화됨
            if (headerTenant != null
                    && !equalsTenant(headerTenant, jwtTenant)) {
                // 명시적 크로스-테넌트 스위치 시도.
                boolean platformAdmin = principal.isAdmin() || principal.hallManager();
                if (!platformAdmin) {
                    writeForbiddenTenant(response); // 403 — 하이재킹/오접속 차단
                    return;
                }
                if (!resolver.exists(headerTenant)) {
                    writeBadTenant(response);        // 400 — 미존재 테넌트
                    return;
                }
                tenantId = headerTenant;             // 관리자 명시 스위치(감사 로깅은 컨트롤러/감사 레이어)
            } else {
                tenantId = jwtTenant;                // 기본: JWT tid
            }
        } else {
            // 미인증(공개 경로) — 1단계 해소 유지: 헤더 → 도메인 → 기준 테넌트.
            if (headerTenant != null) {
                if (!resolver.exists(headerTenant)) {
                    writeBadTenant(response);
                    return;
                }
                tenantId = headerTenant;
            } else {
                String byDomain = resolver.resolveByDomain(request.getHeader("Host"));
                tenantId = (byDomain != null) ? byDomain : TenantContext.DEFAULT_TENANT;
            }
        }

        try {
            TenantContext.set(tenantId);
            chain.doFilter(request, response);
        } finally {
            TenantContext.clear();
        }
    }

    /** Bearer 토큰이 유효하면 principal 복원, 없거나 무효면 null(공개 경로 취급). 접근 제어는 하지 않는다. */
    private KintexPrincipal tryPrincipal(HttpServletRequest request) {
        String header = request.getHeader("Authorization");
        if (header != null && header.startsWith(BEARER)) {
            try {
                return jwtService.verify(header.substring(BEARER.length()));
            } catch (Exception ignored) {
                // 무효 토큰 → 미인증으로 취급(도메인/기준 테넌트 해소). 실제 401은 인증 필터/엔트리포인트가 처리.
            }
        }
        return null;
    }

    private static boolean equalsTenant(String a, String b) {
        String na = TenantContext.normalize(a);
        String nb = TenantContext.normalize(b);
        return na == null ? nb == null : na.equals(nb);
    }

    private static String trimToNull(String v) {
        if (v == null) {
            return null;
        }
        String t = v.trim();
        return t.isEmpty() ? null : t;
    }

    private void writeBadTenant(HttpServletResponse response) throws IOException {
        writeError(response, ErrorCode.VALIDATION, "알 수 없는 테넌트입니다.");
    }

    private void writeForbiddenTenant(HttpServletResponse response) throws IOException {
        writeError(response, ErrorCode.FORBIDDEN, "요청한 테넌트에 접근할 수 없습니다.");
    }

    private void writeError(HttpServletResponse response, ErrorCode code, String message) throws IOException {
        response.setStatus(code.status().value());
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(
                ApiResponse.fail(new ApiError(code.name(), message))));
    }
}
