"""语言无关的专业软件 capability 契约加载与查询。"""

from __future__ import annotations

import json
import logging
import re
import threading
from dataclasses import dataclass
from hashlib import sha256
from pathlib import Path, PurePosixPath
from typing import Any

from jsonschema import Draft202012Validator, FormatChecker

CONTRACT_ROOT = Path(__file__).resolve().parents[1] / "software-contracts"
logger = logging.getLogger(__name__)


class SoftwareContractError(ValueError):
    pass


@dataclass(frozen=True)
class OutputSpec:
    output_id: str
    filename: str
    media_type: str
    relative_path: str
    publish: bool
    required: bool


@dataclass(frozen=True)
class WorkspaceSpec:
    state_output: str
    preview_outputs: tuple[str, ...]
    required_adapter_version: str


@dataclass(frozen=True)
class CapabilityContract:
    capability: str
    display_name: str
    default_enrollment: bool
    output_namespace: str
    request_schema: dict[str, Any]
    input_policy: dict[str, Any]
    outputs: dict[str, OutputSpec]
    feature_path: tuple[str, ...]
    features: dict[str, str]
    summary: dict[str, Any]
    legacy_runtime: dict[str, Any] | None
    workspace: WorkspaceSpec | None

    def normalize_request(self, request: object) -> tuple[dict[str, Any], str]:
        errors = sorted(
            Draft202012Validator(
                self.request_schema, format_checker=FormatChecker()
            ).iter_errors(request),
            key=lambda item: list(item.absolute_path),
        )
        if errors:
            first = errors[0]
            location = ".".join(str(item) for item in first.absolute_path)
            prefix = f"{location}: " if location else ""
            raise SoftwareContractError(f"invalid {self.capability} request: {prefix}{first.message}")
        encoded = json.dumps(request, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
        max_bytes = int(self.request_schema.get("x-maxBytes") or 256 * 1024)
        if len(encoded.encode("utf-8")) > max_bytes:
            raise SoftwareContractError(f"{self.capability} request is too large")
        normalized = json.loads(encoded)
        return normalized, sha256(encoded.encode("utf-8")).hexdigest()

    def input_bindings(self, request: dict[str, Any]) -> list[dict[str, Any]]:
        return list(request.get("inputs") or [])

    def expected_outputs(self, request: dict[str, Any]) -> dict[str, OutputSpec]:
        requested = {
            item.get("key")
            for item in request.get("outputs") or []
            if isinstance(item, dict) and isinstance(item.get("key"), str)
        }
        expected = {
            output_id: spec
            for output_id, spec in self.outputs.items()
            if spec.required or output_id in requested
        }
        if requested - self.outputs.keys():
            raise SoftwareContractError(f"{self.capability} request contains unknown outputs")
        return expected

    def output_spec(self, output_id: str) -> OutputSpec:
        try:
            return self.outputs[output_id]
        except KeyError as exc:
            raise SoftwareContractError("unsupported output artifact identity") from exc

    def feature(self, request: dict[str, Any]) -> str:
        value: Any = request
        for part in self.feature_path:
            if not isinstance(value, dict):
                return ""
            value = value.get(part)
        return value if isinstance(value, str) else ""

    def required_adapter_version(self, request: dict[str, Any]) -> str:
        return self.features.get(self.feature(request), "0.0.0")

    def summarize(self, request: dict[str, Any]) -> dict[str, Any]:
        return {
            "display_name": self.display_name,
            "title": str(_value_at(request, self.summary.get("title_path") or []) or ""),
            "formats": [
                item.get("format")
                for item in request.get("outputs") or []
                if isinstance(item, dict)
            ],
        }

    def submission_schema(self) -> dict[str, Any]:
        definitions = self.request_schema.get("$defs") or {}
        properties = _resolve_local_refs(
            dict(self.request_schema.get("properties") or {}), definitions
        )
        properties.pop("schema_version", None)
        required = [
            item for item in self.request_schema.get("required") or []
            if item != "schema_version"
        ]
        return {
            "type": "object",
            "properties": properties,
            "required": required,
            "additionalProperties": False,
        }


def _value_at(value: object, path: list[str]) -> object:
    current = value
    for part in path:
        if not isinstance(current, dict):
            return None
        current = current.get(part)
    return current


def _resolve_local_refs(value: Any, definitions: dict[str, Any]) -> Any:
    if isinstance(value, list):
        return [_resolve_local_refs(item, definitions) for item in value]
    if not isinstance(value, dict):
        return value
    if set(value) == {"$ref"} and isinstance(value["$ref"], str):
        prefix = "#/$defs/"
        if not value["$ref"].startswith(prefix):
            raise RuntimeError("software contract contains a non-local schema reference")
        name = value["$ref"][len(prefix):]
        if name not in definitions:
            raise RuntimeError("software contract contains an unknown schema reference")
        return _resolve_local_refs(definitions[name], definitions)
    return {
        key: _resolve_local_refs(item, definitions)
        for key, item in value.items()
    }


def _load_contract(path: Path) -> CapabilityContract:
    raw = json.loads(path.read_text(encoding="utf-8"))
    required = {
        "capability", "display_name", "default_enrollment", "output_namespace", "request_schema",
        "input_policy", "outputs", "feature_path", "features", "summary", "legacy_runtime",
        "workspace",
    }
    if not isinstance(raw, dict) or set(raw) != required:
        raise RuntimeError(f"invalid software contract fields: {path.name}")
    capability = raw["capability"]
    namespace = raw["output_namespace"]
    if not isinstance(capability, str) or not re.fullmatch(r"[a-z][a-z0-9_.-]+@v[1-9][0-9]*", capability):
        raise RuntimeError(f"invalid software capability: {path.name}")
    if not isinstance(namespace, str) or not re.fullmatch(r"[a-z][a-z0-9_-]{0,31}", namespace):
        raise RuntimeError(f"invalid output namespace: {path.name}")
    outputs: dict[str, OutputSpec] = {}
    for output_id, item in raw["outputs"].items():
        if not isinstance(item, dict) or set(item) != {
            "filename", "media_type", "relative_path", "publish", "required"
        }:
            raise RuntimeError(f"invalid output spec: {path.name}:{output_id}")
        filename = item["filename"]
        relative_path = item["relative_path"]
        parsed_path = PurePosixPath(relative_path) if isinstance(relative_path, str) else None
        if (
            not isinstance(output_id, str)
            or not re.fullmatch(r"[a-z][a-z0-9_]{0,63}", output_id)
            or not isinstance(filename, str)
            or PurePosixPath(filename).name != filename
            or parsed_path is None
            or parsed_path.is_absolute()
            or ".." in parsed_path.parts
            or parsed_path.name != filename
            or parsed_path.as_posix() != relative_path
            or not isinstance(item["media_type"], str)
            or not item["media_type"]
            or not isinstance(item["publish"], bool)
            or not isinstance(item["required"], bool)
        ):
            raise RuntimeError(f"unsafe output spec: {path.name}:{output_id}")
        outputs[output_id] = OutputSpec(output_id=output_id, **item)
    if len({item.relative_path for item in outputs.values()}) != len(outputs):
        raise RuntimeError(f"duplicate output paths: {path.name}")
    workspace_raw = raw["workspace"]
    workspace: WorkspaceSpec | None = None
    if workspace_raw is not None:
        if not isinstance(workspace_raw, dict) or set(workspace_raw) != {
            "state_output", "preview_outputs", "required_adapter_version"
        }:
            raise RuntimeError(f"invalid workspace spec: {path.name}")
        state_output = workspace_raw["state_output"]
        preview_outputs = workspace_raw["preview_outputs"]
        required_adapter_version = workspace_raw["required_adapter_version"]
        if (
            not isinstance(state_output, str)
            or state_output not in outputs
            or not outputs[state_output].required
            or not isinstance(preview_outputs, list)
            or not preview_outputs
            or any(not isinstance(item, str) or item not in outputs for item in preview_outputs)
            or len(set(preview_outputs)) != len(preview_outputs)
            or not any(outputs[item].required for item in preview_outputs)
            or state_output in preview_outputs
            or not isinstance(required_adapter_version, str)
            or not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", required_adapter_version)
        ):
            raise RuntimeError(f"unsafe workspace spec: {path.name}")
        workspace = WorkspaceSpec(
            state_output, tuple(preview_outputs), required_adapter_version
        )
    Draft202012Validator.check_schema(raw["request_schema"])
    return CapabilityContract(
        capability=capability,
        display_name=raw["display_name"],
        default_enrollment=raw["default_enrollment"],
        output_namespace=namespace,
        request_schema=raw["request_schema"],
        input_policy=raw["input_policy"],
        outputs=outputs,
        feature_path=tuple(raw["feature_path"]),
        features=raw["features"],
        summary=raw["summary"],
        legacy_runtime=raw["legacy_runtime"],
        workspace=workspace,
    )


class _ContractRegistry:
    """Atomically reload contracts after a file change; retain the last valid snapshot."""

    def __init__(self, root: Path) -> None:
        self.root = root
        self._lock = threading.RLock()
        self._signature: tuple[tuple[str, int, int], ...] = ()
        self._failed_signature: tuple[tuple[str, int, int], ...] | None = None
        self._contracts: dict[str, CapabilityContract] = {}
        self._reload(initial=True)

    def _files(self) -> list[Path]:
        return sorted(self.root.glob("*.json"))

    def _current_signature(self) -> tuple[tuple[str, int, int], ...]:
        return tuple(
            (path.name, path.stat().st_mtime_ns, path.stat().st_size)
            for path in self._files()
        )

    def _reload(self, *, initial: bool = False) -> None:
        with self._lock:
            signature = self._current_signature()
            if not initial and signature in {self._signature, self._failed_signature}:
                return
            try:
                loaded = [_load_contract(path) for path in self._files()]
                contracts = {item.capability: item for item in loaded}
                if not contracts or len(contracts) != len(loaded):
                    raise RuntimeError(
                        "software contracts are missing or contain duplicate capabilities"
                    )
            except Exception:
                if initial:
                    raise
                self._failed_signature = signature
                logger.exception("Ignoring invalid software contract update")
                return
            self._contracts = contracts
            self._signature = signature
            self._failed_signature = None

    def contracts(self) -> dict[str, CapabilityContract]:
        self._reload()
        with self._lock:
            return dict(self._contracts)


_registry = _ContractRegistry(CONTRACT_ROOT)


def get_contracts() -> dict[str, CapabilityContract]:
    return _registry.contracts()


def supported_capabilities() -> frozenset[str]:
    return frozenset(get_contracts())


def default_capabilities() -> tuple[str, ...]:
    return tuple(
        item.capability for item in get_contracts().values() if item.default_enrollment
    )


# Import compatibility only. Runtime paths use the query functions above so edits hot reload.
CONTRACTS = get_contracts()
SUPPORTED_CAPABILITIES = supported_capabilities()
DEFAULT_CAPABILITIES = default_capabilities()


def get_contract(capability: str) -> CapabilityContract:
    try:
        return get_contracts()[capability]
    except KeyError as exc:
        raise SoftwareContractError("unsupported capability") from exc


def version_at_least(actual: str, required: str) -> bool:
    def parts(value: str) -> tuple[int, ...]:
        match = re.match(r"^(\d+)(?:\.(\d+))?(?:\.(\d+))?", value.strip())
        return tuple(int(item or 0) for item in match.groups(default="0")) if match else (0, 0, 0)

    return parts(actual) >= parts(required)


def node_supports_request(
    contract: CapabilityContract,
    request: dict[str, Any],
    runtime: dict[str, Any],
) -> bool:
    capability_runtime = (runtime.get("capability_runtime") or {}).get(contract.capability)
    if isinstance(capability_runtime, dict):
        if capability_runtime.get("health") != "ready":
            return False
        if int(capability_runtime.get("available_slots") or 0) <= 0:
            return False
        actual_version = str(capability_runtime.get("adapter_version") or "0.0.0")
        advertised_features = capability_runtime.get("features")
        feature = contract.feature(request)
        if isinstance(advertised_features, list) and feature not in advertised_features:
            return False
        required_version = contract.required_adapter_version(request)
        if contract.workspace is not None:
            if int(capability_runtime.get("workspace_protocol_version") or 0) < 1:
                return False
            if version_at_least(
                contract.workspace.required_adapter_version, required_version
            ):
                required_version = contract.workspace.required_adapter_version
        return version_at_least(actual_version, required_version)
    if contract.workspace is not None:
        return False
    # 兼容尚未升级 capability_runtime 的 Node；兼容路径由版本化 contract 声明。
    legacy_config = contract.legacy_runtime or {}
    if not legacy_config:
        return False
    legacy = _value_at(runtime, legacy_config.get("detail_path") or [])
    assumed_version = str(legacy_config.get("assumed_adapter_version") or "0.0.0")
    slots = int(_value_at(runtime, legacy_config.get("slots_path") or []) or 0)
    if legacy is None:
        return (
            slots > 0
            and version_at_least(assumed_version, contract.required_adapter_version(request))
        )
    return (
        isinstance(legacy, dict)
        and legacy.get("health") == "ready"
        and slots > 0
        and version_at_least(
            str(legacy.get("adapter_version") or "0.0.0"),
            contract.required_adapter_version(request),
        )
    )


def node_available_slots(capability: str, runtime: dict[str, Any]) -> int:
    contract = get_contract(capability)
    item = (runtime.get("capability_runtime") or {}).get(capability)
    if isinstance(item, dict) and item.get("health") == "ready":
        if (
            contract.workspace is not None
            and int(item.get("workspace_protocol_version") or 0) < 1
        ):
            return 0
        return max(0, int(item.get("available_slots") or 0))
    legacy_config = contract.legacy_runtime or {}
    if not legacy_config:
        return 0
    legacy = _value_at(runtime, legacy_config.get("detail_path") or [])
    slots = max(0, int(_value_at(runtime, legacy_config.get("slots_path") or []) or 0))
    if legacy is None:
        return slots
    if (
        isinstance(legacy, dict)
        and legacy.get("health") == "ready"
    ):
        return slots
    return 0
