package com.erp.provisioning;

import com.erp.tenant.ReservedSubdomains;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSession;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.security.SecureRandom;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Phase 3-A 프로비저닝 API.
 *
 *  GET  /api/admin/provisioning/table-groups         — Step 2 체크박스 렌더용
 *  GET  /api/admin/provisioning/check                — 실시간 중복/포맷 검증
 *  POST /api/admin/provisioning/companies            — 마법사 최종 submit (202 accepted)
 *  GET  /api/admin/provisioning/status/{id}          — Step 4 폴링
 *
 * ⚠ 인증: Phase 3-A 에선 SUPER_ADMIN 권한 체크를 생략하고 SecurityConfig 차원에서 permitAll.
 *   실운영 배포 전에 Controller 앞단에 권한 검증을 추가해야 함 (JwtAuthenticationFilter 가 세팅한 user_type attribute 검사).
 */
@RestController
@RequestMapping("/api/admin/provisioning")
@RequiredArgsConstructor
@Slf4j
public class ProvisioningController {

    private final CompanyProvisioningService service;
    private final ProvisioningRegistry registry;
    private final SqlSession sqlSession;
    private final CompanyStatsService statsService;
    private final SuperAdminGuard guard;

    @GetMapping("/table-groups")
    public ResponseEntity<List<Map<String, Object>>> tableGroups(HttpServletRequest request) {
        enforceSuperAdmin(request);
        return ResponseEntity.ok(Arrays.stream(TableGroup.values()).map(TableGroup::toMap).toList());
    }

    /**
     * 회사관리 UI (v9 accordion 메인 화면) 전용 리스트 + derived 집계.
     * 정적 필드는 COMPANY_MNG 에서, users/db_size 등은 tenant DB/메타 서버 런타임 조회.
     * N+1 — MVP. 회사 50 개 넘어가면 병렬/캐시 도입 필요.
     */
    @GetMapping("/companies-stats")
    public ResponseEntity<List<Map<String, Object>>> companiesStats(HttpServletRequest request) {
        enforceSuperAdmin(request);
        return ResponseEntity.ok(statsService.listWithStats());
    }

    @GetMapping("/check")
    public ResponseEntity<Map<String, Object>> check(
            HttpServletRequest request,
            @RequestParam(required = false) String subdomain,
            @RequestParam(required = false) String dbPrefix,
            @RequestParam(required = false) String companyCode) {
        enforceSuperAdmin(request);
        Map<String, Object> result = new LinkedHashMap<>();

        if (subdomain != null) {
            boolean formatOk = isValidSubdomain(subdomain);
            boolean exists = false;
            if (formatOk) {
                Map<String, Object> p = new HashMap<>();
                p.put("subdomain", subdomain);
                exists = sqlSession.selectOne("provisioning.existsSubdomain", p) != null;
            }
            Map<String, Object> sub = new LinkedHashMap<>();
            sub.put("value", subdomain);
            sub.put("valid_format", formatOk);
            sub.put("reserved", ReservedSubdomains.VALUES.contains(subdomain));
            sub.put("available", formatOk && !exists);
            sub.put("url_preview", subdomain + ".invyone.com");
            result.put("subdomain", sub);
        }

        if (dbPrefix != null) {
            boolean formatOk = isValidDbPrefix(dbPrefix);
            String dbName = dbPrefix + "_invyone";
            boolean exists = false;
            if (formatOk) {
                Map<String, Object> p = new HashMap<>();
                p.put("db_name", dbName);
                exists = sqlSession.selectOne("provisioning.existsDbName", p) != null;
            }
            Map<String, Object> pre = new LinkedHashMap<>();
            pre.put("value", dbPrefix);
            pre.put("db_name", dbName);
            pre.put("valid_format", formatOk);
            pre.put("available", formatOk && !exists);
            result.put("db_prefix", pre);
        }

        if (companyCode != null) {
            boolean formatOk = isValidCompanyCode(companyCode);
            boolean exists = false;
            if (formatOk) {
                Map<String, Object> p = new HashMap<>();
                p.put("company_code", companyCode);
                exists = sqlSession.selectOne("provisioning.existsCompanyCode", p) != null;
            }
            Map<String, Object> cc = new LinkedHashMap<>();
            cc.put("value", companyCode);
            cc.put("valid_format", formatOk);
            cc.put("available", formatOk && !exists);
            result.put("company_code", cc);
        }

        return ResponseEntity.ok(result);
    }

    @PostMapping("/companies")
    public ResponseEntity<Map<String, Object>> create(HttpServletRequest request,
                                                       @RequestBody Map<String, Object> req) {
        enforceSuperAdmin(request);
        String companyCode = asStr(req.get("company_code"));
        String companyName = asStr(req.get("company_name"));
        String subdomain = asStr(req.get("subdomain"));
        String dbPrefix = asStr(req.getOrDefault("db_prefix", subdomain));

        // 필수 필드
        if (companyCode == null || companyName == null || subdomain == null || dbPrefix == null) {
            return ResponseEntity.badRequest().body(Map.of(
                    "error", "missing_required",
                    "message", "company_code, company_name, subdomain, db_prefix 필수"));
        }
        // 포맷
        if (!isValidCompanyCode(companyCode)) {
            return ResponseEntity.badRequest().body(Map.of(
                    "error", "invalid_format", "field", "company_code"));
        }
        if (!isValidSubdomain(subdomain)) {
            return ResponseEntity.badRequest().body(Map.of(
                    "error", "invalid_format", "field", "subdomain"));
        }
        if (!isValidDbPrefix(dbPrefix)) {
            return ResponseEntity.badRequest().body(Map.of(
                    "error", "invalid_format", "field", "db_prefix"));
        }
        // 중복
        String dbName = dbPrefix + "_invyone";
        if (sqlSession.selectOne("provisioning.existsCompanyCode", Map.of("company_code", companyCode)) != null) {
            return ResponseEntity.status(409).body(Map.of("error", "duplicate", "field", "company_code"));
        }
        if (sqlSession.selectOne("provisioning.existsSubdomain", Map.of("subdomain", subdomain)) != null) {
            return ResponseEntity.status(409).body(Map.of("error", "duplicate", "field", "subdomain"));
        }
        if (sqlSession.selectOne("provisioning.existsDbName", Map.of("db_name", dbName)) != null) {
            return ResponseEntity.status(409).body(Map.of("error", "duplicate", "field", "db_prefix"));
        }

        Map<String, Object> reqMutable = new HashMap<>(req);
        reqMutable.put("db_prefix", dbPrefix);
        if (reqMutable.get("initial_password") == null || ((String) reqMutable.get("initial_password")).isBlank()) {
            reqMutable.put("initial_password", generateRandomPassword());
        }

        ProvisioningStatus status;
        try {
            status = service.initiate(reqMutable);
        } catch (DataIntegrityViolationException e) {
            // 사전 체크 후 race condition 으로 끼어든 동일 요청 → DB UNIQUE 제약이 잡아줌
            return ResponseEntity.status(409).body(Map.of(
                    "error", "duplicate",
                    "message", "동시 요청에서 회사 코드/서브도메인/DB 이름이 이미 사용됨"));
        }
        service.provisionAsync(status.getId(), reqMutable);

        Map<String, Object> body = new LinkedHashMap<>();
        body.put("provisioning_id", status.getId());
        body.put("company_code", companyCode);
        body.put("db_name", status.getDbName());
        body.put("subdomain", subdomain);
        body.put("admin_user_id", dbPrefix + "_admin");
        body.put("initial_password", reqMutable.get("initial_password"));
        body.put("status_url", "/api/admin/provisioning/status/" + status.getId());
        // 초기 비밀번호가 본문에 포함되므로 캐시·프록시·브라우저 히스토리에 저장 금지
        return ResponseEntity.accepted()
                .header("Cache-Control", "no-store")
                .header("Pragma", "no-cache")
                .body(body);
    }

    @GetMapping("/status/{id}")
    public ResponseEntity<Map<String, Object>> status(HttpServletRequest request,
                                                       @PathVariable("id") String id) {
        enforceSuperAdmin(request);
        ProvisioningStatus s = registry.get(id);
        if (s == null) return ResponseEntity.notFound().build();
        return ResponseEntity.ok(s.toMap());
    }

    // ------------------------------------------------------------------
    // 권한 체크 — SuperAdminGuard 로 위임 (호스트 격리 + role 검증).
    // CompanyMgmtController 와 동일한 가드를 공유.
    // ------------------------------------------------------------------
    private void enforceSuperAdmin(HttpServletRequest request) {
        guard.enforce(request);
    }

    // --- Validation helpers ---

    private static boolean isValidCompanyCode(String s) {
        return s != null && s.matches("^[A-Z][A-Z0-9_]{2,30}$");
    }

    private static boolean isValidSubdomain(String s) {
        return s != null
                && s.matches("^[a-z][a-z0-9-]{2,30}$")
                && !ReservedSubdomains.VALUES.contains(s);
    }

    private static boolean isValidDbPrefix(String s) {
        return s != null && s.matches("^[a-z][a-z0-9_]{2,30}$");
    }

    private static String asStr(Object v) {
        return (v == null) ? null : v.toString().trim().isEmpty() ? null : v.toString().trim();
    }

    private static String generateRandomPassword() {
        String chars = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789@#$";
        SecureRandom r = new SecureRandom();
        StringBuilder sb = new StringBuilder(12);
        for (int i = 0; i < 12; i++) sb.append(chars.charAt(r.nextInt(chars.length())));
        return sb.toString();
    }
}
