"""Canonical pre-execution product derivation for Task26 candidate identity."""
from __future__ import annotations

import hashlib
import json
import re
import stat
import zipfile
from pathlib import Path
from typing import Mapping, cast

_HEX = re.compile(r"^[0-9a-f]{64}$")
_TOOL_KEYS = {
    "candidate_derivation",
    "commit_observer",
    "dualcoach_controller",
    "evidence_contract",
    "final_state",
    "runtime_authority",
    "independent_candidate_verifier",
    "installed_provenance",
    "local_telegram_qa",
    "frozen_bootstrap",
    "source_golden_path",
    "ty_hermetic_runner",
    "ty_wheel_builder",
    "ty_surface_gate",
    "telegram_adapter",
    "verify_source_golden_path",
}
_EMPTY_WHEELHOUSE_PAYLOAD = {
    "root_mode": None,
    "directories": [],
    "entries": [],
}
EMPTY_WHEELHOUSE_INVENTORY_SHA256 = hashlib.sha256(
    json.dumps(
        _EMPTY_WHEELHOUSE_PAYLOAD,
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
        allow_nan=False,
    ).encode()
).hexdigest()


def canonical(value: object) -> bytes:
    return json.dumps(
        value,
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
        allow_nan=False,
    ).encode()


def sha256_file(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as stream:
        for chunk in iter(lambda: stream.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()


def trust_boundary_digest(trust_boundary: Mapping[str, object]) -> str:
    return hashlib.sha256(canonical(dict(trust_boundary))).hexdigest()


def qualification_tool_hashes(
    *, scripts_dir: Path, package_root: Path
) -> dict[str, str]:
    paths = {
        "candidate_derivation": package_root
        / "gateway/platforms/task26_candidate_derivation.py",
        "commit_observer": package_root / "gateway/commit_observer.py",
        "dualcoach_controller": package_root
        / "gateway/platforms/dualcoach_tasks21_25_controller.py",
        "evidence_contract": package_root
        / "gateway/platforms/task26_evidence_contract.py",
        "final_state": package_root / "gateway/platforms/task26_final_state.py",
        "runtime_authority": package_root
        / "gateway/platforms/task26_runtime_authority.py",
        "independent_candidate_verifier": scripts_dir
        / "independent_verify_candidate.py",
        "installed_provenance": scripts_dir / "installed_wheel_provenance.py",
        "local_telegram_qa": scripts_dir / "task26_local_telegram_qa.py",
        "frozen_bootstrap": scripts_dir / "task26_frozen_bootstrap.py",
        "source_golden_path": scripts_dir / "source_golden_path.py",
        "ty_hermetic_runner": scripts_dir / "task26_hermetic_ty_runner.py",
        "ty_wheel_builder": scripts_dir / "task26_build_ty_executable_wheel.py",
        "ty_surface_gate": scripts_dir / "task26_ty_surface_gate.py",
        "telegram_adapter": package_root / "gateway/platforms/telegram.py",
        "verify_source_golden_path": scripts_dir / "verify_source_golden_path.py",
    }
    return {name: sha256_file(path) for name, path in sorted(paths.items())}


def _inputs(value: object) -> dict[str, object]:
    if not isinstance(value, dict) or set(value) != {
        "schema",
        "generation",
        "hermes_wheel_sha256",
        "profile_wheel_sha256",
        "wheelhouse_inventory_sha256",
        "wheelhouse_entry_count",
        "trust_boundary_sha256",
        "qualification_tool_sha256",
        "hermetic_ty",
    }:
        raise ValueError("candidate derivation input schema is invalid")
    inputs = cast(dict[str, object], value)
    tools = inputs.get("qualification_tool_sha256")
    tool_values = cast(dict[str, object], tools) if isinstance(tools, dict) else {}
    hermetic_ty = inputs.get("hermetic_ty")
    hermetic_values = (
        cast(dict[str, object], hermetic_ty) if isinstance(hermetic_ty, dict) else {}
    )
    digest_fields = (
        "hermes_wheel_sha256",
        "profile_wheel_sha256",
        "wheelhouse_inventory_sha256",
        "trust_boundary_sha256",
    )
    if (
        inputs.get("schema")
        != "task26-preexecution-product-derivation-inputs-v1"
        or inputs.get("generation") != 1
        or any(_HEX.fullmatch(str(inputs.get(field, ""))) is None for field in digest_fields)
        or type(inputs.get("wheelhouse_entry_count")) is not int
        or cast(int, inputs["wheelhouse_entry_count"]) < 0
        or not isinstance(tools, dict)
        or set(tool_values) != _TOOL_KEYS
        or not isinstance(hermetic_ty, dict)
        or set(hermetic_values)
        != {
            "schema",
            "corrected_ty_wheel_sha256",
            "ty_version",
            "binary_member",
            "binary_member_sha256",
            "builder_tool_sha256",
            "runner_tool_sha256",
        }
        or hermetic_values.get("schema")
        != "task26-preexecution-hermetic-ty-binding-v1"
        or hermetic_values.get("ty_version") != "0.0.21"
        or hermetic_values.get("binary_member")
        != "ty-0.0.21.data/scripts/ty"
        or any(
            _HEX.fullmatch(str(hermetic_values.get(field, ""))) is None
            for field in (
                "corrected_ty_wheel_sha256",
                "binary_member_sha256",
                "builder_tool_sha256",
                "runner_tool_sha256",
            )
        )
        or hermetic_values.get("builder_tool_sha256")
        != tool_values.get("ty_wheel_builder")
        or hermetic_values.get("runner_tool_sha256")
        != tool_values.get("ty_hermetic_runner")
        or any(
            _HEX.fullmatch(str(item)) is None for item in tool_values.values()
        )
        or inputs["hermes_wheel_sha256"] == inputs["profile_wheel_sha256"]
        or (
            inputs["wheelhouse_entry_count"] == 0
            and inputs["wheelhouse_inventory_sha256"]
            != EMPTY_WHEELHOUSE_INVENTORY_SHA256
        )
    ):
        raise ValueError("candidate derivation inputs are invalid")
    return inputs


def candidate_digest_from_inputs(value: object) -> str:
    inputs = _inputs(value)
    return hashlib.sha256(canonical(inputs)).hexdigest()


def build_product_binding(value: object) -> dict[str, object]:
    inputs = _inputs(value)
    document: dict[str, object] = {
        "schema": "task26-preexecution-product-binding-v1",
        "derivation_inputs": inputs,
        "candidate_digest": candidate_digest_from_inputs(inputs),
    }
    document["binding_sha256"] = hashlib.sha256(canonical(document)).hexdigest()
    return document


def _private_wheel(path: Path, label: str) -> None:
    info = path.lstat()
    if (
        stat.S_ISLNK(info.st_mode)
        or not stat.S_ISREG(info.st_mode)
        or info.st_nlink != 1
        or stat.S_IMODE(info.st_mode) not in {0o600, 0o400}
    ):
        raise ValueError(f"actual sealed wheel is invalid: {label}")


def validate_product_binding(
    value: object,
    *,
    expected_tool_hashes: Mapping[str, str] | None = None,
    expected_trust_boundary_sha256: str | None = None,
    expected_wheelhouse_inventory_sha256: str | None = None,
    expected_wheelhouse_entry_count: int | None = None,
    actual_hermes_wheel: Path | None = None,
    actual_profile_wheel: Path | None = None,
    actual_ty_wheel: Path | None = None,
) -> dict[str, object]:
    if not isinstance(value, dict) or set(value) != {
        "schema",
        "derivation_inputs",
        "candidate_digest",
        "binding_sha256",
    }:
        raise ValueError("product binding schema is invalid")
    document = cast(dict[str, object], value)
    inputs = _inputs(document.get("derivation_inputs"))
    unsigned = {
        key: item for key, item in document.items() if key != "binding_sha256"
    }
    recomputed_candidate = candidate_digest_from_inputs(inputs)
    if (
        document.get("schema") != "task26-preexecution-product-binding-v1"
        or document.get("candidate_digest") != recomputed_candidate
        or document.get("binding_sha256")
        != hashlib.sha256(canonical(unsigned)).hexdigest()
    ):
        raise ValueError("candidate does not equal canonical derivation inputs")
    if expected_tool_hashes is not None and inputs["qualification_tool_sha256"] != dict(
        expected_tool_hashes
    ):
        raise ValueError("qualification tool hashes differ")
    if (
        expected_trust_boundary_sha256 is not None
        and inputs["trust_boundary_sha256"] != expected_trust_boundary_sha256
    ):
        raise ValueError("trust boundary digest differs")
    if (
        expected_wheelhouse_inventory_sha256 is not None
        and inputs["wheelhouse_inventory_sha256"]
        != expected_wheelhouse_inventory_sha256
    ) or (
        expected_wheelhouse_entry_count is not None
        and inputs["wheelhouse_entry_count"] != expected_wheelhouse_entry_count
    ):
        raise ValueError("wheelhouse derivation binding differs")
    for path, field, label in (
        (actual_hermes_wheel, "hermes_wheel_sha256", "Hermes"),
        (actual_profile_wheel, "profile_wheel_sha256", "profile"),
    ):
        if path is not None:
            _private_wheel(path, label)
            if sha256_file(path) != inputs[field]:
                raise ValueError(f"actual sealed wheel differs: {label}")
    if actual_ty_wheel is not None:
        _private_wheel(actual_ty_wheel, "corrected Ty")
        hermetic_binding = cast(dict[str, object], inputs["hermetic_ty"])
        if sha256_file(actual_ty_wheel) != hermetic_binding[
            "corrected_ty_wheel_sha256"
        ]:
            raise ValueError("actual corrected Ty wheel differs")
        try:
            with zipfile.ZipFile(actual_ty_wheel) as archive:
                member = archive.read(str(hermetic_binding["binary_member"]))
        except (KeyError, zipfile.BadZipFile) as exc:
            raise ValueError("corrected Ty executable member is invalid") from exc
        if hashlib.sha256(member).hexdigest() != hermetic_binding[
            "binary_member_sha256"
        ]:
            raise ValueError("corrected Ty executable member differs")
    return document


def validate_deployment_receipt(
    receipt: object, product_binding: object
) -> dict[str, object]:
    binding = validate_product_binding(product_binding)
    inputs = cast(dict[str, object], binding["derivation_inputs"])
    if not isinstance(receipt, dict):
        raise ValueError("deployment receipt is invalid")
    document = cast(dict[str, object], receipt)
    expected = {
        "schema": "task26-source-deployment-receipt-v2",
        "candidate_digest": binding["candidate_digest"],
        "candidate_product_binding_sha256": binding["binding_sha256"],
        "hermes_wheel_sha256": inputs["hermes_wheel_sha256"],
        "profile_wheel_sha256": inputs["profile_wheel_sha256"],
    }
    if any(document.get(key) != item for key, item in expected.items()):
        raise ValueError("deployment receipt wheel or product binding differs")
    if binding["candidate_digest"] in {
        document.get("hermes_wheel_sha256"),
        document.get("profile_wheel_sha256"),
    }:
        raise ValueError("deployment wheel field cannot equal candidate digest")
    return document
