from __future__ import annotations

from datetime import datetime, timedelta, timezone
from functools import lru_cache
from typing import Any

from backend.features.heaven.engine import (
    BRANCH_ELEMENT,
    ELEMENT_CONTROLS,
    ELEMENT_GENERATES,
    LINE_POSITIONS,
    Solar,
)


SHANGHAI = timezone(timedelta(hours=8), "Asia/Shanghai")

TRIGRAM_BITS = {
    "乾": (1, 1, 1),
    "兑": (1, 1, 0),
    "离": (1, 0, 1),
    "震": (1, 0, 0),
    "巽": (0, 1, 1),
    "坎": (0, 1, 0),
    "艮": (0, 0, 1),
    "坤": (0, 0, 0),
}
BITS_TRIGRAM = {bits: name for name, bits in TRIGRAM_BITS.items()}
PALACE_ELEMENT = {
    "乾": "金",
    "兑": "金",
    "离": "火",
    "震": "木",
    "巽": "木",
    "坎": "水",
    "艮": "土",
    "坤": "土",
}

# 京房纳甲通行表。每组均按初爻至三爻、四爻至上爻排列。
NAJIA = {
    "乾": {
        "inner": (("甲", "子"), ("甲", "寅"), ("甲", "辰")),
        "outer": (("壬", "午"), ("壬", "申"), ("壬", "戌")),
    },
    "坤": {
        "inner": (("乙", "未"), ("乙", "巳"), ("乙", "卯")),
        "outer": (("癸", "丑"), ("癸", "亥"), ("癸", "酉")),
    },
    "震": {
        "inner": (("庚", "子"), ("庚", "寅"), ("庚", "辰")),
        "outer": (("庚", "午"), ("庚", "申"), ("庚", "戌")),
    },
    "巽": {
        "inner": (("辛", "丑"), ("辛", "亥"), ("辛", "酉")),
        "outer": (("辛", "未"), ("辛", "巳"), ("辛", "卯")),
    },
    "坎": {
        "inner": (("戊", "寅"), ("戊", "辰"), ("戊", "午")),
        "outer": (("戊", "申"), ("戊", "戌"), ("戊", "子")),
    },
    "离": {
        "inner": (("己", "卯"), ("己", "丑"), ("己", "亥")),
        "outer": (("己", "酉"), ("己", "未"), ("己", "巳")),
    },
    "艮": {
        "inner": (("丙", "辰"), ("丙", "午"), ("丙", "申")),
        "outer": (("丙", "戌"), ("丙", "子"), ("丙", "寅")),
    },
    "兑": {
        "inner": (("丁", "巳"), ("丁", "卯"), ("丁", "丑")),
        "outer": (("丁", "亥"), ("丁", "酉"), ("丁", "未")),
    },
}

PALACE_STAGES = (
    ("本宫", (), 6),
    ("一世", (0,), 1),
    ("二世", (0, 1), 2),
    ("三世", (0, 1, 2), 3),
    ("四世", (0, 1, 2, 3), 4),
    ("五世", (0, 1, 2, 3, 4), 5),
    ("游魂", (0, 1, 2, 4), 4),
    ("归魂", (4,), 3),
)

SIX_SPIRITS = ("青龙", "朱雀", "勾陈", "螣蛇", "白虎", "玄武")
SPIRIT_START = {
    "甲": 0,
    "乙": 0,
    "丙": 1,
    "丁": 1,
    "戊": 2,
    "己": 3,
    "庚": 4,
    "辛": 4,
    "壬": 5,
    "癸": 5,
}

BRANCH_CLASH = {
    "子": "午", "午": "子", "丑": "未", "未": "丑",
    "寅": "申", "申": "寅", "卯": "酉", "酉": "卯",
    "辰": "戌", "戌": "辰", "巳": "亥", "亥": "巳",
}
BRANCH_COMBINE = {
    "子": "丑", "丑": "子", "寅": "亥", "亥": "寅",
    "卯": "戌", "戌": "卯", "辰": "酉", "酉": "辰",
    "巳": "申", "申": "巳", "午": "未", "未": "午",
}
BRANCH_HARM = {
    "子": "未", "未": "子", "丑": "午", "午": "丑",
    "寅": "巳", "巳": "寅", "卯": "辰", "辰": "卯",
    "申": "亥", "亥": "申", "酉": "戌", "戌": "酉",
}
THREE_PUNISHMENTS = (frozenset("寅巳申"), frozenset("丑未戌"), frozenset("子卯"))
SELF_PUNISHMENT = set("辰午酉亥")

ADVANCE_PAIRS = {
    ("亥", "子"), ("寅", "卯"), ("巳", "午"), ("申", "酉"),
    ("丑", "辰"), ("辰", "未"), ("未", "戌"), ("戌", "丑"),
}
RETREAT_PAIRS = {(target, source) for source, target in ADVANCE_PAIRS}


def build_six_yao_chart(values: list[int], cast_at: str = "") -> dict[str, Any]:
    """Return a deterministic Jing Fang Na Jia chart for a six-coin result."""
    if len(values) != 6 or any(value not in {6, 7, 8, 9} for value in values):
        raise ValueError("六爻必须由六、七、八、九组成，且从初爻到上爻排列。")
    observed_at = _parse_cast_at(cast_at)
    solar = Solar.fromYmdHms(
        observed_at.year,
        observed_at.month,
        observed_at.day,
        observed_at.hour,
        observed_at.minute,
        observed_at.second,
    )
    lunar = solar.getLunar()
    month_gz = lunar.getMonthInGanZhiExact()
    day_gz = lunar.getDayInGanZhiExact2()
    time_gz = lunar.getTimeInGanZhi()
    void_branches = tuple(lunar.getDayXunKongExact2())
    month_branch = month_gz[1]
    day_stem, day_branch = day_gz[0], day_gz[1]

    bits = tuple(1 if value % 2 else 0 for value in values)
    transformed_values = tuple(7 if value == 6 else 8 if value == 9 else value for value in values)
    transformed_bits = tuple(1 if value % 2 else 0 for value in transformed_values)
    palace = _palace_map()[bits]
    palace_element = PALACE_ELEMENT[palace["trigram"]]
    self_position = int(palace["self_position"])
    response_position = self_position + 3 if self_position <= 3 else self_position - 3
    najia = _najia_for_bits(bits)
    transformed_najia = _najia_for_bits(transformed_bits)
    spirits = tuple(
        SIX_SPIRITS[(SPIRIT_START[day_stem] + index) % 6] for index in range(6)
    )

    lines: list[dict[str, Any]] = []
    for index, ((stem, branch), value) in enumerate(zip(najia, values)):
        position = index + 1
        element = BRANCH_ELEMENT[branch]
        transformed_stem, transformed_branch = transformed_najia[index]
        transformed_element = BRANCH_ELEMENT[transformed_branch]
        line = {
            "position": position,
            "position_name": LINE_POSITIONS[index],
            "value": value,
            "yin_yang": "阳" if value % 2 else "阴",
            "moving": value in {6, 9},
            "stem": stem,
            "branch": branch,
            "element": element,
            "relative": _six_relative(palace_element, element),
            "spirit": spirits[index],
            "role": "世" if position == self_position else "应" if position == response_position else "",
            "void": branch in void_branches,
            "month": _calendar_relation("月", month_branch, branch),
            "day": _calendar_relation("日", day_branch, branch),
        }
        if line["moving"]:
            line["transformation"] = {
                "value": transformed_values[index],
                "yin_yang": "阳" if transformed_values[index] % 2 else "阴",
                "stem": transformed_stem,
                "branch": transformed_branch,
                "element": transformed_element,
                "relative": _six_relative(palace_element, transformed_element),
                "relation_to_origin": _transformation_relation(
                    branch,
                    element,
                    transformed_branch,
                    transformed_element,
                ),
            }
        lines.append(line)

    hidden = _hidden_spirits(palace["trigram"], palace_element, lines)
    for item in hidden:
        lines[item["position"] - 1].setdefault("hidden_spirits", []).append(item)

    return {
        "method": "京房纳甲·八宫世应",
        "method_version": "xiaobai-six-yao-v1",
        "sources": ["jingfang", "huozhulin", "zengshan"],
        "cast_at": observed_at.isoformat(timespec="seconds"),
        "timezone": "Asia/Shanghai",
        "day_boundary": "晚子时仍按民用当日排日柱",
        "calendar": {
            "month": month_gz,
            "month_branch": month_branch,
            "day": day_gz,
            "day_branch": day_branch,
            "time": time_gz,
            "day_void": "".join(void_branches),
        },
        "palace": {
            "name": f"{palace['trigram']}宫",
            "trigram": palace["trigram"],
            "element": palace_element,
            "stage": palace["stage"],
            "self_position": self_position,
            "response_position": response_position,
        },
        "lines": lines,
        "hidden_spirits": hidden,
        "branch_pattern": _hexagram_branch_pattern(lines),
        "relationships": _significant_line_relationships(lines),
    }


def _parse_cast_at(raw: str) -> datetime:
    value = str(raw or "").strip()
    if not value:
        return datetime.now(SHANGHAI)
    try:
        parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
    except ValueError as exc:
        raise ValueError("起卦时间格式不正确。") from exc
    if parsed.tzinfo is None:
        parsed = parsed.replace(tzinfo=SHANGHAI)
    return parsed.astimezone(SHANGHAI)


@lru_cache(maxsize=1)
def _palace_map() -> dict[tuple[int, ...], dict[str, Any]]:
    result: dict[tuple[int, ...], dict[str, Any]] = {}
    for trigram, trigram_bits in TRIGRAM_BITS.items():
        pure = trigram_bits + trigram_bits
        for stage, flipped, self_position in PALACE_STAGES:
            bits = list(pure)
            for index in flipped:
                bits[index] = 1 - bits[index]
            key = tuple(bits)
            if key in result:
                raise RuntimeError("八宫映射出现重复卦象。")
            result[key] = {
                "trigram": trigram,
                "stage": stage,
                "self_position": self_position,
            }
    if len(result) != 64:
        raise RuntimeError("八宫映射未覆盖六十四卦。")
    return result


def _najia_for_bits(bits: tuple[int, ...]) -> tuple[tuple[str, str], ...]:
    inner = BITS_TRIGRAM[bits[:3]]
    outer = BITS_TRIGRAM[bits[3:]]
    return tuple(NAJIA[inner]["inner"] + NAJIA[outer]["outer"])


def _six_relative(palace_element: str, line_element: str) -> str:
    if line_element == palace_element:
        return "兄弟"
    if ELEMENT_GENERATES[line_element] == palace_element:
        return "父母"
    if ELEMENT_GENERATES[palace_element] == line_element:
        return "子孙"
    if ELEMENT_CONTROLS[palace_element] == line_element:
        return "妻财"
    return "官鬼"


def _calendar_relation(prefix: str, actor_branch: str, line_branch: str) -> dict[str, Any]:
    actor_element = BRANCH_ELEMENT[actor_branch]
    line_element = BRANCH_ELEMENT[line_branch]
    labels = []
    if actor_branch == line_branch:
        labels.append(f"临{prefix}{'建' if prefix == '月' else '辰'}")
    if BRANCH_CLASH[actor_branch] == line_branch:
        labels.append("月破" if prefix == "月" else "日冲")
    if BRANCH_COMBINE[actor_branch] == line_branch:
        labels.append(f"{prefix}合")
    if BRANCH_HARM[actor_branch] == line_branch:
        labels.append(f"{prefix}害")
    element_relation = _actor_element_relation(actor_element, line_element, prefix)
    return {
        "branch": actor_branch,
        "element": actor_element,
        "branch_relations": labels,
        "element_relation": element_relation,
    }


def _actor_element_relation(actor: str, target: str, prefix: str) -> str:
    if actor == target:
        return f"{prefix}与爻同气"
    if ELEMENT_GENERATES[actor] == target:
        return f"{prefix}生爻"
    if ELEMENT_CONTROLS[actor] == target:
        return f"{prefix}克爻"
    if ELEMENT_GENERATES[target] == actor:
        return f"爻生{prefix}"
    return f"爻克{prefix}"


def _transformation_relation(
    origin_branch: str,
    origin_element: str,
    target_branch: str,
    target_element: str,
) -> list[str]:
    labels = []
    if (origin_branch, target_branch) in ADVANCE_PAIRS:
        labels.append("化进神")
    elif (origin_branch, target_branch) in RETREAT_PAIRS:
        labels.append("化退神")
    if BRANCH_COMBINE[origin_branch] == target_branch:
        labels.append("化合")
    if BRANCH_CLASH[origin_branch] == target_branch:
        labels.append("化冲")
    if target_element == origin_element:
        labels.append("变爻同气")
    elif ELEMENT_GENERATES[target_element] == origin_element:
        labels.append("回头生")
    elif ELEMENT_CONTROLS[target_element] == origin_element:
        labels.append("回头克")
    elif ELEMENT_GENERATES[origin_element] == target_element:
        labels.append("原爻生变")
    else:
        labels.append("原爻克变")
    return labels


def _hidden_spirits(
    palace_trigram: str,
    palace_element: str,
    lines: list[dict[str, Any]],
) -> list[dict[str, Any]]:
    present = {str(line["relative"]) for line in lines}
    missing = {"父母", "兄弟", "子孙", "妻财", "官鬼"} - present
    if not missing:
        return []
    pure_bits = TRIGRAM_BITS[palace_trigram] + TRIGRAM_BITS[palace_trigram]
    result = []
    for index, (stem, branch) in enumerate(_najia_for_bits(pure_bits)):
        element = BRANCH_ELEMENT[branch]
        relative = _six_relative(palace_element, element)
        if relative not in missing:
            continue
        result.append(
            {
                "position": index + 1,
                "position_name": LINE_POSITIONS[index],
                "stem": stem,
                "branch": branch,
                "element": element,
                "relative": relative,
                "flying_relative": lines[index]["relative"],
            }
        )
    return result


def _hexagram_branch_pattern(lines: list[dict[str, Any]]) -> str:
    pairs = ((0, 3), (1, 4), (2, 5))
    if all(BRANCH_CLASH[lines[left]["branch"]] == lines[right]["branch"] for left, right in pairs):
        return "六冲"
    if all(BRANCH_COMBINE[lines[left]["branch"]] == lines[right]["branch"] for left, right in pairs):
        return "六合"
    return ""


def _significant_line_relationships(lines: list[dict[str, Any]]) -> list[dict[str, Any]]:
    result = []
    for left_index in range(6):
        for right_index in range(left_index + 1, 6):
            left = lines[left_index]
            right = lines[right_index]
            if not (left["moving"] or right["moving"] or left["role"] or right["role"]):
                continue
            labels = _branch_pair_relations(left["branch"], right["branch"])
            element_relation = _pair_element_relation(left["element"], right["element"])
            if not labels and element_relation == "同气":
                continue
            result.append(
                {
                    "positions": [left["position"], right["position"]],
                    "lines": [left["position_name"], right["position_name"]],
                    "branch_relations": labels,
                    "element_relation": element_relation,
                }
            )
    return result


def _branch_pair_relations(left: str, right: str) -> list[str]:
    labels = []
    if BRANCH_COMBINE[left] == right:
        labels.append("六合")
    if BRANCH_CLASH[left] == right:
        labels.append("六冲")
    if BRANCH_HARM[left] == right:
        labels.append("六害")
    pair = frozenset((left, right))
    if pair in THREE_PUNISHMENTS or (left == right and left in SELF_PUNISHMENT):
        labels.append("相刑")
    return labels


def _pair_element_relation(left: str, right: str) -> str:
    if left == right:
        return "同气"
    if ELEMENT_GENERATES[left] == right:
        return "前者生后者"
    if ELEMENT_GENERATES[right] == left:
        return "后者生前者"
    if ELEMENT_CONTROLS[left] == right:
        return "前者克后者"
    return "后者克前者"
