"""Discover host manifests inside explicitly trusted adapter roots."""
from __future__ import annotations

import json
from collections.abc import Iterable
from dataclasses import dataclass
from pathlib import Path

from ..domain.host import HostAdapterContractError
from ..json_boundary import JsonBoundaryError, load_owned_object


@dataclass(frozen=True)
class HostManifest:
    path: Path
    host_id: str
    factory_ref: str
    native_provider_id: str
    required_executables: tuple[str, ...]
    relay_contract: Path


def discover_host_manifests(roots: Iterable[Path]) -> tuple[HostManifest, ...]:
    manifests: list[HostManifest] = []
    for manifest_path in _manifest_paths(roots):
        manifests.append(_read_manifest(manifest_path))
    return tuple(manifests)


def _manifest_paths(roots: Iterable[Path]) -> tuple[Path, ...]:
    manifests: list[Path] = []
    for root in roots:
        if root.is_symlink():
            raise HostAdapterContractError(
                f"host discovery root is symbolic link: {root}"
            )
        if not root.is_dir():
            continue
        resolved_root = root.resolve()
        for manifest_path in sorted(root.glob("*/manifest.json")):
            resolved_manifest = manifest_path.resolve()
            if resolved_root not in resolved_manifest.parents:
                raise HostAdapterContractError(
                    f"host adapter escapes discovery root: {manifest_path}"
                )
            manifests.append(resolved_manifest)
    return tuple(manifests)


def _read_manifest(manifest_path: Path) -> HostManifest:
    raw = _read_json_object(manifest_path)
    host_id = _required_id(raw, "id", manifest_path)
    factory_ref = _required_string(raw, "factory", manifest_path)
    native_provider_id = _optional_id(raw, "nativeProviderId", manifest_path)
    required_executables = _string_tuple(raw, "requiredExecutables", manifest_path)
    relay_ref = _required_string(raw, "relayContract", manifest_path)
    relay_contract = _resolve_relay_contract(manifest_path, relay_ref)
    return HostManifest(
        path=manifest_path,
        host_id=host_id,
        factory_ref=factory_ref,
        native_provider_id=native_provider_id,
        required_executables=required_executables,
        relay_contract=relay_contract,
    )


def _read_json_object(manifest_path: Path) -> dict[str, object]:
    try:
        raw = load_owned_object(manifest_path, artifact="host adapter manifest")
    except JsonBoundaryError as exc:
        raise HostAdapterContractError(
            f"invalid host manifest: {manifest_path}"
        ) from exc
    if not isinstance(raw, dict) or raw.get("schemaVersion") != 1:
        raise HostAdapterContractError(f"invalid host manifest: {manifest_path}")
    return raw


def _required_id(raw: dict[str, object], key: str, manifest_path: Path) -> str:
    value = _required_string(raw, key, manifest_path).strip().lower()
    if not value:
        raise HostAdapterContractError(f"invalid host manifest: {manifest_path}")
    return value


def _optional_id(raw: dict[str, object], key: str, manifest_path: Path) -> str:
    value = raw.get(key, "")
    if not isinstance(value, str):
        raise HostAdapterContractError(f"invalid host manifest: {manifest_path}")
    return value.strip().lower()


def _required_string(
    raw: dict[str, object], key: str, manifest_path: Path
) -> str:
    value = raw.get(key)
    if not isinstance(value, str) or not value:
        raise HostAdapterContractError(f"invalid host manifest: {manifest_path}")
    return value


def _string_tuple(
    raw: dict[str, object], key: str, manifest_path: Path
) -> tuple[str, ...]:
    value = raw.get(key)
    if not isinstance(value, list) or not all(
        isinstance(item, str) and item for item in value
    ):
        raise HostAdapterContractError(f"invalid host manifest: {manifest_path}")
    return tuple(value)


def _resolve_relay_contract(manifest_path: Path, relay_ref: str) -> Path:
    adapter_root = manifest_path.parent.resolve()
    if Path(relay_ref).is_absolute():
        raise HostAdapterContractError(
            f"relay contract escapes adapter root: {relay_ref!r}"
        )
    relay_contract = (adapter_root / relay_ref).resolve()
    if adapter_root not in relay_contract.parents or not relay_contract.is_file():
        raise HostAdapterContractError(
            f"relay contract escapes adapter root: {relay_ref!r}"
        )
    return relay_contract
