package com.erp.tenant;

import com.zaxxer.hikari.HikariDataSource;
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.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.regex.Pattern;

/**
 * Host 헤더에서 서브도메인을 파싱 → CompanyResolver 로 DB 이름 조회 →
 * TenantRoutingDataSource 에 tenant 풀이 없으면 lazy 생성 → DbContextHolder 세팅.
 *
 * Phase 2 (실제 라우팅):
 *   - localhost / IP / invyone.com / www / admin → META
 *   - 알 수 없는 서브도메인        → META (log.debug 만)
 *   - 등록된 서브도메인            → tenant DB 로 라우팅
 *
 * SecurityConfig 에서 JwtAuthenticationFilter 앞에 등록됨.
 */
@Slf4j
@RequiredArgsConstructor
public class SubdomainResolverFilter extends OncePerRequestFilter {

    private static final Pattern IPV4 = Pattern.compile("^\\d{1,3}(\\.\\d{1,3}){3}$");

    private final CompanyResolver companyResolver;
    private final TenantRoutingDataSource routingDataSource;
    private final TenantDbSettings tenantDbSettings;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {
        try {
            String host = request.getHeader("Host");
            String subdomain = extractSubdomain(host);

            if (subdomain == null) {
                DbContextHolder.setMeta();
                // 메인 도메인/IP/localhost 요청은 tenant attribute 없음 — 후속 필터가 스킵
            } else {
                // resolve 쿼리 자체는 메타 DB 를 타야 하므로 먼저 META 로 세팅.
                DbContextHolder.setMeta();
                CompanyResolver.Resolved resolved = companyResolver.resolve(subdomain);

                if (resolved == null) {
                    log.debug("[Tenant] unknown subdomain '{}' → META fallback (host={})", subdomain, host);
                    // META 유지. TenantConsistencyGuardFilter 가 JWT 붙은 요청이면 거부.
                    request.setAttribute("tenant_resolve_attempted", Boolean.TRUE);
                } else {
                    ensureTenantPool(resolved.dbName());
                    DbContextHolder.set(resolved.dbName());
                    request.setAttribute("tenant_db_name", resolved.dbName());
                    request.setAttribute("tenant_company_code", resolved.companyCode());
                    request.setAttribute("tenant_resolve_attempted", Boolean.TRUE);
                    log.info("[Tenant] routed: subdomain={} → db={}, company={} (path={})",
                            subdomain, resolved.dbName(), resolved.companyCode(), request.getRequestURI());
                }
            }

            chain.doFilter(request, response);
        } finally {
            DbContextHolder.clear();
        }
    }

    /** 회사 DB 풀이 없으면 최초 1회 생성. minIdle=0 정책은 Factory 가 책임. */
    private void ensureTenantPool(String dbName) {
        if (routingDataSource.hasTenant(dbName)) return;
        synchronized (routingDataSource) {
            if (routingDataSource.hasTenant(dbName)) return;
            HikariDataSource ds = TenantDataSourceFactory.createTenant(
                    tenantDbSettings.buildJdbcUrl(dbName),
                    tenantDbSettings.username(),
                    tenantDbSettings.password(),
                    dbName);
            routingDataSource.addTenant(dbName, ds);
        }
    }

    /**
     * Host 헤더에서 서브도메인 추출. 포트 제거 + IP/bare localhost/예약어 제외.
     *
     * 운영 (3파트, e.g. qnc.invyone.com) → 첫 파트
     * dev   (2파트, {sub}.localhost)    → 첫 파트 (RFC 6761, 별도 DNS 불필요)
     * 그 외 (invyone.com 같은 베이스 / bare localhost / IP) → null (META)
     */
    static String extractSubdomain(String host) {
        if (host == null || host.isBlank()) return null;

        int colon = host.indexOf(':');
        if (colon != -1) host = host.substring(0, colon);

        host = host.toLowerCase();

        if ("localhost".equals(host)) return null;
        if (IPV4.matcher(host).matches()) return null;

        String[] parts = host.split("\\.");

        // 2파트 — "{sub}.localhost" 만 허용 (dev 전용). invyone.com 같은 베이스 도메인은 null.
        if (parts.length == 2) {
            if (!"localhost".equals(parts[1])) return null;
            String first = parts[0];
            if (first.isEmpty()) return null;
            if (ReservedSubdomains.VALUES.contains(first)) return null;
            return first;
        }

        // 3파트 이상 (운영) — 첫 번째 파트가 서브도메인
        if (parts.length < 3) return null;
        String first = parts[0];
        if (ReservedSubdomains.VALUES.contains(first)) return null;

        return first;
    }
}
