from __future__ import annotations

import hashlib
from datetime import datetime, timezone
from typing import Any


class FragmentValidationError(ValueError):
    pass


def _now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


def normalize_fragment(data: dict[str, Any]) -> dict[str, Any]:
    """Fragment/2 信封 → left.ingest 字段 + meta。"""
    protocol = data.get("protocol", "niannian.fragment/2")
    if protocol not in ("niannian.fragment/2", "2"):
        raise FragmentValidationError(f"unsupported protocol: {protocol}")

    project = str(data.get("project") or "global")
    content = data.get("content") or {}
    summary = str(content.get("summary") or data.get("summary") or "").strip()
    if not summary:
        raise FragmentValidationError("content.summary is required")

    taxonomy = data.get("taxonomy") or {}
    kind = str(taxonomy.get("kind") or "note")
    scope = dict(data.get("scope") or {})
    source_obj = data.get("source") or {}
    channel = str(source_obj.get("channel") or "http")
    tool = str(scope.get("tool") or "")
    source = f"{tool}:{channel}" if tool else channel

    retrieval = data.get("retrieval") or {}
    tags = sorted(set(retrieval.get("tags") or []))
    relations = dict(data.get("relations") or {})
    thread_id = relations.get("thread_id")
    if not thread_id and scope.get("loop_id") and scope.get("node_id"):
        thread_id = f"{scope['loop_id']}:{scope['node_id']}"
        relations["thread_id"] = thread_id

    dedup_key = data.get("dedup_key")
    if not dedup_key and scope.get("loop_id") and scope.get("node_id"):
        h = hashlib.sha256(summary.encode()).hexdigest()[:8]
        dedup_key = f"{tool or project}:{scope.get('loop_id')}:{scope.get('node_id')}:{kind}:{h}"

    meta: dict[str, Any] = {
        "fragment_protocol": "2",
        "kind": kind,
        "scope": scope,
        "relations": relations,
        "dedup_key": dedup_key,
        "occurred_at": data.get("occurred_at") or _now_iso(),
    }
    if taxonomy.get("subtype"):
        meta["subtype"] = taxonomy.get("subtype")
    if taxonomy.get("confidence") is not None:
        meta["confidence"] = taxonomy.get("confidence")
    if retrieval.get("entities"):
        meta["entities"] = retrieval.get("entities")
    if content.get("body"):
        meta["body_ref"] = str(content.get("body"))[:2000]

    refs = relations.get("refs") or []
    if refs:
        meta["relations"]["refs"] = refs

    return {
        "text": summary,
        "tags": tags,
        "source": source,
        "project": project,
        "meta": meta,
        "dedup_key": dedup_key,
        "thread_id": thread_id,
    }


def build_fragment(
    summary: str,
    *,
    kind: str = "note",
    project: str = "global",
    scope: dict[str, Any] | None = None,
    refs: list[str] | None = None,
    tags: list[str] | None = None,
    source_channel: str = "sdk",
    subtype: str | None = None,
    thread_id: str | None = None,
) -> dict[str, Any]:
    """SDK record() 便捷构造 fragment/2 信封。"""
    scope = dict(scope or {})
    relations: dict[str, Any] = {}
    if refs:
        relations["refs"] = refs
    if thread_id:
        relations["thread_id"] = thread_id
    elif scope.get("loop_id") and scope.get("node_id"):
        relations["thread_id"] = f"{scope['loop_id']}:{scope['node_id']}"

    fragment: dict[str, Any] = {
        "protocol": "niannian.fragment/2",
        "project": project,
        "content": {"summary": summary},
        "taxonomy": {"kind": kind},
        "scope": scope,
        "source": {"channel": source_channel},
        "retrieval": {"tags": tags or []},
    }
    if subtype:
        fragment["taxonomy"]["subtype"] = subtype
    if relations:
        fragment["relations"] = relations
    return fragment
