from __future__ import annotations

import json
from functools import lru_cache
from pathlib import Path
from typing import Any

from backend.bootstrap.config import APP_DIR


KNOWLEDGE_FILE = APP_DIR / "data" / "heaven_knowledge.json"
# Baked into the image outside the ./data bind mount so volume overlay cannot hide it.
KNOWLEDGE_SEED_FILE = Path(__file__).resolve().parent / "assets" / "heaven_knowledge.json"


class HeavenKnowledgeError(ValueError):
    """Structured knowledge-file failure surfaced to HTTP as Chinese API errors."""

    def __init__(self, message: str, *, code: str) -> None:
        super().__init__(message)
        self.error_code = code


def prepare_heaven_context(mode: str, calculation: dict[str, Any]) -> dict[str, Any]:
    """Build the only context shape that may cross the LLM boundary."""
    if mode == "trend":
        prepared = _prepare_trend(calculation)
    elif mode == "fortune":
        prepared = _prepare_fortune(calculation)
    elif mode == "heart":
        prepared = _prepare_heart(calculation)
    else:
        raise ValueError("不支持的问天知识模式。")
    prepared["knowledge"] = retrieve_heaven_knowledge(mode, prepared)
    return prepared


def retrieve_heaven_knowledge(mode: str, context: dict[str, Any]) -> dict[str, Any]:
    catalog = _knowledge_catalog()
    source_ids: list[str]
    records: list[dict[str, Any]]
    if mode == "trend":
        source_ids = ["zhouyi"]
        records = _trend_records(catalog, context)
    elif mode == "fortune":
        source_ids = ["neijing"]
        records = _fortune_records(catalog, context)
    elif mode == "heart":
        source_ids = ["zhouyi", "jingfang", "huozhulin", "zengshan"]
        records = _heart_records(catalog, context)
    else:
        raise ValueError("不支持的问天知识模式。")
    return {
        "version": str(catalog.get("version") or ""),
        "retrieval": "deterministic-keyed",
        "sources": [
            {"id": source_id, **dict(catalog["sources"][source_id])}
            for source_id in source_ids
        ],
        "records": records,
    }


def _prepare_trend(context: dict[str, Any]) -> dict[str, Any]:
    return {
        "mode": "trend",
        "calculation": {
            "data_trade_date": context.get("data_trade_date") or "",
            "hexagram": context.get("hexagram") or {},
            "movement": context.get("movement") or {},
        },
        "interpretation_contract": {
            "required": ["明确卦势倾向", "主要矛盾", "实际动爻转折", "本卦到之卦的变化关系"],
            "allowed": ["偏进或偏守", "先难后易或由盛转收", "结论有条件或存在分歧"],
            "forbidden": ["原始行情旁证", "具体涨跌预测", "时间点预测", "无条件买卖指令", "泛化劝诫代替解卦"],
        },
    }


def _prepare_fortune(context: dict[str, Any]) -> dict[str, Any]:
    field = context.get("five_phase_field") or {}
    framework = field.get("framework") or {}
    relations = framework.get("relations") or {}
    layers = {
        str(item.get("id") or ""): item
        for item in framework.get("layers") or []
        if isinstance(item, dict)
    }
    six_qi = field.get("six_qi") or {}
    movement = field.get("movement") or {}
    pillars = field.get("pillars") or {}
    personal = context.get("personal_profile") or {}
    sector_catalog = {
        str(group.get("element") or ""): [
            str(item.get("name") or "").strip()
            for item in group.get("industries") or []
            if str(item.get("name") or "").strip()
        ]
        for group in field.get("sector_catalog") or []
        if isinstance(group, dict)
    }
    industry_bases: dict[str, list[str]] = {}

    def add_industry_basis(element: str, basis: str) -> None:
        if element not in sector_catalog or not sector_catalog[element]:
            return
        industry_bases.setdefault(element, [])
        if basis not in industry_bases[element]:
            industry_bases[element].append(basis)

    add_industry_basis(str(movement.get("phase") or ""), "中运")
    for label, qi in (
        ("司天", six_qi.get("sitian")),
        ("在泉", six_qi.get("zaiquan")),
        ("主气", six_qi.get("host_qi")),
        ("客气", six_qi.get("guest_qi")),
    ):
        qi_text = str(qi or "")
        add_industry_basis(qi_text[-1:] if qi_text else "", label)
    day_master = personal.get("day_master") or {}
    current = personal.get("current") or {}
    personal_context = {}
    if day_master:
        current_ten_gods = current.get("ten_gods") or {}
        personal_context = {
            "natal_day_master": {
                "stem": day_master.get("stem") or "",
                "element": day_master.get("element") or "",
            },
            "today_relative_to_natal_day_master": {
                "pillars": current.get("pillars") or {},
                "stem_relations": {
                    key: str((current_ten_gods.get(key) or {}).get("stem") or "")
                    for key in ("year", "month", "day")
                },
            },
        }
    return {
        "mode": "fortune",
        "calculation": {
            "calendar_date": context.get("calendar_date") or field.get("date") or "",
            "lunar_date": field.get("lunar_date") or "",
            "pillars": {
                "year": pillars.get("year") or "",
                "month": pillars.get("month") or "",
                "day": pillars.get("day") or "",
            },
            "solar_terms": field.get("solar_terms") or {},
            "year_movement": {
                "phase": movement.get("phase") or "",
                "tendency": movement.get("tendency") or "",
                "label": movement.get("label") or "",
            },
            "annual_qi": {
                "sitian": six_qi.get("sitian") or "",
                "zaiquan": six_qi.get("zaiquan") or "",
                "ruling": six_qi.get("ruling") or "",
                "ruling_qi": six_qi.get("ruling_qi") or "",
                "annual_pattern": relations.get("annual_pattern") or {},
            },
            "current_qi": {
                "step": six_qi.get("step"),
                "step_name": six_qi.get("step_name") or "",
                "host_qi": six_qi.get("host_qi") or "",
                "guest_qi": six_qi.get("guest_qi") or "",
                "guest_host_relation": relations.get("guest_host") or {},
                "alignment": relations.get("alignment") or six_qi.get("alignment") or "",
            },
            "day_trigger": {
                "day_pillar": pillars.get("day") or "",
                "summary": (layers.get("day") or {}).get("summary") or "",
            },
            "industry_symbols": [
                {
                    "element": element,
                    "basis": bases,
                    "industries": sector_catalog[element],
                }
                for element, bases in industry_bases.items()
            ],
            "personal": personal_context,
        },
        "excluded_from_interpretation": [
            "五行权重与百分比",
            "主导元素排序",
            "权重生成的复合断语",
            "预制情绪与交易行为结论",
            "行业实时行情旁证",
            "简化喜用神与强弱结论",
        ],
        "interpretation_contract": {
            "required": ["年纲", "当前客主加临", "日辰触发", "行业影响", "个人合参（如有）", "制衡动作"],
            "forbidden": [
                "重新计算五行权重",
                "把相生直接判吉",
                "把相克直接判凶",
                "用市场涨跌证明气场",
                "把行业取象写成行业涨跌预测或投资推荐",
                "把当日日柱误称为用户命局",
                "推断未提供的命局强弱或喜用神",
            ],
        },
    }


def _prepare_heart(context: dict[str, Any]) -> dict[str, Any]:
    preset = str(context.get("question_preset") or "custom")
    if preset not in {"trade", "mind", "unthemed", "custom"}:
        preset = "custom"
    return {
        "mode": "heart",
        "calculation": {
            "question": str(context.get("question") or "").strip(),
            "question_preset": preset,
            "question_scope": {
                "trade": "股票交易中的参与条件、机会、阻碍与风险，不是融资或商业合作问题。",
                "mind": "影响股票交易判断的情绪、执念或盲点。",
                "unthemed": "不指定事项的一般观照。",
                "custom": "只按用户实际写出的事项理解，不补写背景。",
            }[preset],
            "ritual": context.get("ritual") or {},
            "hexagram": context.get("hexagram") or {},
            "six_yao": context.get("six_yao") or {},
        },
        "interpretation_contract": {
            "required": ["回应所问", "本卦处境", "世应与相关六亲", "关键动变", "之卦趋向", "可验证动作"],
            "plain_language": "专业术语首次出现时立即用白话解释。",
            "forbidden": [
                "修改纳甲排盘",
                "猜测未输入的问题",
                "把股票交易改写成融资或合作问题",
                "把六亲直接等同于现实人物或资金来源",
                "单凭六神或空亡断吉凶",
                "根据旬空填实或干支日期预测应期",
                "具体股价和时间点预测",
                "无条件买卖指令",
            ],
        },
    }


def _trend_records(catalog: dict[str, Any], context: dict[str, Any]) -> list[dict[str, Any]]:
    hexagram = (context.get("calculation") or {}).get("hexagram") or {}
    moving = [line for line in hexagram.get("lines") or [] if line.get("moving")]
    method_key = "stable" if not moving else "single" if len(moving) == 1 else "multiple"
    rules = catalog["trend"]["rules"]
    records = [
        {"id": "trend-method", "source": "product_method", "text": catalog["trend"]["method"]},
        {"id": f"trend-moving-{method_key}", "source": "product_method", "text": rules[method_key]},
        _hexagram_record("primary", hexagram),
    ]
    records.extend(_line_record(line) for line in moving)
    transformed = hexagram.get("transformed") or {}
    if transformed:
        records.append(_hexagram_record("transformed", transformed))
    return records


def _fortune_records(catalog: dict[str, Any], context: dict[str, Any]) -> list[dict[str, Any]]:
    calculation = context.get("calculation") or {}
    movement = calculation.get("year_movement") or {}
    annual_qi = calculation.get("annual_qi") or {}
    current_qi = calculation.get("current_qi") or {}
    knowledge = catalog["fortune"]
    records = [
        {"id": "fortune-principle", "source": "neijing", "text": knowledge["principle"]},
    ]
    if calculation.get("industry_symbols"):
        records.append(
            {
                "id": "fortune-industry-boundary",
                "source": "product_method",
                "text": knowledge["industry_boundary"],
            }
        )
    personal = calculation.get("personal") or {}
    if personal:
        records.append(
            {
                "id": "fortune-personal-boundary",
                "source": "product_method",
                "text": knowledge["personal_boundary"],
            }
        )
        today = personal.get("today_relative_to_natal_day_master") or {}
        relation_semantics = knowledge.get("personal_relations") or {}
        for relation in dict.fromkeys((today.get("stem_relations") or {}).values()):
            if relation in relation_semantics:
                records.append(
                    {
                        "id": f"fortune-personal-{relation}",
                        "source": "product_method",
                        "subject": relation,
                        "text": relation_semantics[relation],
                    }
                )
    tendency = str(movement.get("tendency") or "")
    if tendency in knowledge["movement"]:
        records.append({"id": f"movement-{tendency}", "source": "neijing", "text": knowledge["movement"][tendency]})
    for key in ("sitian", "zaiquan"):
        qi = str(annual_qi.get(key) or "")
        if qi in knowledge["qi"]:
            records.append({"id": f"annual-{key}", "source": "neijing", "subject": qi, "text": knowledge["qi"][qi]})
    for key in ("host_qi", "guest_qi"):
        qi = str(current_qi.get(key) or "")
        if qi in knowledge["qi"]:
            records.append({"id": f"current-{key}", "source": "neijing", "subject": qi, "text": knowledge["qi"][qi]})
    relation = current_qi.get("guest_host_relation") or {}
    relation_type = str(relation.get("type") or "")
    if relation_type in knowledge["relations"]:
        records.append({"id": f"relation-{relation_type}", "source": "neijing", "subject": relation.get("label") or "", "text": knowledge["relations"][relation_type]})
    records.append({"id": "day-trigger", "source": "neijing", "text": knowledge["day_trigger"]})
    return records


def _heart_records(catalog: dict[str, Any], context: dict[str, Any]) -> list[dict[str, Any]]:
    calculation = context.get("calculation") or {}
    hexagram = calculation.get("hexagram") or {}
    six_yao = calculation.get("six_yao") or {}
    preset = str(calculation.get("question_preset") or "custom")
    heart = catalog["heart"]
    records = [
        {"id": "heart-focus", "source": "product_method", "text": heart["focus"].get(preset, heart["focus"]["custom"])},
        {"id": "heart-evidence-order", "source": "product_method", "items": heart["evidence_order"]},
        {"id": "heart-limits", "source": "product_method", "text": heart["limits"]},
        {"id": "heart-self-response", "source": "jingfang", "text": heart["semantics"]["self_response"]},
        {"id": "heart-calendar", "source": "zengshan", "text": heart["semantics"]["calendar"]},
        {"id": "heart-movement", "source": "huozhulin", "text": heart["semantics"]["movement"]},
        {"id": "heart-six-spirits", "source": "zengshan", "text": heart["semantics"]["six_spirits"]},
        {"id": "heart-timing-boundary", "source": "product_method", "text": heart["semantics"]["timing_boundary"]},
        _hexagram_record("primary", hexagram),
    ]
    relatives = {
        str(line.get("relative") or "")
        for line in six_yao.get("lines") or []
        if line.get("relative")
    }
    for relative in sorted(relatives):
        text = (heart["semantics"].get("relatives") or {}).get(relative)
        if text:
            records.append(
                {
                    "id": f"heart-relative-{relative}",
                    "source": "huozhulin",
                    "subject": relative,
                    "text": text,
                }
            )
    records.extend(_line_record(line) for line in hexagram.get("lines") or [] if line.get("moving"))
    transformed = hexagram.get("transformed") or {}
    if transformed:
        records.append(_hexagram_record("transformed", transformed))
    palace = six_yao.get("palace") or {}
    records.append(
        {
            "id": "heart-palace",
            "source": "jingfang",
            "text": (
                f"本卦归{palace.get('name') or '--'}、{palace.get('stage') or '--'}，"
                f"世在{palace.get('self_position') or '--'}爻，应在{palace.get('response_position') or '--'}爻。"
            ),
        }
    )
    return records


def _hexagram_record(kind: str, hexagram: dict[str, Any]) -> dict[str, Any]:
    return {
        "id": f"zhouyi-{kind}",
        "source": "zhouyi",
        "kind": kind,
        "name": hexagram.get("name") or "",
        "inner_trigram": hexagram.get("inner_trigram") or "",
        "outer_trigram": hexagram.get("outer_trigram") or "",
        "text": hexagram.get("text") or "",
        "tuan": hexagram.get("tuan") or "",
        "image": hexagram.get("image") or "",
    }


def _line_record(line: dict[str, Any]) -> dict[str, Any]:
    return {
        "id": f"zhouyi-line-{line.get('position') or ''}",
        "source": "zhouyi",
        "position": line.get("position"),
        "position_name": line.get("position_name") or "",
        "line_name": line.get("line_name") or "",
        "text": line.get("text") or "",
        "image": line.get("image") or "",
    }


def resolve_heaven_knowledge_path() -> Path:
    """Prefer the persisted data-dir file; fall back to the image-baked seed."""
    if KNOWLEDGE_FILE.is_file():
        return KNOWLEDGE_FILE
    if KNOWLEDGE_SEED_FILE.is_file():
        return KNOWLEDGE_SEED_FILE
    raise HeavenKnowledgeError(
        "问天知识文件缺失：未找到 heaven_knowledge.json。"
        "请确认宿主机 data 目录或镜像内 seed 文件完整。",
        code="heaven_knowledge_missing",
    )


@lru_cache(maxsize=1)
def _knowledge_catalog() -> dict[str, Any]:
    path = resolve_heaven_knowledge_path()
    try:
        raw = path.read_text(encoding="utf-8")
    except OSError as exc:
        raise HeavenKnowledgeError(
            f"问天知识文件无法读取（{path.name}）：{exc.strerror or exc}",
            code="heaven_knowledge_missing",
        ) from exc
    try:
        payload = json.loads(raw)
    except json.JSONDecodeError as exc:
        raise HeavenKnowledgeError(
            f"问天知识文件 JSON 损坏（{path.name}），无法解析："
            f"第 {exc.lineno} 行附近。",
            code="heaven_knowledge_invalid",
        ) from exc
    if not isinstance(payload, dict):
        raise HeavenKnowledgeError(
            f"问天知识文件格式不正确（{path.name}）：根节点必须是对象。",
            code="heaven_knowledge_invalid",
        )
    if not payload.get("version") or not isinstance(payload.get("sources"), dict):
        raise HeavenKnowledgeError(
            f"问天知识库格式不完整（{path.name}）：缺少 version 或 sources。",
            code="heaven_knowledge_invalid",
        )
    return payload


def clear_heaven_knowledge_cache() -> None:
    _knowledge_catalog.cache_clear()
