"""VP deep-learn web — query build · policy filter · section synthesize (pure functions)."""

from __future__ import annotations

import re
from pathlib import Path
from typing import Any, Callable

from niannian.soul.search_providers import SearchHit, SearchProvider, filter_hits_by_allowlist
from niannian.virtual_persona.canon_lookup import (
    DEFAULT_FANWORK_POLICY,
    VALID_FANWORK_POLICIES,
    is_fanwork_hit,
)
from niannian.virtual_persona.enrich import synthesize_sections
from niannian.virtual_persona.profile_schema import DEEP_LEARN_PROFILE_SECTIONS

_DEFAULT_QUERY_TEMPLATE = "{label} {topic} 设定 资料"

TOPIC_QUERY_TEMPLATES: dict[str, str] = {
    topic: _DEFAULT_QUERY_TEMPLATE for topic in DEEP_LEARN_PROFILE_SECTIONS
}

_LINK_RE = re.compile(r"\[([^\]]+)\]\([^)]+\)")

DeepLearnSynthesizeFn = Callable[[str, str, list[SearchHit], str, str], str]


def build_deep_learn_query(label: str, topic: str) -> str:
    name = str(label or "").strip()
    section = str(topic or "").strip()
    template = TOPIC_QUERY_TEMPLATES.get(section, _DEFAULT_QUERY_TEMPLATE)
    return template.format(label=name, topic=section).strip()


def filter_hits_for_policy(hits: list[SearchHit], policy: str) -> list[SearchHit]:
    resolved = policy if policy in VALID_FANWORK_POLICIES else DEFAULT_FANWORK_POLICY
    if resolved != "strict":
        return list(hits)
    return [hit for hit in hits if not is_fanwork_hit(hit)]


def _bullets_to_prose(text: str) -> str:
    parts: list[str] = []
    for line in str(text or "").splitlines():
        stripped = line.strip()
        if not stripped:
            continue
        if stripped.startswith("- "):
            stripped = stripped[2:].strip()
        stripped = _LINK_RE.sub(r"\1", stripped)
        if stripped:
            parts.append(stripped)
    return "；".join(parts)


def synthesize_deep_section(
    label: str,
    topic: str,
    hits: list[SearchHit],
    *,
    fanwork_policy: str,
    excerpt: str = "",
) -> str:
    """Expand enrich section bullets into a long assembly paragraph."""
    name = str(label or "角色").strip() or "角色"
    section = str(topic or "").strip()
    policy = fanwork_policy if fanwork_policy in VALID_FANWORK_POLICIES else DEFAULT_FANWORK_POLICY
    filtered = filter_hits_for_policy(hits, policy)
    sections = synthesize_sections(name, filtered)
    section_text = sections.get(section, "")

    prefix = ""
    excerpt_clean = str(excerpt or "").strip()
    if excerpt_clean:
        prefix = f"结合已有手稿（{excerpt_clean[:200]}）。"

    intro = f"关于{name}的「{section}」"
    if filtered:
        details: list[str] = []
        for hit in filtered[:3]:
            snippet = (hit.snippet or hit.title or "").strip()
            if snippet:
                details.append(f"据《{hit.title}》记载，{snippet[:160].rstrip('。')}。")
        if not details:
            prose = _bullets_to_prose(section_text)
            if prose:
                details.append(prose)
        body = "".join(details)
        sources = "、".join(f"《{hit.title}》" for hit in filtered[:5])
        summary = f"{intro}，根据公开资料整理如下：{body}以上信息主要参考{sources}。"
    elif section_text:
        summary = f"{intro}，{_bullets_to_prose(section_text)}。"
    elif excerpt_clean:
        summary = f"{intro}，{excerpt_clean[:400]}。"
    else:
        summary = f"{intro}，公开检索资料暂时有限，建议后续补充更多来源后再完善本节。"

    return prefix + summary


def _slug_source(text: str) -> str:
    slug = re.sub(r"[^\w\-]+", "_", str(text or "source").strip())[:32]
    return slug or "source"


def _write_deep_learn_sources(
    sources_dir: Path,
    run_id: str,
    hits: list[SearchHit],
) -> list[str]:
    sources_dir.mkdir(parents=True, exist_ok=True)
    written: list[str] = []
    for idx, hit in enumerate(hits, start=1):
        fname = f"dl_{run_id}_{idx:02d}_{_slug_source(hit.title)}.txt"
        body = f"{hit.title}\n{hit.url}\n\n{hit.snippet}\n"
        (sources_dir / fname).write_text(body, encoding="utf-8")
        written.append(str(sources_dir / fname))
    return written


def run_task(
    *,
    label: str,
    topic: str,
    excerpt: str,
    run_id: str,
    sources_dir: Path,
    search: SearchProvider | None = None,
    allowlist: list[str] | None = None,
    fanwork_policy: str = DEFAULT_FANWORK_POLICY,
    mock: bool = True,
    max_results: int = 5,
    synthesize_fn: DeepLearnSynthesizeFn | None = None,
) -> dict[str, Any]:
    """Search, filter, synthesize one deep-learn topic; write sources/*.txt."""
    query = build_deep_learn_query(label, topic)
    policy = fanwork_policy if fanwork_policy in VALID_FANWORK_POLICIES else DEFAULT_FANWORK_POLICY

    hits: list[SearchHit] = []
    if search is not None:
        raw_hits = search.search(query, limit=max(1, int(max_results)))
        hits = filter_hits_by_allowlist(raw_hits, allowlist or [])
    hits = filter_hits_for_policy(hits, policy)

    fn = synthesize_fn
    if fn is not None:
        summary = fn(label, topic, hits, policy, excerpt)
    else:
        summary = synthesize_deep_section(
            label,
            topic,
            hits,
            fanwork_policy=policy,
            excerpt=excerpt,
        )

    source_paths = _write_deep_learn_sources(sources_dir, run_id, hits)
    source_urls = [hit.url for hit in hits if hit.url]

    return {
        "query": query,
        "hits_count": len(hits),
        "source_urls": source_urls,
        "fanwork_policy": policy,
        "summary": summary,
        "source_paths": source_paths,
        "mock": bool(mock),
    }
