"""Immutable v17 input checks for the NutriCoach v1.5 candidate."""

from __future__ import annotations

from collections.abc import Mapping, Sequence
from pathlib import Path
from typing import Final

from scripts.verify_nutricoach_v140_candidate_core import (
    CandidateContractError,
    JsonValue,
    require_digest,
    require_string,
    sha256_file,
)

R71B_MAINTENANCE_OVERLAY_PATHS: Final = frozenset({
    "cron/physique_inline_card.py",
    "cron/scheduler.py",
    "gateway/platforms/nutrition_weekly_dispatcher.py",
    "gateway/platforms/nutrition_weekly_dispatch_result.py",
    "gateway/platforms/nutrition_weekly_maintenance_contract.py",
    "gateway/platforms/nutrition_weekly_maintenance_io.py",
    "gateway/platforms/nutrition_weekly_maintenance_store.py",
    "gateway/platforms/nutrition_weekly_operations.py",
    "gateway/platforms/nutrition_weekly_operations_publication_contract.py",
    "gateway/platforms/telegram.py",
    "gateway/platforms/telegram_weekly_host_dispatch.py",
    "scripts/execute_nutricoach_v150_sealed_live.py",
    "scripts/nutricoach_v150_candidate_cli.py",
    "scripts/nutricoach_v150_candidate_wheel_sources.py",
    "scripts/nutricoach_v150_candidate_source_inventory.py",
    "scripts/nutricoach_v150_concrete_host.py",
    "scripts/nutricoach_v150_controller_worker.py",
    "scripts/nutricoach_v150_detached_bootstrap.py",
    "scripts/nutricoach_v150_host_operations.py",
    "scripts/nutricoach_v150_live_upgrade_state.py",
    "scripts/nutricoach_v150_observer_contract.py",
    "scripts/nutricoach_v150_observer_final.py",
    "scripts/nutricoach_v150_observer_runtime.py",
    "scripts/nutricoach_v150_r71b_cron_watch.py",
    "scripts/nutricoach_v150_r71b_maintenance_confirmation.py",
    "scripts/nutricoach_v150_r71b_maintenance_host.py",
    "scripts/nutricoach_v150_r71b_maintenance_transaction.py",
    "scripts/nutricoach_v150_r71b_task10_evidence.py",
    "scripts/nutricoach_v150_r71b_package.py",
    "scripts/nutricoach_v150_r71b_preparation.py",
    "scripts/nutricoach_v150_r71b_preseal_arguments.py",
    "scripts/nutricoach_v150_r71b_preseal_verifier.py",
    "scripts/nutricoach_v150_r71b_sealed_host.py",
    "scripts/nutricoach_v150_rollback_guard.py",
    "scripts/nutricoach_v150_sealed_controller.py",
    "scripts/nutricoach_v150_sealed_target.py",
    "scripts/nutricoach_v150_seven_day_observer.py",
    "scripts/prepare_nutricoach_v150_v15_preseal.py",
    "scripts/rehearse_nutricoach_v150_v15.py",
    "scripts/verify_nutricoach_v150_candidate.py",
    "scripts/verify_nutricoach_v150_candidate_inputs.py",
    "scripts/verify_nutricoach_v150_preseal_v15.py",
    "tests/cron/test_physique_inline_card_maintenance.py",
    "tests/gateway/nutrition_weekly_maintenance_adversarial_support.py",
    "tests/gateway/nutrition_weekly_maintenance_fs_mutations.py",
    "tests/gateway/nutrition_weekly_maintenance_support.py",
    "tests/gateway/test_nutrition_weekly_dispatcher_maintenance.py",
    "tests/gateway/test_nutrition_weekly_topic59_maintenance.py",
    "tests/gateway/test_nutrition_weekly_topic59_maintenance_failures.py",
    "tests/test_nutricoach_v150_r71b_package_digest.py",
    "tests/test_nutricoach_v150_seven_day_observer.py",
    "tests/test_nutricoach_v150_v15_authority_adversarial.py",
    "tests/test_nutricoach_v150_v15_closure.py",
    "tests/test_nutricoach_v150_v15_safety.py",
})

SUCCESSOR_OVERLAY_PATHS: Final = frozenset({
    "cron/physique_inline_card.py",
    "cron/scheduler.py",
    "dualcoach/profile/checkin_cli/customer_admin.py",
    "dualcoach/profile/checkin_cli/customer_coaching.py",
    "dualcoach/profile/checkin_cli/multi_customer_admission_migration.py",
    "dualcoach/profile/checkin_cli/nutrition_onboarding.py",
    "dualcoach/profile/checkin_cli/nutrition_onboarding_authority.py",
    "dualcoach/profile/checkin_cli/nutrition_onboarding_clarification_policy.py",
    "dualcoach/profile/checkin_cli/nutrition_onboarding_clarification_rules.py",
    "dualcoach/profile/checkin_cli/nutrition_onboarding_contract.py",
    "dualcoach/profile/checkin_cli/nutrition_onboarding_finalize_service.py",
    "dualcoach/profile/checkin_cli/nutrition_onboarding_finalization.py",
    "dualcoach/profile/checkin_cli/nutrition_onboarding_input_help.py",
    "dualcoach/profile/checkin_cli/nutrition_onboarding_models.py",
    "dualcoach/profile/checkin_cli/nutrition_onboarding_projection.py",
    "dualcoach/profile/checkin_cli/nutrition_onboarding_reconciliation.py",
    "dualcoach/profile/checkin_cli/nutrition_onboarding_session.py",
    "dualcoach/profile/checkin_cli/nutrition_readiness_contract.py",
    "dualcoach/profile/checkin_cli/nutrition_readiness_validators.py",
    "dualcoach/profile/checkin_cli/weekly_operations_store.py",
    "dualcoach/profile/checkin_cli/wizard.py",
    "dualcoach/profile/checkin_cli/wizard_models.py",
    "dualcoach/profile/checkin_cli/wizard_storage.py",
    "dualcoach/profile/tests/test_multi_customer_migration.py",
    "tests/gateway/test_nutrition_coaching.py",
    "gateway/platforms/dualcoach_activation_cutover.py",
    "gateway/platforms/dualcoach_admin.py",
    "gateway/platforms/dualcoach_customer_invite.py",
    "gateway/platforms/nutrition_coaching.py",
    "gateway/platforms/nutrition_onboarding_reconciliation.py",
    "gateway/platforms/nutrition_onboarding_reconciliation_copy.py",
    "gateway/platforms/nutrition_weekly_dispatcher.py",
    "gateway/platforms/physique_checkin.py",
    "gateway/platforms/physique_checkin_bindings.py",
    "gateway/platforms/telegram.py",
    "gateway/platforms/telegram_checkin_stepper_transport.py",
    "gateway/platforms/telegram_physique_checkin_stepper.py",
    "gateway/platforms/telegram_customer_bootstrap.py",
    "gateway/platforms/telegram_nutrition_onboarding_copy.py",
    "gateway/platforms/telegram_nutrition_onboarding_customer_v2.py",
    "gateway/platforms/telegram_nutrition_onboarding_runtime.py",
    "gateway/platforms/telegram_nutrition_onboarding_runtime_callback.py",
    "gateway/platforms/telegram_nutrition_onboarding_runtime_collection.py",
    "gateway/platforms/telegram_nutrition_onboarding_runtime_notice.py",
    "gateway/platforms/telegram_nutrition_onboarding_runtime_publication.py",
    "gateway/platforms/telegram_nutrition_onboarding_runtime_publication_transport.py",
    "scripts/nutricoach_v150_runtime_ops.py",
}) | R71B_MAINTENANCE_OVERLAY_PATHS


def verify_v17_overlay(
    root: Path,
    source_entries: Sequence[Mapping[str, JsonValue]],
) -> None:
    """Bind the combined source snapshot to the qualified v17 overlay."""
    evidence_lines = (
        (root / "inputs/v17-evidence-files.sha256")
        .read_text(encoding="utf-8")
        .splitlines()
    )
    evidence_hashes = {
        Path(absolute).name: digest
        for digest, absolute in (
            line.split("  ", maxsplit=1) for line in evidence_lines
        )
    }
    expected_inputs = {
        "exact-current-source-set.final.sha256": root
        / "inputs/v17-exact-source-set.sha256",
        "patch-tree.sha256": root / "inputs/v17-patch-tree.sha256",
        "sandbox.diff": root / "inputs/v17-sandbox.diff",
    }
    for name, path in expected_inputs.items():
        if evidence_hashes.get(name) != sha256_file(path):
            raise CandidateContractError(f"v17 evidence inventory mismatch: {name}")
    source_hashes = {
        require_string(entry.get("path"), "source path"): require_digest(
            entry.get("sha256"), "source hash"
        )
        for entry in source_entries
    }
    overlay_lines = (
        (root / "inputs/successor-overlay.sha256")
        .read_text(encoding="utf-8")
        .splitlines()
    )
    overlay = {
        relative: digest
        for digest, relative in (
            line.split("  ", maxsplit=1) for line in overlay_lines
        )
    }
    if frozenset(overlay) != SUCCESSOR_OVERLAY_PATHS:
        raise CandidateContractError("successor overlay path set is invalid")
    for relative, digest in overlay.items():
        if source_hashes.get(f"snapshot/source/{relative}") != digest:
            raise CandidateContractError(
                f"successor overlay mismatch: {relative}"
            )
    for line in (
        (root / "inputs/v17-patch-tree.sha256").read_text(encoding="utf-8").splitlines()
    ):
        digest, absolute = line.split("  ", maxsplit=1)
        marker = "/patch-tree/"
        if marker not in absolute:
            raise CandidateContractError("v17 patch-tree path is invalid")
        relative = absolute.split(marker, maxsplit=1)[1]
        expected = overlay.get(relative, digest)
        if source_hashes.get(f"snapshot/source/{relative}") != expected:
            raise CandidateContractError(f"qualified v17 overlay mismatch: {relative}")
