from __future__ import annotations
"""
테스트용 복제 페이지: /gov/motie/heads 의 동작을 그대로 두되
경로를 /gov/motie/heads_test, 템플릿을 gov/motie/heads_test.html 로 분리.
"""
from typing import Dict, List, Tuple
import re
import difflib
import logging
from fastapi import APIRouter, Request, Query
from fastapi.responses import HTMLResponse, JSONResponse
from app.web.deps import templates, sb

# 원본 heads.py의 정의를 그대로 재사용하여 동기화 이슈 방지
from .heads import (
    MOTIE_ORDER_POS as _MOTIE_ORDER_POS,
    _norm,
    _normalize_position as _normalize_position_base,
    _normalize_department,
    _canon_department,
    _pick_best,
)

router = APIRouter()
log = logging.getLogger("gov.motie.heads_test")

# heads_test 전용: 강화된 직위 정규화 테스트 버전
def _normalize_position(pos: str) -> str:
    p = _norm(pos)
    # 괄호 내용 전체 제거(위치 무관)
    p = re.sub(r"\(.*?\)", "", p)
    # 수식어 제거(위치 무관)
    p = re.sub(r"(직무대리|대행|겸임)", "", p)
    # '제 1차관' / '제1차관' → '1차관'
    p = re.sub(r"^제\s*([0-9]+차관)$", r"\1", p)
    # 공백 정리
    p = re.sub(r"\s+", " ", p).strip()
    return p

@router.get("/gov/motie/heads_test", response_class=HTMLResponse)
async def gov_motie_heads_ordered_test(
    request: Request,
    q: str = Query(default=""),
    debug: int = Query(default=0),
):
    # 0) 우선 DB 뷰(motie_heads_cur)가 있으면 그것을 사용 (정확도 우선)
    try:
        v = (
            sb.table("motie_heads_cur")
              .select("unit,position,name,phone,task,indent,ord")
              .order("ord", desc=False)
              .limit(5000)
              .execute()
              .data
        ) or []
    except Exception:
        v = []

    if v:
        items: List[dict] = []
        key = _norm(q).lower()
        for r in v:
            row = {
                "unit": r.get("unit"),
                "indent": r.get("indent") or 0,
                "name": r.get("name") or "(공석)",
                "position": r.get("position"),
                "phone": r.get("phone") or "-",
                "task": r.get("task") or "-",
            }
            hay = " ".join([str(x or "") for x in row.values()]).lower()
            if not key or key in hay:
                items.append(row)

        return templates.TemplateResponse(
            "gov/motie/heads_test.html",
            {
                "request": request,
                "ministry": "산업통상자원부(MOTIE) - 테스트",
                "q": q,
                "items": items,
                "debug": debug,
                "dbg_lines": [
                    "[info] using motie_heads_cur view",
                    f"[info] rows={len(v)}",
                ],
                "misses": [],
            },
        )

    # 1) 뷰가 없으면 기존 로컬 로직으로 계산(참고 용도)
    try:
        rows = (
            sb.table("motie_org_cur")
              .select("department,position,name,phone,task")
              .limit(50000)
              .execute()
              .data
        ) or []
    except Exception as e:
        log.exception("[heads_test] Supabase fetch failed: %s", e)
        rows = []

    dbg_lines: List[str] = []
    def _dbg(msg: str):
        dbg_lines.append(msg)
        if debug >= 1:
            log.debug(msg)

    _dbg(f"[heads_test] fetched_rows={len(rows)}")

    norm_rows: List[dict] = []
    for i, r in enumerate(rows):
        dep_raw = r.get("department") or ""
        pos_raw = r.get("position") or ""
        dep = _canon_department(dep_raw)
        pos = _normalize_position(pos_raw)
        name = _norm(r.get("name") or "")
        phone = _norm(r.get("phone") or "")
        task = _norm(r.get("task") or "")
        rec = {
            "department_raw": dep_raw, "department": dep,
            "position_raw": pos_raw,   "position": pos,
            "name": name, "phone": phone, "task": task
        }
        norm_rows.append(rec)

        if debug >= 2:
            changed = (dep_raw != dep) or (pos_raw != pos)
            if changed or debug >= 3:
                _dbg(f"[normalize#{i}] dep:'{dep_raw}'→'{dep}', pos:'{pos_raw}'→'{pos}', name:'{name}', phone:'{phone}'")

    by_key: Dict[Tuple[str, str], List[dict]] = {}
    by_dep: Dict[str, List[dict]] = {}
    by_pos: Dict[str, List[dict]] = {}
    for r in norm_rows:
        dep = r["department"]; pos = r["position"]
        if dep and pos:
            by_key.setdefault((dep, pos), []).append(r)
        if dep:
            by_dep.setdefault(dep, []).append(r)
        if pos:
            by_pos.setdefault(pos, []).append(r)
    _dbg(f"[heads_test] unique_keys={len(by_key)}")

    key = _norm(q).lower()
    items: List[dict] = []
    misses: List[Tuple[str, str]] = []

    def _pos_matches(expected: str, actual: str) -> bool:
        e = _normalize_position(expected)
        a = _normalize_position(actual)
        if a == e:
            return True
        # 대변인 케이스: '부대변인'은 제외하고, '대변인'을 포함하면 허용
        if e == "대변인":
            if a == "부대변인" or a.endswith("부대변인") or a.startswith("부대변인"):
                return False
            if "대변인" in a:
                return True
        # 일반 케이스: expected가 actual의 부분문자열이면서 너무 광범위하지 않은 경우 허용
        if e and a and e in a and len(e) >= 2:
            return True
        return False

    # heads_test는 정확도를 우선: 교차 부서 매칭은 금지
    # 허용 동치 부서(표기 변형)만 인정
    _DEPT_EQUIV: Dict[str, List[str]] = {
        "대변인": ["대변인실"],
        "제1차관": ["제1차관실"],
        "제2차관": ["제2차관실"],
    }

    for unit, indent, expected_pos in _MOTIE_ORDER_POS:
        dep_norm = _canon_department(unit)
        pos_norm = _normalize_position(expected_pos)

        cands = by_key.get((dep_norm, pos_norm), [])
        rec = _pick_best(cands)

        if rec:
            name  = rec["name"] or "(공석)"
            pos   = rec["position"] or pos_norm
            phone = rec["phone"] or "-"
            task  = rec["task"] or "-"
            if debug >= 1:
                _dbg(f"[match] OK unit='{unit}', key=('{dep_norm}','{pos_norm}'), picked='{name}', phone='{phone}' (cands={len(cands)})")
                if debug >= 3:
                    for j, c in enumerate(cands):
                        _dbg(f"  └ cand[{j}] name='{c['name']}', pos='{c['position']}', phone='{c['phone']}'")
        else:
            if debug >= 2:
                # 부서별 후보 요약 출력 (최대 10개)
                rows_for_dep = by_dep.get(dep_norm, [])
                _dbg(f"[debug] dep='{dep_norm}' has {len(rows_for_dep)} rows")
                for j, rr in enumerate(rows_for_dep[:10]):
                    _dbg(f"    cand[{j}] name='{rr.get('name')}', pos='{rr.get('position')}', phone='{rr.get('phone')}'")
                # 직위 버킷 요약
                pos_bucket = by_pos.get(pos_norm, [])
                _dbg(f"[debug] pos='{pos_norm}' bucket size={len(pos_bucket)}")
                for j, rr in enumerate(pos_bucket[:10]):
                    _dbg(f"    pos_cand[{j}] dep='{rr.get('department')}', name='{rr.get('name')}', pos='{rr.get('position')}'")
            # Fallback 1: 부서 동일 + 직위 유사/포함 (같은 부서만 허용)
            loose = [r for ((d,p), arr) in by_key.items() if d == dep_norm for r in arr if _pos_matches(pos_norm, r.get('position') or '')]
            if loose:
                rec = _pick_best(loose)
                name  = rec["name"] or "(공석)"
                pos   = rec["position"] or pos_norm
                phone = rec["phone"] or "-"
                task  = rec["task"] or "-"
                if debug >= 1:
                    _dbg(f"[match] FALLBACK unit='{unit}', dep='{dep_norm}', expected_pos='{pos_norm}' -> picked='{name}', pos='{pos}' (loose_cands={len(loose)})")
            else:
                # Fallback 2: 허용된 동치부서만 인정(예: '제1차관' ↔ '제1차관실', '대변인' ↔ '대변인실')
                allowed = set(_DEPT_EQUIV.get(dep_norm, [])) | {dep_norm}
                loose2 = [r for ((d,p), arr) in by_key.items() if d in allowed for r in arr if _pos_matches(pos_norm, r.get('position') or '')]
                if loose2:
                    rec = _pick_best(loose2)
                    name  = rec["name"] or "(공석)"
                    pos   = rec["position"] or pos_norm
                    phone = rec["phone"] or "-"
                    task  = rec["task"] or "-"
                    if debug >= 1:
                        _dbg(f"[match] FALLBACK2 unit='{unit}', dep~='{dep_norm}', expected_pos='{pos_norm}' -> picked='{name}', dep='{rec.get('department')}', pos='{pos}' (loose2_cands={len(loose2)})")
                else:
                    name, pos, phone, task = "(공석)", pos_norm, "-", "-"
                    misses.append((dep_norm, pos_norm))
                    if debug >= 1:
                        dep_keys = list({d for d, _ in by_key.keys()})
                        pos_keys = list({p for _, p in by_key.keys()})
                        near_dep = difflib.get_close_matches(dep_norm, dep_keys, n=3, cutoff=0.6)
                        near_pos = difflib.get_close_matches(pos_norm, pos_keys, n=3, cutoff=0.6)
                        _dbg(f"[match] MISS unit='{unit}', key=('{dep_norm}','{pos_norm}') near_dep={near_dep} near_pos={near_pos}")

        row = {
            "unit": unit, "indent": indent,
            "name": name, "position": pos, "phone": phone, "task": task,
        }
        if not key or key in (" ".join([unit, name, pos, phone, task]).lower()):
            items.append(row)

    return templates.TemplateResponse(
        "gov/motie/heads_test.html",
        {
            "request": request,
            "ministry": "산업통상자원부(MOTIE) - 테스트",
            "q": q,
            "items": items,
            "debug": debug,
            "dbg_lines": dbg_lines,
            "misses": misses,
        },
    )


@router.get("/gov/motie/heads_test/debug", response_class=JSONResponse)
async def heads_test_debug(
    dep: str | None = Query(default=None),
    pos: str | None = Query(default=None),
    limit: int = Query(default=100, ge=1, le=2000),
):
    """정규화 결과를 JSON으로 확인하는 디버그 엔드포인트.
    dep/pos 필터를 주면 해당 문자열이 포함된 원본/정규화 값을 함께 반환한다.
    """
    try:
        rows = (
            sb.table("motie_org_cur")
              .select("department,position,name,phone,task")
              .limit(50000)
              .execute()
              .data
        ) or []
    except Exception:
        rows = []

    out = []
    for r in rows:
        dep_raw = r.get("department") or ""
        pos_raw = r.get("position") or ""
        dep_norm = _canon_department(dep_raw)
        pos_norm = _normalize_position(pos_raw)
        if dep and (dep not in dep_raw and dep not in dep_norm):
            continue
        if pos and (pos not in pos_raw and pos not in pos_norm):
            continue
        out.append({
            "name": r.get("name"),
            "dep_raw": dep_raw,
            "dep_norm": dep_norm,
            "pos_raw": pos_raw,
            "pos_norm": pos_norm,
            "phone": r.get("phone"),
        })
        if len(out) >= limit:
            break

    return {"count": len(out), "rows": out}


# ---------- 자동 타깃 동기화(미리보기/적용) ----------
def _guess_expected_pos(unit: str) -> str:
    u = _norm(unit)
    # 특수 케이스 우선
    if u in ("제1차관",):
        return "1차관"
    if u in ("제2차관",):
        return "2차관"
    if u.endswith("본부장실"):
        return "본부장" if "통상" not in u else "통상교섭본부장"
    if u.endswith("위원회"):
        return "위원장"
    if u.endswith("사무국"):
        return "사무국장"
    if u.endswith("관리원"):
        return "원장"
    if u == "대변인":
        return "대변인"
    if u.endswith("정책관") or u.endswith("기획관"):
        return "국장"
    if u.endswith("관리관"):
        return "관리관"
    # 접미 일반 규칙
    if u.endswith("실"):
        return "실장"
    if u.endswith("국"):
        return "국장"
    if u.endswith("관"):
        return "국장"
    if u.endswith("과"):
        return "과장"
    if u.endswith("팀"):
        return "팀장"
    return "-"

def _guess_indent(unit: str) -> int:
    u = _norm(unit)
    if u in ("장관실", "제1차관", "제2차관", "통상교섭본부장실"):
        return 0
    if u.endswith(("실",)) or u in ("대변인",):
        return 1
    if u.endswith(("정책관", "기획관", "국", "위원회")):
        return 2
    if u.endswith(("과", "팀", "사무국")):
        return 3
    return 1

@router.get("/gov/motie/heads_test/target_sync", response_class=JSONResponse)
async def heads_test_target_sync(
    apply: int = Query(default=0, ge=0, le=1),
    update_existing: int = Query(default=0, ge=0, le=1),
    limit: int = Query(default=2000, ge=1, le=5000),
):
    """motie_org_cur의 부서 목록을 기준으로 motie_heads_target을 자동 보강.
    - apply=0: 미리보기만(JSON)
    - apply=1: 누락 unit만 삽입(ord는 max+1부터), update_existing=1이면 기대직위/indent 업데이트
    """
    # 현재 부서 목록
    try:
        deps = (
            sb.table("motie_org_cur")
              .select("department")
              .limit(50000)
              .execute()
              .data
        ) or []
    except Exception:
        deps = []
    dep_names = sorted({(r.get("department") or "").strip() for r in deps if (r.get("department") or "").strip()})

    # 타깃 현황
    try:
        tgt = (
            sb.table("motie_heads_target")
              .select("ord,unit,expected_pos,indent")
              .order("ord", desc=False)
              .limit(5000)
              .execute()
              .data
        ) or []
    except Exception:
        tgt = []
    by_unit = { (r.get("unit") or "").strip(): r for r in tgt }
    max_ord = max([r.get("ord") or 0 for r in tgt] + [0])

    inserts = []
    updates = []
    for d in dep_names:
        if d not in by_unit:
            max_ord += 1
            inserts.append({
                "ord": max_ord,
                "unit": d,
                "expected_pos": _guess_expected_pos(d),
                "indent": _guess_indent(d),
            })
        elif update_existing:
            cur = by_unit[d]
            new_ep = _guess_expected_pos(d)
            new_ind = _guess_indent(d)
            if (cur.get("expected_pos") != new_ep) or (cur.get("indent") != new_ind):
                updates.append({
                    "ord": cur.get("ord"),
                    "unit": d,
                    "expected_pos": new_ep,
                    "indent": new_ind,
                })

    preview = {
        "total_deps": len(dep_names),
        "existing_targets": len(tgt),
        "missing_units": len(inserts),
        "will_update": len(updates) if update_existing else 0,
        "sample_inserts": inserts[:20],
        "sample_updates": updates[:20],
    }

    if not apply:
        return preview

    # 적용: upsert(INSERT 새로, UPDATE 선택 적용)
    try:
        for i in range(0, len(inserts), 500):
            part = inserts[i:i+500]
            if part:
                sb.table("motie_heads_target").upsert(part).execute()
        if update_existing and updates:
            for u in updates:
                sb.table("motie_heads_target").update({
                    "unit": u["unit"],
                    "expected_pos": u["expected_pos"],
                    "indent": u["indent"],
                }).eq("ord", u["ord"]).execute()
        preview["applied_inserts"] = len(inserts)
        preview["applied_updates"] = len(updates) if update_existing else 0
        return preview
    except Exception as e:
        return JSONResponse({"ok": False, "error": str(e), **preview}, status_code=500)
