#!/usr/bin/env python3
"""Duplicate-safe runtime link registry; PRD state remains canonical."""

from __future__ import annotations

import datetime as dt
import json
import os
import tempfile
from pathlib import Path
from typing import Any


SCHEMA_VERSION = "1.0"
STATE_PATH = Path(".prd_plugin/state/substrate-links.json")
KINDS = {"goal", "capsule", "thread", "watch", "app", "specialist_case", "export"}


class LinkError(ValueError):
    """A runtime link would violate the single-binding contract."""


def _now() -> str:
    return dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")


def _path(root: str | Path) -> Path:
    return Path(root).resolve() / STATE_PATH


def load_links(repo_root: str | Path = ".") -> dict[str, Any]:
    path = _path(repo_root)
    try:
        value = json.loads(path.read_text(encoding="utf-8-sig"))
    except FileNotFoundError:
        return {"schema_version": SCHEMA_VERSION, "links": []}
    except (OSError, json.JSONDecodeError) as exc:
        raise LinkError(f"cannot read runtime links: {exc}") from exc
    report = audit_value(value)
    if report["status"] != "ok":
        raise LinkError(report["findings"][0]["message"])
    return value


def audit_value(value: Any) -> dict[str, Any]:
    findings = []
    if not isinstance(value, dict) or value.get("schema_version") != SCHEMA_VERSION or not isinstance(value.get("links"), list):
        findings.append({"severity": "critical", "code": "invalid_registry", "message": "runtime link registry must be a schema 1.0 object with links[]"})
        rows = []
    else:
        rows = value["links"]
    keys = []
    remotes = []
    for index, row in enumerate(rows):
        if not isinstance(row, dict) or row.get("kind") not in KINDS or not isinstance(row.get("local_id"), str) or not isinstance(row.get("remote_id"), str):
            findings.append({"severity": "high", "code": "invalid_link", "message": f"links[{index}] is invalid"})
            continue
        keys.append((row["kind"], row["local_id"]))
        remotes.append((row["kind"], row["remote_id"]))
    for duplicate in sorted({key for key in keys if keys.count(key) > 1}):
        findings.append({"severity": "high", "code": "duplicate_local_binding", "message": f"duplicate local binding: {duplicate}"})
    for duplicate in sorted({key for key in remotes if remotes.count(key) > 1}):
        findings.append({"severity": "high", "code": "duplicate_remote_binding", "message": f"duplicate remote binding: {duplicate}"})
    summary = {level: sum(row["severity"] == level for row in findings) for level in ("critical", "high", "medium", "low")}
    return {"status": "ok" if not findings else "error", "summary": summary, "findings": findings}


def _write(path: Path, value: dict[str, Any]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    handle, temporary = tempfile.mkstemp(prefix=path.name + ".", suffix=".tmp", dir=path.parent)
    try:
        with os.fdopen(handle, "w", encoding="utf-8", newline="\n") as stream:
            json.dump(value, stream, indent=2)
            stream.write("\n")
            stream.flush()
            os.fsync(stream.fileno())
        os.replace(temporary, path)
    finally:
        try:
            os.unlink(temporary)
        except FileNotFoundError:
            pass


def upsert_link(
    repo_root: str | Path,
    *,
    kind: str,
    local_id: str,
    remote_id: str,
    receipt_hash: str = "",
    status: str = "active",
) -> dict[str, Any]:
    if kind not in KINDS or not local_id or not remote_id:
        raise LinkError("kind, local_id, and remote_id are required")
    value = load_links(repo_root)
    matches = [row for row in value["links"] if row["kind"] == kind and row["local_id"] == local_id]
    if matches and matches[0]["remote_id"] != remote_id:
        raise LinkError(f"{kind}/{local_id} is already bound to {matches[0]['remote_id']}")
    now = _now()
    row = matches[0] if matches else {"kind": kind, "local_id": local_id, "remote_id": remote_id, "created_at": now}
    row.update({"status": status, "updated_at": now})
    if receipt_hash:
        row["receipt_hash"] = receipt_hash
    if not matches:
        value["links"].append(row)
    value["links"].sort(key=lambda item: (item["kind"], item["local_id"]))
    report = audit_value(value)
    if report["status"] != "ok":
        raise LinkError(report["findings"][0]["message"])
    _write(_path(repo_root), value)
    return {"status": "ok", "action": "updated" if matches else "created", "link": dict(row)}


def get_link(repo_root: str | Path, kind: str, local_id: str) -> dict[str, Any] | None:
    return next((dict(row) for row in load_links(repo_root)["links"] if row["kind"] == kind and row["local_id"] == local_id), None)
