from __future__ import annotations

import hashlib
import json
from pathlib import Path
from zipfile import ZipFile

import pytest

from scripts.verify_nutricoach_v140_candidate_core import (
    CandidateContractError,
    JsonValue,
    derive_candidate_digest,
)
from scripts.verify_nutricoach_v140_candidate_checks import verify_no_excuse_delta
from scripts.verify_nutricoach_v140_candidate_loc import verify_loc_delta


def test_candidate_digest_binds_full_derivation_when_not_a_wheel() -> None:
    # Given
    inputs = {
        "hermes_wheel_sha256": "1" * 64,
        "profile_wheel_sha256": "2" * 64,
        "source_tree_digest": "3" * 64,
        "config_digest": "4" * 64,
        "policy_digest": "5" * 64,
        "evidence_digest": "6" * 64,
        "interpreter_sha256": "7" * 64,
    }
    expected = hashlib.sha256(
        json.dumps(
            inputs,
            sort_keys=True,
            separators=(",", ":"),
            allow_nan=False,
        ).encode()
    ).hexdigest()

    # When
    actual = derive_candidate_digest(inputs)

    # Then
    assert actual == expected


def test_candidate_digest_rejects_single_wheel_identity() -> None:
    # Given
    wheel_digest = "a" * 64
    inputs = {
        "hermes_wheel_sha256": wheel_digest,
        "profile_wheel_sha256": "b" * 64,
        "source_tree_digest": "c" * 64,
        "config_digest": "d" * 64,
        "policy_digest": "e" * 64,
        "evidence_digest": "f" * 64,
        "interpreter_sha256": "0" * 64,
    }

    # When / Then
    with pytest.raises(CandidateContractError, match="single wheel"):
        _ = derive_candidate_digest(inputs, claimed=wheel_digest)


def test_physical_inventory_rejects_unknown_regular_file(
    tmp_path: Path,
) -> None:
    # Given
    from scripts.verify_nutricoach_v140_candidate_physical import verify_physical_inventory

    _ = (tmp_path / "qualification.json").write_text("{}", encoding="utf-8")
    _ = (tmp_path / "unknown.txt").write_text("x", encoding="utf-8")
    inventory: tuple[JsonValue, ...] = ({
        "path": "qualification.json",
        "sha256": hashlib.sha256(b"{}").hexdigest(),
        "mode": "0o444",
        "role": "qualification",
    },)

    # When / Then
    with pytest.raises(CandidateContractError, match="physical inventory set mismatch"):
        _ = verify_physical_inventory(tmp_path, inventory, "0" * 64)


def test_no_excuse_delta_records_resolved_inherited_violations() -> None:
    verify_no_excuse_delta(1_651, 1_628, 0, 23)
    with pytest.raises(CandidateContractError, match="no-excuse baseline delta"):
        verify_no_excuse_delta(1_651, 1_628, 0, 0)


def test_loc_receipt_must_match_shipped_baseline_and_candidate_bytes(
    tmp_path: Path,
) -> None:
    # Given: baseline and candidate wheels with independently countable source.
    member = "gateway/platforms/nutrition_coaching.py"
    baseline = tmp_path / "baseline.whl"
    candidate = tmp_path / "candidate.whl"
    with ZipFile(baseline, "w") as archive:
        archive.writestr(member, "first = 1\nsecond = 2\n")
    with ZipFile(candidate, "w") as archive:
        archive.writestr(member, "first = 1\n")
    manifest: dict[str, JsonValue] = {
        "baseline_wheels": {"hermes": baseline.name},
        "builds": [{"hermes": candidate.name}],
    }
    receipt: dict[str, JsonValue] = {
        "nutrition_coaching_member": member,
        "baseline_nutrition_coaching_pure_loc": 2,
        "nutrition_coaching_pure_loc": 1,
        "nutrition_coaching_delta": -1,
    }
    # When / Then: exact claims pass and a stale count fails.
    verify_loc_delta(tmp_path, manifest, receipt)

    receipt["nutrition_coaching_pure_loc"] = 2
    with pytest.raises(
        CandidateContractError, match="does not match wheel bytes"
    ):
        verify_loc_delta(tmp_path, manifest, receipt)
