"""Taxonomy 半自动 · routing_rules · 待确认队列。"""

from __future__ import annotations

from pathlib import Path
from typing import Any
from urllib.parse import urlparse

import yaml

from niannian.vault.store import VaultStore, _config_vault_path


class TaxonomyService:
    def __init__(self, store: VaultStore, *, home: Path) -> None:
        self._store = store
        self._home = home

    def _taxonomy_write_path(self, namespace: str) -> Path:
        name = "taxonomy.yaml" if namespace == "user" else "niannian_taxonomy.yaml"
        override = self._home / "config" / "vault" / name
        if override.is_file() or namespace == "user":
            override.parent.mkdir(parents=True, exist_ok=True)
            if not override.is_file():
                src = _config_vault_path(name)
                if src.is_file():
                    override.write_text(src.read_text(encoding="utf-8"), encoding="utf-8")
                else:
                    override.write_text("categories: []\n", encoding="utf-8")
            return override
        return _config_vault_path(name)

    def load_routing_rules(self) -> dict[str, Any]:
        paths = [
            self._store._root / "_rules" / "routing_rules.yaml",
            _config_vault_path("routing_rules.yaml"),
        ]
        merged: list[dict[str, Any]] = []
        for path in paths:
            if not path.is_file():
                continue
            with path.open(encoding="utf-8") as f:
                data = yaml.safe_load(f) or {}
            rules = data.get("rules") if isinstance(data, dict) else []
            if isinstance(rules, list):
                merged.extend(r for r in rules if isinstance(r, dict))
        return {"rules": merged}

    def save_routing_rule(self, rule: dict[str, Any]) -> None:
        path = self._store._root / "_rules" / "routing_rules.yaml"
        path.parent.mkdir(parents=True, exist_ok=True)
        data = {"rules": []}
        if path.is_file():
            with path.open(encoding="utf-8") as f:
                loaded = yaml.safe_load(f) or {}
            if isinstance(loaded, dict):
                data = loaded
        rules = list(data.get("rules") or [])
        rules.append(rule)
        data["rules"] = rules
        path.write_text(yaml.safe_dump(data, allow_unicode=True, sort_keys=False), encoding="utf-8")
        if self._store._manifest is not None:
            self._store._manifest.update_file_entry("vault/_rules/routing_rules.yaml", path)

    def match_routing(self, frontmatter: dict[str, Any]) -> dict[str, str] | None:
        rules = self.load_routing_rules().get("rules") or []
        prop = str(frontmatter.get("proposed_subproject") or frontmatter.get("subproject") or "")
        domain = ""
        url = frontmatter.get("source_url")
        if url:
            try:
                domain = (urlparse(str(url)).hostname or "").lower()
            except Exception:
                domain = ""
        for rule in rules:
            if not isinstance(rule, dict):
                continue
            match = rule.get("match") or {}
            target = rule.get("target") or {}
            if not isinstance(match, dict) or not isinstance(target, dict):
                continue
            m_prop = str(match.get("proposed_subproject") or "")
            m_domain = str(match.get("source_domain") or "").lower()
            if m_prop and m_prop != prop:
                continue
            if m_domain and (not domain or not (domain == m_domain or domain.endswith("." + m_domain))):
                continue
            if not m_prop and not m_domain:
                continue
            cat = str(target.get("category") or "")
            sub = str(target.get("subproject") or "")
            if cat and sub:
                return {"category": cat, "subproject": sub}
        return None

    def list_pending(self, namespace: str = "user") -> list[dict[str, Any]]:
        tree = self._store.list_tree(namespace)
        pending = (tree.get("categories") or {}).get("待确认") or {}
        items: list[dict[str, Any]] = []
        for subproject, pages in pending.items():
            for page in pages or []:
                try:
                    doc = self._store.read_document(page["rel_path"])
                except FileNotFoundError:
                    continue
                fm = doc.get("frontmatter") or {}
                items.append(
                    {
                        "vault_id": page.get("vault_id") or fm.get("id"),
                        "rel_path": page["rel_path"],
                        "title": page.get("title") or fm.get("title"),
                        "proposed_subproject": subproject,
                        "proposed_category": fm.get("proposed_category"),
                        "source_url": fm.get("source_url"),
                        "taxonomy_status": fm.get("taxonomy_status", "pending"),
                    }
                )
        return items

    def add_subproject_to_taxonomy(self, namespace: str, category: str, subproject: str) -> None:
        path = self._taxonomy_write_path(namespace)
        with path.open(encoding="utf-8") as f:
            data = yaml.safe_load(f) or {}
        if not isinstance(data, dict):
            data = {"categories": []}
        cats = list(data.get("categories") or [])
        found = False
        for cat in cats:
            if not isinstance(cat, dict):
                continue
            cid = str(cat.get("id") or cat.get("label") or "")
            if cid == category:
                subs = list(cat.get("subprojects") or [])
                if subproject not in subs:
                    subs.append(subproject)
                cat["subprojects"] = subs
                found = True
                break
        if not found:
            cats.append({"id": category, "label": category, "subprojects": [subproject]})
        data["categories"] = cats
        path.write_text(yaml.safe_dump(data, allow_unicode=True, sort_keys=False), encoding="utf-8")

    def confirm(
        self,
        *,
        namespace: str = "user",
        action: str,
        category: str,
        subproject: str,
        vault_ids: list[str] | None = None,
        proposed_subproject: str | None = None,
        add_routing_rule: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        if namespace != "user":
            raise ValueError("taxonomy confirm only for user namespace")
        if action not in ("adopt", "assign"):
            raise ValueError("action must be adopt or assign")
        if not category or not subproject:
            raise ValueError("category and subproject required")

        targets: list[str] = []
        if vault_ids:
            targets = list(vault_ids)
        elif proposed_subproject:
            for item in self.list_pending(namespace):
                if item.get("proposed_subproject") == proposed_subproject:
                    vid = item.get("vault_id")
                    if vid:
                        targets.append(str(vid))
        else:
            raise ValueError("vault_ids or proposed_subproject required")

        if action == "adopt":
            self.add_subproject_to_taxonomy(namespace, category, subproject)

        moved: list[dict[str, str]] = []
        for vid in targets:
            moved.append(self._store.relocate_document(vid, category=category, subproject=subproject))

        if add_routing_rule:
            self.save_routing_rule(add_routing_rule)

        return {"action": action, "moved": moved, "count": len(moved)}
