package com.erp.crosstenant;

import com.erp.dto.ApiResponse;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * SUPER_ADMIN 의 cross-tenant 어드민 합산 엔드포인트.
 *
 * 모든 엔드포인트는 진입 시 두 가드를 통과해야 한다:
 *   1. JWT role == "SUPER_ADMIN"            → 미통과 시 403
 *   2. 현재 컨텍스트 == META DB              → 미통과 시 400
 * 통과 후 {@link CrossTenantAggregator#fanOut} 으로 회사 N 개에 fan-out.
 *
 * Phase A (2026-04-27): 인프라만. 스모크 테스트용 {@code /_active-companies} 엔드포인트 1개.
 * Phase B 부터 {@code /users}, {@code /menus} ... 14개 메뉴 fan-out 엔드포인트 추가 예정.
 *
 * 라우팅 규약: admin.invyone.com 또는 메인 도메인(메타 컨텍스트) 에서만 호출 가능.
 */
@RestController
@RequestMapping("/api/admin/cross-tenant")
@RequiredArgsConstructor
@Slf4j
public class CrossTenantController {

    /**
     * 회사당 cap 디폴트. cross-tenant 는 "전사 둘러보기" 용이라 정확한 페이지네이션 불필요 —
     * 200건 넘으면 검색으로 좁히거나 회사 도메인 단일 모드로 전환하는 게 본 설계의 의도.
     * 호출자가 ?per_company_limit= 으로 override 가능.
     */
    private static final int DEFAULT_PER_COMPANY_LIMIT = 200;
    private static final int MAX_PER_COMPANY_LIMIT = 2000;   // 안전 가드

    private final CrossTenantAggregator aggregator;

    /**
     * Phase A 스모크 테스트 엔드포인트.
     *
     * 가드 두 개 통과 후 메타 DB 의 {@code COMPANY_MNG} 에서 활성 회사 목록 반환.
     * Aggregator 의 fan-out 자체는 호출하지 않음 — Phase B 에서 첫 mapper (listUsers) 추가 시 활성화.
     *
     * 검증 항목:
     *   - 컨트롤러 라우팅 정상
     *   - SUPER_ADMIN 가드 (다른 role 이면 403)
     *   - META 컨텍스트 가드 (회사 도메인이면 400)
     *   - {@code provisioning.listActiveCompanies} mapper 등록 확인
     */
    @GetMapping("/_active-companies")
    public ResponseEntity<ApiResponse<Map<String, Object>>> activeCompaniesSmoke(HttpServletRequest request) {
        try {
            CrossTenantContext.requireManagementHost(request);
        } catch (org.springframework.web.server.ResponseStatusException e) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN)
                    .body(ApiResponse.error(e.getReason(), request.getRequestURI()));
        }
        if (!CrossTenantContext.isSuperAdmin(request)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN)
                    .body(ApiResponse.error("super_admin_required", request.getRequestURI()));
        }
        if (!CrossTenantContext.isMetaContext()) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                    .body(ApiResponse.error("cross_tenant_requires_meta_context", request.getRequestURI()));
        }

        List<Map<String, Object>> rows = aggregator.listActiveCompaniesForSmokeTest();

        Map<String, Object> data = new LinkedHashMap<>();
        data.put("rows", rows);
        data.put("total", rows.size());
        return ResponseEntity.ok(ApiResponse.success(data, "Phase A smoke test ok"));
    }

    /**
     * GET /api/admin/cross-tenant/users
     *
     * 활성 회사 N 개에 fan-out 으로 사용자 목록 합산.
     * 응답 행마다 {@code company_code} 박혀있어 화면 측에서 회사 컬럼/필터 가능.
     *
     * 지원 파라미터: search, status, dept_code (단일 회사 화면과 동일).
     * 페이지네이션은 1차 구현에선 비지원 — 회사당 전체 반환 후 클라이언트에서 페이지네이션 (설계서 §9.3).
     *
     * 회사 한 곳이 실패해도 나머지는 반환 (실패 격리). 응답에 {@code companies_failed} +
     * 헤더 {@code X-CrossTenant-Failed} 로 어떤 회사가 빠졌는지 명시.
     */
    @GetMapping("/users")
    public ResponseEntity<ApiResponse<Map<String, Object>>> listUsers(
            HttpServletRequest request,
            @RequestParam Map<String, Object> queryParams) {
        try {
            CrossTenantContext.requireManagementHost(request);
        } catch (org.springframework.web.server.ResponseStatusException e) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN)
                    .body(ApiResponse.error(e.getReason(), request.getRequestURI()));
        }
        if (!CrossTenantContext.isSuperAdmin(request)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN)
                    .body(ApiResponse.error("super_admin_required", request.getRequestURI()));
        }
        if (!CrossTenantContext.isMetaContext()) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                    .body(ApiResponse.error("cross_tenant_requires_meta_context", request.getRequestURI()));
        }

        // search 파라미터 ILIKE 패턴화 — admin.selectUserList 와 동일한 변환 규칙 (양쪽 % 감쌈)
        Map<String, Object> params = new HashMap<>(queryParams);
        Object rawSearch = params.get("search");
        if (rawSearch != null && !String.valueOf(rawSearch).isBlank()) {
            params.put("search", "%" + rawSearch + "%");
        }

        int perCompanyLimit = resolvePerCompanyLimit(params);
        params.put("per_company_limit_plus_one", perCompanyLimit + 1);

        // ★ /users 는 includeMeta=true — 메타 DB 의 SUPER_ADMIN 들도 함께 반환 (company_code='*').
        // SUPER_ADMIN 자기 자신들도 어디선가 관리되어야 한다는 요구. roles/batches/lang-keys 는 메타에 의미있는 데이터 없으니 false.
        CrossTenantAggregator.Result result = aggregator.fanOut(
                "admin-cross-tenant.listUsers", params, perCompanyLimit, true);

        return buildResponse(result, perCompanyLimit);
    }

    /**
     * GET /api/admin/cross-tenant/roles
     *
     * 활성 회사 N 개에 fan-out 으로 권한 그룹 목록 합산.
     * 단일 회사 GET /api/roles 와 동일한 컬럼 + 행마다 company_code 추가.
     *
     * 지원 파라미터: search (AUTH_NAME ILIKE 검색).
     */
    @GetMapping("/roles")
    public ResponseEntity<ApiResponse<Map<String, Object>>> listRoles(
            HttpServletRequest request,
            @RequestParam Map<String, Object> queryParams) {
        return runFanOut(request, queryParams, "admin-cross-tenant.listRoleGroups", true);
    }

    /**
     * GET /api/admin/cross-tenant/batches
     *
     * 활성 회사 N 개에 fan-out 으로 배치 목록 합산. 행마다 company_code 박힘.
     * 지원 파라미터: search, is_active.
     */
    @GetMapping("/batches")
    public ResponseEntity<ApiResponse<Map<String, Object>>> listBatches(
            HttpServletRequest request,
            @RequestParam Map<String, Object> queryParams) {
        // 배치는 search 를 SQL 안에서 '%' || #{search} || '%' 로 감싸므로 컨트롤러 변환 불필요
        return runFanOut(request, queryParams, "admin-cross-tenant.listBatches", false);
    }

    /**
     * GET /api/admin/cross-tenant/lang-keys
     *
     * 활성 회사 N 개에 fan-out 으로 다국어 키 목록 합산. 행마다 company_code 박힘.
     * 지원 파라미터: search, menu_code.
     */
    @GetMapping("/lang-keys")
    public ResponseEntity<ApiResponse<Map<String, Object>>> listLangKeys(
            HttpServletRequest request,
            @RequestParam Map<String, Object> queryParams) {
        return runFanOut(request, queryParams, "admin-cross-tenant.listLangKeys", true);
    }

    /**
     * 공통 fan-out 실행 헬퍼.
     *
     * @param wrapSearchWithPercent true 면 search 파라미터를 컨트롤러에서 % 로 감쌈
     *                              (mapper 가 그대로 ILIKE 에 넣는 방식 — listUsers, listRoleGroups, listLangKeys).
     *                              false 면 mapper 안에서 '%' || ? || '%' 로 직접 감싸는 케이스 (listBatches).
     */
    private ResponseEntity<ApiResponse<Map<String, Object>>> runFanOut(
            HttpServletRequest request,
            Map<String, Object> queryParams,
            String mapperId,
            boolean wrapSearchWithPercent) {
        try {
            CrossTenantContext.requireManagementHost(request);
        } catch (org.springframework.web.server.ResponseStatusException e) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN)
                    .body(ApiResponse.error(e.getReason(), request.getRequestURI()));
        }
        if (!CrossTenantContext.isSuperAdmin(request)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN)
                    .body(ApiResponse.error("super_admin_required", request.getRequestURI()));
        }
        if (!CrossTenantContext.isMetaContext()) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                    .body(ApiResponse.error("cross_tenant_requires_meta_context", request.getRequestURI()));
        }

        Map<String, Object> params = new HashMap<>(queryParams);
        if (wrapSearchWithPercent) {
            Object rawSearch = params.get("search");
            if (rawSearch != null && !String.valueOf(rawSearch).isBlank()) {
                params.put("search", "%" + rawSearch + "%");
            }
        }

        int perCompanyLimit = resolvePerCompanyLimit(params);
        params.put("per_company_limit_plus_one", perCompanyLimit + 1);

        CrossTenantAggregator.Result result = aggregator.fanOut(mapperId, params, perCompanyLimit);

        return buildResponse(result, perCompanyLimit);
    }

    /**
     * 회사당 cap 결정 — 쿼리 파라미터 {@code per_company_limit} 가 있으면 사용 (1~MAX 사이 클램프),
     * 없으면 디폴트 200.
     */
    private int resolvePerCompanyLimit(Map<String, Object> params) {
        Object raw = params.get("per_company_limit");
        if (raw == null) return DEFAULT_PER_COMPANY_LIMIT;
        try {
            int v = Integer.parseInt(String.valueOf(raw));
            if (v < 1) return DEFAULT_PER_COMPANY_LIMIT;
            if (v > MAX_PER_COMPANY_LIMIT) return MAX_PER_COMPANY_LIMIT;
            return v;
        } catch (NumberFormatException e) {
            return DEFAULT_PER_COMPANY_LIMIT;
        }
    }

    /**
     * Aggregator 결과 → 응답 봉투. truncated 정보 포함.
     * 응답 헤더에도 X-CrossTenant-Failed / X-CrossTenant-Truncated 박아 디버깅 편의.
     */
    private ResponseEntity<ApiResponse<Map<String, Object>>> buildResponse(
            CrossTenantAggregator.Result result, int perCompanyLimit) {
        Map<String, Object> data = new LinkedHashMap<>();
        data.put("rows", result.rows);
        data.put("total", result.rows.size());
        data.put("companies_queried", result.companies_queried);
        data.put("companies_failed", result.companies_failed);
        data.put("truncated", !result.truncated_company_codes.isEmpty());
        data.put("truncated_company_codes", result.truncated_company_codes);
        data.put("per_company_limit", perCompanyLimit);

        ResponseEntity.BodyBuilder builder = ResponseEntity.ok();
        if (!result.failed_company_codes.isEmpty()) {
            builder.header("X-CrossTenant-Failed", String.join(",", result.failed_company_codes));
        }
        if (!result.truncated_company_codes.isEmpty()) {
            builder.header("X-CrossTenant-Truncated", String.join(",", result.truncated_company_codes));
        }
        return builder.body(ApiResponse.success(data));
    }
}
