"""대운(大運) 계산 — saju-web/lib/daeun-calculator.ts 와 1:1 매핑.

핵심 알고리즘:
1. 방향 결정 (양남음녀=순행, 음남양녀=역행)
   - 양/음은 년간 인덱스 % 2 == 0 여부 (TS: yearStemIndex = (year - 1900 + 6) % 10)
2. 시작 나이 계산 (절기 기준)
   - 순행: 다음 절기까지 일수 / 3 (floor)
   - 역행: 이전 절기부터 일수 / 3 (TS는 Math.ceil 사용)
   - clamp [1, 10]
3. 8개 대운 — 시작 나이부터 10년 단위, 월주에서 ±1씩 60갑자 진행

⚠️ TS `getCurrentSolarTerm`의 미묘한 동작을 재현:
   - TS는 saju-web 인덱스 i=23(대한)부터 i=0(입춘) 순서로 내려가며,
     `getSolarTermDate(year, i)` 의 날짜와 비교하여 첫 번째 매치를 반환.
   - 대한(i=23)은 1월 20일경이라 같은 해의 대부분의 날짜가 매치 → currentTerm=23이
     대다수.
   - 매치 못 찾으면 폴백 23 반환 → 입춘 이전 (1월 초·중순) 또한 23으로 처리됨.
   - 결과적으로 forward 의 days 가 매우 크게 잡혀 startAge=10으로 cap 되는 게 보통.
   - 본 구현은 sxtwl 정확 날짜로 동일한 비교 로직을 수행 → TS 결과 정확 재현.

⚠️ saju-web 인덱스 vs sxtwl JieQi 인덱스:
   - saju-web 0=입춘 → sxtwl 3=立春
   - 변환: sxtwl_idx = (web_idx + 3) % 24
"""
from __future__ import annotations

from datetime import date as _date
from functools import lru_cache
from typing import Optional

from .constants import (
    HEAVENLY_STEMS,
    HEAVENLY_STEMS_KR,
    EARTHLY_BRANCHES,
    EARTHLY_BRANCHES_KR,
)
from .solar_terms import _all_jieqi_dates


# saju-web 절기 인덱스 → sxtwl JieQi 인덱스
# 0=입춘, 1=우수, 2=경칩, ..., 22=소한, 23=대한 (saju-web)
# 3=立春, 4=雨水, 5=驚蟄, ..., 1=小寒, 2=大寒 (sxtwl)
def _web_to_sxtwl_idx(web_idx: int) -> int:
    return (web_idx + 3) % 24


@lru_cache(maxsize=512)
def _saju_web_solar_term_date(year: int, web_idx: int) -> _date:
    """saju-web 인덱스 기준 `year` 의 절기 날짜 반환 (정확 날짜).

    TS `getSolarTermDate(year, termIndex)` 의 1:1 대응.
    sxtwl 으로 정확한 천문학적 날짜를 사용 (TS의 search-range 근사보다 정확).

    `year` 는 양력 그레고리안 연도. 예를 들어 大寒(saju-web 23)의 1985년 날짜는
    1985-01-20이며, sxtwl 에서는 getJieQiByYear(1984) 의 결과에 idx=2로 포함됨.
    """
    sxtwl_idx = _web_to_sxtwl_idx(web_idx)
    # 해당 sxtwl_idx 의 절기 중 그레고리안 연도가 일치하는 날짜 검색.
    # sxtwl 의 getJieQiByYear(y) 는 立春 y년부터 그 다음 立春 직전(=大寒 y+1년)까지 포함.
    for src_year in (year - 1, year, year + 1):
        for d, idx in _all_jieqi_dates(src_year):
            if idx == sxtwl_idx and d.year == year:
                return d
    # 폴백 — 정상 동작 시 도달 불가
    base_month = [
        2, 2, 3, 3, 4, 4,
        5, 5, 6, 6, 7, 7,
        8, 8, 9, 9, 10, 10,
        11, 11, 12, 12, 1, 1,
    ]
    base_day = [
        4, 19, 5, 20, 4, 20,
        5, 21, 6, 21, 7, 23,
        7, 23, 8, 23, 8, 23,
        7, 22, 7, 22, 5, 20,
    ]
    return _date(year, base_month[web_idx], base_day[web_idx])


def _get_current_solar_term_web(year: int, month: int, day: int) -> int:
    """TS getCurrentSolarTerm 의 1:1 재현.

    saju-web 절기 인덱스 (0=입춘 ... 23=대한) 중 birth date 이상인 가장 큰 i 반환.
    탐색 순서: i=23 → i=0. i=23 (대한, 1월 20일경) 매치 시 즉시 반환되므로
    대부분의 날짜가 23을 반환. 매치 못 찾으면 폴백 23.

    TS의 특수 처리: i >= 22 (소한, 대한) 이고 birthMonth >= 2 면 termYear=year,
    그 외 i >= 22 는 termYear = year - 1. 이는 1월 출생자가 전년도 대한·소한 기준이 됨을 의미.
    본 함수는 i >= 22 일 때 TS와 동일한 termYear 조정 후 비교.
    """
    target = _date(year, month, day)

    for i in range(23, -1, -1):
        # 기본은 birthYear 와 동일 연도의 절기 날짜
        term_date = _saju_web_solar_term_date(year, i)
        term_year = year
        term_month = term_date.month
        term_day = term_date.day

        # 대한(23) / 소한(22) 특수 처리 — birthMonth < 2 (1월)면 전년도 절기
        if i >= 22 and month < 2:
            term_year = year - 1
            shifted = _saju_web_solar_term_date(term_year, i)
            term_month = shifted.month
            term_day = shifted.day

        try:
            term = _date(term_year, term_month, term_day)
        except ValueError:
            continue

        if target >= term:
            return i

    # 입춘 이전 (1월 초) — TS는 23 반환
    return 23


def _calculate_daeun_start_age(
    year: int, month: int, day: int, gender: str, is_yang_year: bool
) -> int:
    """대운 시작 나이 정밀 계산 (절기 기준).

    TS calculateDaeunStartAge 와 1:1 매핑.
    - 양남(陽男) / 음녀(陰女) → 순행: 다음 절기까지 일수
    - 음남(陰男) / 양녀(陽女) → 역행: 이전 절기부터 일수
    - startAge = floor(days / 3), clamp [1, 10]
    """
    target = _date(year, month, day)
    current_term = _get_current_solar_term_web(year, month, day)

    if (gender == "male" and is_yang_year) or (gender == "female" and not is_yang_year):
        # 순행 — 다음 절기까지
        next_term_index = (current_term + 1) % 24
        next_year = year + 1 if current_term == 23 else year
        next_date = _saju_web_solar_term_date(next_year, next_term_index)
        diff = next_date - target
        # TS: Math.ceil(diffTime / DAY) — date 차분은 정수이므로 그대로
        days = diff.days
    else:
        # 역행 — 이전 절기부터
        term_date = _saju_web_solar_term_date(year, current_term)
        term_year = year
        term_month = term_date.month
        # TS: 대한, 소한 처리 — i >= 22 일 때
        if current_term >= 22 and month >= 2:
            term_year = year
        elif current_term >= 22:
            term_year = year - 1
            term_date = _saju_web_solar_term_date(term_year, current_term)
        try:
            term_dt = _date(term_year, term_month, term_date.day)
        except ValueError:
            term_dt = term_date
        diff = target - term_dt
        days = diff.days

    start_age = days // 3  # Math.floor

    # 최소 1세, 최대 10세
    return max(1, min(10, start_age))


def calculate_daeun(
    year: int,
    month: int,
    day: int,
    gender: str,
    month_stem: str,
    month_branch: str,
) -> list[dict]:
    """8개 대운 계산.

    Args:
        year, month, day: 양력 생년월일
        gender: "male" | "female"
        month_stem, month_branch: 월주 천간/지지 (사주 계산 후 전달)

    Returns:
        8개 DaeunPillar dict (snake_case 키):
        - age: 시작 나이
        - start_year: 시작 년도
        - end_year: 끝 년도 (start_year + 9)
        - stem, branch: 천간/지지
        - stem_kr, branch_kr: 한글
    """
    try:
        month_stem_idx = HEAVENLY_STEMS.index(month_stem)
        month_branch_idx = EARTHLY_BRANCHES.index(month_branch)
    except ValueError:
        return []

    # 양남음녀(陽男陰女)=순행, 음남양녀(陰男陽女)=역행
    # TS: yearStemIndex = (year - 1900 + 6) % 10 → isYangYear = (idx % 2 == 0)
    year_stem_idx = (year - 1900 + 6) % 10
    if year_stem_idx < 0:
        year_stem_idx += 10
    is_yang_year = (year_stem_idx % 2) == 0

    if gender == "male":
        is_forward = is_yang_year
    else:
        is_forward = not is_yang_year

    start_age = _calculate_daeun_start_age(year, month, day, gender, is_yang_year)

    daeun_list: list[dict] = []
    for i in range(8):
        age = start_age + (i * 10)
        start_year = year + age
        end_year = start_year + 9

        if is_forward:
            stem_idx = (month_stem_idx + i + 1) % 10
            branch_idx = (month_branch_idx + i + 1) % 12
        else:
            stem_idx = (month_stem_idx - i - 1 + 100) % 10
            branch_idx = (month_branch_idx - i - 1 + 120) % 12

        daeun_list.append({
            "age": age,
            "start_year": start_year,
            "end_year": end_year,
            "stem": HEAVENLY_STEMS[stem_idx],
            "branch": EARTHLY_BRANCHES[branch_idx],
            "stem_kr": HEAVENLY_STEMS_KR[stem_idx],
            "branch_kr": EARTHLY_BRANCHES_KR[branch_idx],
        })

    return daeun_list


def get_current_daeun(daeun_list: list[dict], current_year: int) -> Optional[dict]:
    """현재 연도에 해당하는 대운 반환 (없으면 None)."""
    for d in daeun_list:
        if d["start_year"] <= current_year <= d["end_year"]:
            return d
    return None


def get_daeun_description(daeun: dict, day_stem: str) -> str:
    """대운 해석 — TS getDaeunDescription 와 1:1 매핑.

    daeun dict 키는 snake_case (age, stem, branch, stem_kr, branch_kr).
    """
    age = daeun["age"]
    stem = daeun["stem"]
    branch = daeun["branch"]
    stem_kr = daeun["stem_kr"]
    branch_kr = daeun["branch_kr"]
    ganzi = f"{stem}{branch}"

    description = (
        f"{age}세부터 {age + 9}세까지의 10년은 {stem_kr}{branch_kr}({ganzi}) 대운입니다. "
    )

    # 대운 천간 인덱스 (음양 판정용)
    try:
        stem_idx = HEAVENLY_STEMS.index(stem)
    except ValueError:
        stem_idx = 0

    if age < 20:
        description += "청소년기로 학업과 기초를 다지는 시기입니다. "
    elif age < 40:
        description += "성장과 발전의 시기로 사회활동이 왕성한 때입니다. "
    elif age < 60:
        description += "안정과 성숙의 시기로 경험이 쌓이는 때입니다. "
    else:
        description += "원숙한 시기로 인생의 지혜를 나누는 때입니다. "

    if stem_idx % 2 == 0:
        description += "적극적이고 외향적인 활동이 유리합니다."
    else:
        description += "차분하고 내실을 다지는 것이 좋습니다."

    return description
