"""사주 종합 분석 — 오행/신강신약/용신/세운/perform_full_analysis.

saju-web/lib/ai-interpretation.ts 와 1:1 매핑.

핵심 함수:
- calculate_detailed_element_balance: 가중치 오행 점수 (천간 1.0, 본기 1.0, 중기 0.5, 여기 0.3)
- calculate_element_score: 비율 (%)
- analyze_day_master_strength: 신강/신약/중화 + score(정수) + reasons(텍스트 리스트)
- estimate_yongshin: 용신/희신/기신 + explanation
- calculate_seun: 올해(주어진 year) 세운 + 일주/년월시 충/합 매핑
- perform_full_analysis: 위 모두 + branch_interactions + shinsal + gongmang + hidden_stems

출력 키는 snake_case. pillar 값은 한글(`세운`/`년주`/`월주`/`일주`/`시주`).
"""
from __future__ import annotations

import math
from typing import Optional

from .constants import (
    FIVE_ELEMENTS,
    FIVE_ELEMENTS_KR,
    HIDDEN_STEMS,
)
from .shinsal import (
    get_hidden_stems,
    get_all_hidden_stems,
    analyze_branch_interactions,
    calculate_shinsal,
    calculate_gongmang,
)
from .core import get_year_ganzi


# ─── 상생/상극 맵 (TS PRODUCE_MAP / OVERCOME_MAP 동일) ─────────────
_PRODUCE_MAP: dict[str, str] = {
    "木": "火", "火": "土", "土": "金", "金": "水", "水": "木",
}

_OVERCOME_MAP: dict[str, str] = {
    "木": "土", "火": "金", "土": "水", "金": "木", "水": "火",
}


def _get_producing_element(elem: str) -> str:
    """`elem`을 생하는 오행 (= 인성). TS getProducingElement 동일."""
    for k, v in _PRODUCE_MAP.items():
        if v == elem:
            return k
    return ""


def _get_overcoming_me(elem: str) -> str:
    """`elem`을 극하는 오행 (= 관살). TS getOvercomingMe 동일."""
    for k, v in _OVERCOME_MAP.items():
        if v == elem:
            return k
    return ""


def _js_round(x: float) -> int:
    """JavaScript Math.round 호환 — half-away-from-zero (양수에서는 half-up).

    Python의 round()는 banker's rounding(half-to-even)을 쓰므로 호환을 위해 우회.
    오행 점수는 항상 ≥ 0 이므로 floor(x + 0.5) 로 충분.
    """
    if x >= 0:
        return math.floor(x + 0.5)
    return -math.floor(-x + 0.5)


# ─── 오행 밸런스 ──────────────────────────────────────────────────
def calculate_detailed_element_balance(saju: dict) -> dict:
    """가중치 적용 오행 점수.

    - 천간 stem: 1.0 per pillar
    - 지지 hidden_stems: 본기 1.0, 중기 0.5, 여기 0.3
    - 결과는 소수점 둘째 자리로 반올림 (TS Math.round(x*100)/100).
    """
    balance: dict[str, float] = {"木": 0.0, "火": 0.0, "土": 0.0, "金": 0.0, "水": 0.0}

    # 천간
    stems = [saju["year"]["stem"], saju["month"]["stem"], saju["day"]["stem"]]
    if saju.get("hour") is not None:
        stems.append(saju["hour"]["stem"])

    for stem in stems:
        elem = FIVE_ELEMENTS.get(stem)
        if elem in balance:
            balance[elem] += 1.0

    # 지지 지장간
    branches = [saju["year"]["branch"], saju["month"]["branch"], saju["day"]["branch"]]
    if saju.get("hour") is not None:
        branches.append(saju["hour"]["branch"])

    weights = [1.0, 0.5, 0.3]
    for branch in branches:
        hidden = get_hidden_stems(branch)
        for i, hs in enumerate(hidden):
            elem = FIVE_ELEMENTS.get(hs)
            if elem in balance:
                w = weights[i] if i < len(weights) else 0.3
                balance[elem] += w

    # 소수점 둘째 자리 반올림 (TS Math.round(x*100)/100)
    for k in list(balance.keys()):
        balance[k] = _js_round(balance[k] * 100) / 100

    return balance


def calculate_element_score(saju: dict) -> dict:
    """오행 비율 (%). TS: Math.round((value / total) * 100)."""
    balance = calculate_detailed_element_balance(saju)
    total = sum(balance.values())

    scores: dict[str, int] = {}
    for elem, value in balance.items():
        if total > 0:
            scores[elem] = _js_round((value / total) * 100)
        else:
            scores[elem] = 0
    return scores


# ─── 신강/신약 분석 ───────────────────────────────────────────────
def analyze_day_master_strength(saju: dict) -> dict:
    """신강/신약/중화 판단 — TS analyzeDayMasterStrength 와 1:1 동일.

    score 가산/감산 규칙:
      1. 월령 득령
         - 월지 본기 == 일간 오행: +3 ("월령 득령: 월지 X(Y)의 본기가 일간과 같은 ...")
         - 월지 본기 == 인성: +2 ("월령 득령: 월지 X(Y)의 본기가 일간을 생하는 ...")
         - 기타: -2 ("월령 실령: 월지 X(Y)의 본기가 일간을 돕지 않음")
      2. 통근 (지지 지장간 중 일간/인성 오행 포함된 지지 개수)
         - >=3: +2 / >=2: +1 / 그 외: -1
      3. 투출 (년주/월주/시주 천간 중 일간/인성 오행 개수, 일주 제외)
         - >=2: +2 / ==1: +1 / 0: -1
      4. 오행 비율 (조력 > 약화 * 1.3 또는 반대)
         - +1 / -1

    결과: score >= 3 → 신강, score <= -2 → 신약, else 중화.
    """
    day_stem = saju["day_stem"]
    day_element = FIVE_ELEMENTS[day_stem]
    producing_element = _get_producing_element(day_element)
    reasons: list[str] = []
    score = 0

    # 1. 월령 득령
    month_branch = saju["month"]["branch"]
    month_branch_kr = saju["month"]["branch_kr"]
    month_hidden = get_hidden_stems(month_branch)
    month_main_element = FIVE_ELEMENTS[month_hidden[0]]

    if month_main_element == day_element:
        score += 3
        reasons.append(
            f"월령 득령: 월지 {month_branch_kr}({month_branch})의 본기가 일간과 같은 "
            f"{FIVE_ELEMENTS_KR[day_element]}으로 강한 힘을 받음"
        )
    elif month_main_element == producing_element:
        score += 2
        reasons.append(
            f"월령 득령: 월지 {month_branch_kr}({month_branch})의 본기가 일간을 생하는 "
            f"{FIVE_ELEMENTS_KR[producing_element]}으로 힘을 받음"
        )
    else:
        score -= 2
        reasons.append(
            f"월령 실령: 월지 {month_branch_kr}({month_branch})의 본기가 일간을 돕지 않음"
        )

    # 2. 통근 (4주 지지 모두 검사)
    all_branches = [saju["year"]["branch"], saju["month"]["branch"], saju["day"]["branch"]]
    if saju.get("hour") is not None:
        all_branches.append(saju["hour"]["branch"])

    root_count = 0
    for branch in all_branches:
        hidden = get_hidden_stems(branch)
        for h in hidden:
            h_elem = FIVE_ELEMENTS.get(h)
            if h_elem == day_element or h_elem == producing_element:
                root_count += 1
                break

    if root_count >= 3:
        score += 2
        reasons.append(f"통근 강함: {root_count}개 지지에서 일간의 뿌리를 찾음")
    elif root_count >= 2:
        score += 1
        reasons.append(f"통근 보통: {root_count}개 지지에서 일간의 뿌리를 찾음")
    else:
        score -= 1
        reasons.append(f"통근 약함: {root_count}개 지지에서만 일간의 뿌리를 찾음")

    # 3. 투출 (년주/월주 천간 + 시주 천간, 일주 제외)
    all_stems = [saju["year"]["stem"], saju["month"]["stem"]]
    if saju.get("hour") is not None:
        all_stems.append(saju["hour"]["stem"])

    helping_stem_count = 0
    for stem in all_stems:
        stem_elem = FIVE_ELEMENTS.get(stem)
        if stem_elem == day_element or stem_elem == producing_element:
            helping_stem_count += 1

    if helping_stem_count >= 2:
        score += 2
        reasons.append(
            f"투출 강함: 천간에 비겁/인성이 {helping_stem_count}개 있어 일간을 도움"
        )
    elif helping_stem_count == 1:
        score += 1
        reasons.append("투출 보통: 천간에 비겁/인성이 1개 있음")
    else:
        score -= 1
        reasons.append("투출 없음: 천간에 일간을 돕는 비겁/인성이 없음")

    # 4. 오행 비율 (조력 vs 약화)
    balance = calculate_detailed_element_balance(saju)
    helping_score = balance.get(day_element, 0.0) + balance.get(producing_element, 0.0)
    draining_score = sum(
        v for k, v in balance.items()
        if k != day_element and k != producing_element
    )

    if helping_score > draining_score * 1.3:
        score += 1
        reasons.append(
            f"오행 비율: 비겁+인성({helping_score:.1f}) > 식상+재관({draining_score:.1f}) "
            f"→ 일간 세력 우세"
        )
    elif draining_score > helping_score * 1.3:
        score -= 1
        reasons.append(
            f"오행 비율: 식상+재관({draining_score:.1f}) > 비겁+인성({helping_score:.1f}) "
            f"→ 일간 세력 열세"
        )

    if score >= 3:
        result = "신강"
    elif score <= -2:
        result = "신약"
    else:
        result = "중화"

    return {"result": result, "score": score, "reasons": reasons}


# ─── 용신 추정 ────────────────────────────────────────────────────
def estimate_yongshin(saju: dict, strength: dict) -> dict:
    """용신/희신/기신 추정 — TS estimateYongShin 와 1:1 동일.

    신강:
      용신/희신 = 식상/재성/관살 중 balance 가장 낮은 2개 (점수 오름차순)
      기신 = 일간 오행 (비겁)
    신약:
      용신/희신 = 인성/비겁 중 balance 낮은 2개
      기신 = 관살
    중화:
      용신/희신 = 모든 오행 중 balance 낮은 2개
      기신 = balance 가장 높은 오행
    """
    day_element = FIVE_ELEMENTS[saju["day_stem"]]
    balance = calculate_detailed_element_balance(saju)

    producing_me = _get_producing_element(day_element)  # 인성
    my_product = _PRODUCE_MAP[day_element]               # 식상
    my_overcome = _OVERCOME_MAP[day_element]             # 재성
    overcome_me = _get_overcoming_me(day_element)        # 관살

    def kr(e: str) -> str:
        return FIVE_ELEMENTS_KR.get(e, e)

    result = strength["result"]

    if result == "신강":
        candidates = [
            {"elem": my_product, "score": balance.get(my_product, 0.0), "name": "식상"},
            {"elem": my_overcome, "score": balance.get(my_overcome, 0.0), "name": "재성"},
            {"elem": overcome_me, "score": balance.get(overcome_me, 0.0), "name": "관살"},
        ]
        candidates.sort(key=lambda x: x["score"])
        yong = candidates[0]
        hee = candidates[1]

        return {
            "yong_shin": yong["elem"], "yong_shin_kr": kr(yong["elem"]),
            "hee_shin": hee["elem"], "hee_shin_kr": kr(hee["elem"]),
            "gi_shin": day_element, "gi_shin_kr": kr(day_element),
            "explanation": (
                f"신강한 사주로 일간의 힘이 넘치므로 {yong['name']}({kr(yong['elem'])}) "
                f"기운을 용신으로 삼아 기운을 설기(泄氣)하거나 제어해야 합니다. "
                f"{hee['name']}({kr(hee['elem'])})이 희신으로 보조합니다."
            ),
        }
    elif result == "신약":
        candidates = [
            {"elem": producing_me, "score": balance.get(producing_me, 0.0), "name": "인성"},
            {"elem": day_element, "score": balance.get(day_element, 0.0), "name": "비겁"},
        ]
        candidates.sort(key=lambda x: x["score"])
        yong = candidates[0]
        hee = candidates[1]

        return {
            "yong_shin": yong["elem"], "yong_shin_kr": kr(yong["elem"]),
            "hee_shin": hee["elem"], "hee_shin_kr": kr(hee["elem"]),
            "gi_shin": overcome_me, "gi_shin_kr": kr(overcome_me),
            "explanation": (
                f"신약한 사주로 일간의 힘이 부족하므로 {yong['name']}({kr(yong['elem'])}) "
                f"기운을 용신으로 삼아 일간을 돕고 힘을 보충해야 합니다. "
                f"{hee['name']}({kr(hee['elem'])})이 희신으로 보조합니다."
            ),
        }
    else:
        # 중화 — Object.entries 순서 (TS): 木, 火, 土, 金, 水
        entries = [(e, balance[e]) for e in ["木", "火", "土", "金", "水"]]
        # Array.prototype.sort 는 안정 정렬(Node v12+). Python sort 도 안정 정렬이므로 동일.
        entries.sort(key=lambda x: x[1])
        yong = entries[0]
        hee = entries[1]
        gi = entries[-1]

        return {
            "yong_shin": yong[0], "yong_shin_kr": kr(yong[0]),
            "hee_shin": hee[0], "hee_shin_kr": kr(hee[0]),
            "gi_shin": gi[0], "gi_shin_kr": kr(gi[0]),
            "explanation": (
                f"중화에 가까운 사주로 오행이 비교적 균형을 이루고 있습니다. "
                f"가장 부족한 {kr(yong[0])}({yong[0]}) 기운을 보충하면 더욱 좋아집니다."
            ),
        }


# ─── 세운 ─────────────────────────────────────────────────────────
_SEUN_CHUNG_PAIRS: list[tuple[str, str]] = [
    ("子", "午"), ("丑", "未"), ("寅", "申"),
    ("卯", "酉"), ("辰", "戌"), ("巳", "亥"),
]

_SEUN_YUKAP_PAIRS: list[tuple[str, str, str]] = [
    ("子", "丑", "土"), ("寅", "亥", "木"), ("卯", "戌", "火"),
    ("辰", "酉", "金"), ("巳", "申", "水"), ("午", "未", "火"),
]


def calculate_seun(year: int, saju: dict) -> dict:
    """올해(주어진 year) 세운 정보 + 일주/년월시 지지와의 충/합 매핑.

    TS calculateSeun 와 1:1 동일. interactions 는 충(沖) 먼저 -> 합(合) 순서.
    """
    ganzi = get_year_ganzi(year)
    element = FIVE_ELEMENTS[ganzi["stem"]]
    element_kr = FIVE_ELEMENTS_KR[element]

    seun_branch = ganzi["branch"]
    seun_branch_kr = ganzi["branch_kr"]

    pillar_branches: list[dict] = [
        {"branch": saju["year"]["branch"], "branch_kr": saju["year"]["branch_kr"], "pillar": "년주"},
        {"branch": saju["month"]["branch"], "branch_kr": saju["month"]["branch_kr"], "pillar": "월주"},
        {"branch": saju["day"]["branch"], "branch_kr": saju["day"]["branch_kr"], "pillar": "일주"},
    ]
    if saju.get("hour") is not None:
        pillar_branches.append({
            "branch": saju["hour"]["branch"],
            "branch_kr": saju["hour"]["branch_kr"],
            "pillar": "시주",
        })

    interactions: list[dict] = []

    # 충 (沖)
    for a, b in _SEUN_CHUNG_PAIRS:
        for pb in pillar_branches:
            if (seun_branch == a and pb["branch"] == b) or (seun_branch == b and pb["branch"] == a):
                interactions.append({
                    "type": "충(沖)",
                    "branches": [seun_branch, pb["branch"]],
                    "branches_kr": [seun_branch_kr, pb["branch_kr"]],
                    "pillars": ["세운", pb["pillar"]],
                    "description": (
                        f"세운 {seun_branch_kr}와 {pb['pillar']} {pb['branch_kr']}가 충 "
                        f"→ 해당 영역에 변동과 변화가 예상됨."
                    ),
                })

    # 합 (六合)
    for a, b, elem in _SEUN_YUKAP_PAIRS:
        for pb in pillar_branches:
            if (seun_branch == a and pb["branch"] == b) or (seun_branch == b and pb["branch"] == a):
                interactions.append({
                    "type": "합(合)",
                    "branches": [seun_branch, pb["branch"]],
                    "branches_kr": [seun_branch_kr, pb["branch_kr"]],
                    "pillars": ["세운", pb["pillar"]],
                    "description": (
                        f"세운 {seun_branch_kr}와 {pb['pillar']} {pb['branch_kr']}가 합 "
                        f"→ 해당 영역에 조화와 좋은 인연이 기대됨."
                    ),
                    "result_element": elem,
                })

    return {
        "stem": ganzi["stem"],
        "branch": ganzi["branch"],
        "stem_kr": ganzi["stem_kr"],
        "branch_kr": ganzi["branch_kr"],
        "element": element,
        "element_kr": element_kr,
        "year": year,
        "interactions": interactions,
    }


# ─── 통합 분석 ────────────────────────────────────────────────────
def perform_full_analysis(saju: dict, current_year: int) -> dict:
    """모든 분석 통합 — TS performFullAnalysis 와 1:1 동일.

    반환 키는 snake_case.
    """
    element_balance = calculate_detailed_element_balance(saju)
    element_scores = calculate_element_score(saju)
    day_master_strength = analyze_day_master_strength(saju)
    yong_shin = estimate_yongshin(saju, day_master_strength)
    branch_interactions = analyze_branch_interactions(saju)
    shinsal = calculate_shinsal(saju)
    gongmang = calculate_gongmang(saju["day_stem"], saju["day"]["branch"])
    seun = calculate_seun(current_year, saju)
    hidden_stems = get_all_hidden_stems(saju)

    return {
        "element_balance": element_balance,
        "element_scores": element_scores,
        "day_master_strength": day_master_strength,
        "yong_shin": yong_shin,
        "branch_interactions": branch_interactions,
        "shinsal": shinsal,
        "gongmang": gongmang,
        "seun": seun,
        "hidden_stems": hidden_stems,
    }
