"""Tests for gate ledger hash chain — integrity and tamper detection."""

from __future__ import annotations

import copy

import pytest

from abi_control_core.sdk.gate_ledger import (
    compute_final_governance_hash,
    compute_receipt_hash,
    create_ledger,
    validate_chain,
)


class TestComputeReceiptHash:
    """Test receipt hash computation — determinism and correctness."""

    def test_receipt_hash_deterministic(self) -> None:
        """Same inputs must produce same hash."""
        hash1 = compute_receipt_hash(
            "lint",
            0,
            "sha256:abc123",
            "sha256:def456",
            None,
        )
        hash2 = compute_receipt_hash(
            "lint",
            0,
            "sha256:abc123",
            "sha256:def456",
            None,
        )
        assert hash1 == hash2
        assert hash1.startswith("sha256:")
        assert len(hash1) == 71  # "sha256:" + 64 hex chars

    def test_receipt_hash_genesis(self) -> None:
        """First gate (GENESIS) uses None for previous_receipt_hash."""
        genesis_hash = compute_receipt_hash(
            "lint",
            0,
            "sha256:abc123",
            "sha256:def456",
            None,
        )
        assert genesis_hash.startswith("sha256:")
        # Internally uses "GENESIS" marker
        assert len(genesis_hash) == 71

    def test_receipt_hash_changes_with_inputs(self) -> None:
        """Different inputs must produce different hashes."""
        hash1 = compute_receipt_hash(
            "lint",
            0,
            "sha256:abc123",
            "sha256:def456",
            None,
        )
        hash2 = compute_receipt_hash(
            "lint",
            0,
            "sha256:different",  # Changed input
            "sha256:def456",
            None,
        )
        assert hash1 != hash2

    def test_receipt_hash_changes_with_sequence(self) -> None:
        """Different sequence must produce different hash."""
        hash1 = compute_receipt_hash(
            "lint",
            0,
            "sha256:abc123",
            "sha256:def456",
            None,
        )
        hash2 = compute_receipt_hash(
            "lint",
            1,  # Different sequence
            "sha256:abc123",
            "sha256:def456",
            None,
        )
        assert hash1 != hash2

    def test_receipt_hash_chains_correctly(self) -> None:
        """Second hash must include first hash in computation."""
        hash1 = compute_receipt_hash(
            "lint",
            0,
            "sha256:abc123",
            "sha256:def456",
            None,
        )
        hash2 = compute_receipt_hash(
            "test",
            1,
            "sha256:ghi789",
            "sha256:jkl012",
            hash1,  # Links to previous
        )
        # Hash2 must be different and must incorporate hash1
        assert hash1 != hash2
        assert hash2.startswith("sha256:")


class TestComputeFinalGovernanceHash:
    """Test final governance hash computation."""

    def test_final_governance_hash_deterministic(self) -> None:
        """Same inputs must produce same final hash."""
        hash1 = compute_final_governance_hash(
            "sha256:abc123",
            "550e8400-e29b-41d4-a716-446655440000",
        )
        hash2 = compute_final_governance_hash(
            "sha256:abc123",
            "550e8400-e29b-41d4-a716-446655440000",
        )
        assert hash1 == hash2
        assert hash1.startswith("sha256:")
        assert len(hash1) == 71

    def test_final_governance_hash_changes_with_ledger_id(self) -> None:
        """Different ledger_id must produce different final hash."""
        hash1 = compute_final_governance_hash(
            "sha256:abc123",
            "550e8400-e29b-41d4-a716-446655440000",
        )
        hash2 = compute_final_governance_hash(
            "sha256:abc123",
            "660e8400-e29b-41d4-a716-446655440000",  # Different UUID
        )
        assert hash1 != hash2


class TestValidateChain:
    """Test hash chain validation — detect tampering, skipping, reordering."""

    def test_chain_valid_empty(self) -> None:
        """Empty ledger is valid."""
        is_valid, message = validate_chain([])
        assert is_valid
        assert message == "Chain valid (empty ledger)"

    def test_chain_valid_single_gate(self) -> None:
        """Single gate with correct GENESIS is valid."""
        gates = [
            {
                "gate_id": "lint",
                "sequence": 0,
                "status": "passed",
                "inputs_hash": "sha256:abc123",
                "outputs_hash": "sha256:def456",
                "receipt_hash": compute_receipt_hash(
                    "lint", 0, "sha256:abc123", "sha256:def456", None
                ),
                "previous_receipt_hash": None,
                "started_at": "2024-01-01T00:00:00Z",
                "completed_at": "2024-01-01T00:01:00Z",
            }
        ]
        is_valid, message = validate_chain(gates)
        assert is_valid
        assert message == "Chain valid"

    def test_chain_valid_multiple_gates(self) -> None:
        """Properly chained receipts validate successfully."""
        hash1 = compute_receipt_hash(
            "lint", 0, "sha256:abc123", "sha256:def456", None
        )
        hash2 = compute_receipt_hash(
            "test", 1, "sha256:ghi789", "sha256:jkl012", hash1
        )
        hash3 = compute_receipt_hash(
            "build", 2, "sha256:mno345", "sha256:pqr678", hash2
        )

        gates = [
            {
                "gate_id": "lint",
                "sequence": 0,
                "status": "passed",
                "inputs_hash": "sha256:abc123",
                "outputs_hash": "sha256:def456",
                "receipt_hash": hash1,
                "previous_receipt_hash": None,
                "started_at": "2024-01-01T00:00:00Z",
                "completed_at": "2024-01-01T00:01:00Z",
            },
            {
                "gate_id": "test",
                "sequence": 1,
                "status": "passed",
                "inputs_hash": "sha256:ghi789",
                "outputs_hash": "sha256:jkl012",
                "receipt_hash": hash2,
                "previous_receipt_hash": hash1,
                "started_at": "2024-01-01T00:01:00Z",
                "completed_at": "2024-01-01T00:02:00Z",
            },
            {
                "gate_id": "build",
                "sequence": 2,
                "status": "passed",
                "inputs_hash": "sha256:mno345",
                "outputs_hash": "sha256:pqr678",
                "receipt_hash": hash3,
                "previous_receipt_hash": hash2,
                "started_at": "2024-01-01T00:02:00Z",
                "completed_at": "2024-01-01T00:03:00Z",
            },
        ]

        is_valid, message = validate_chain(gates)
        assert is_valid
        assert message == "Chain valid"

    def test_chain_broken_by_skip(self) -> None:
        """Removing a gate breaks the chain."""
        hash1 = compute_receipt_hash(
            "lint", 0, "sha256:abc123", "sha256:def456", None
        )
        hash2 = compute_receipt_hash(
            "test", 1, "sha256:ghi789", "sha256:jkl012", hash1
        )
        hash3 = compute_receipt_hash(
            "build", 2, "sha256:mno345", "sha256:pqr678", hash2
        )

        # Create chain with all three gates
        gates = [
            {
                "gate_id": "lint",
                "sequence": 0,
                "status": "passed",
                "inputs_hash": "sha256:abc123",
                "outputs_hash": "sha256:def456",
                "receipt_hash": hash1,
                "previous_receipt_hash": None,
                "started_at": "2024-01-01T00:00:00Z",
                "completed_at": "2024-01-01T00:01:00Z",
            },
            # Skip "test" gate — remove it from the chain
            {
                "gate_id": "build",
                "sequence": 2,  # Wrong sequence — should be 1 now
                "status": "passed",
                "inputs_hash": "sha256:mno345",
                "outputs_hash": "sha256:pqr678",
                "receipt_hash": hash3,
                "previous_receipt_hash": hash2,  # Points to missing gate
                "started_at": "2024-01-01T00:02:00Z",
                "completed_at": "2024-01-01T00:03:00Z",
            },
        ]

        is_valid, message = validate_chain(gates)
        assert not is_valid
        assert "Sequence discontinuity" in message
        assert "build" in message

    def test_chain_broken_by_reorder(self) -> None:
        """Swapping gate order breaks the chain."""
        hash1 = compute_receipt_hash(
            "lint", 0, "sha256:abc123", "sha256:def456", None
        )
        hash2 = compute_receipt_hash(
            "test", 1, "sha256:ghi789", "sha256:jkl012", hash1
        )

        # Create chain but swap the order
        gates = [
            {
                "gate_id": "test",  # Should be second
                "sequence": 0,  # Wrong sequence for this gate
                "status": "passed",
                "inputs_hash": "sha256:ghi789",
                "outputs_hash": "sha256:jkl012",
                "receipt_hash": hash2,
                "previous_receipt_hash": hash1,  # Should be None for sequence 0
                "started_at": "2024-01-01T00:01:00Z",
                "completed_at": "2024-01-01T00:02:00Z",
            },
            {
                "gate_id": "lint",  # Should be first
                "sequence": 1,  # Wrong sequence for this gate
                "status": "passed",
                "inputs_hash": "sha256:abc123",
                "outputs_hash": "sha256:def456",
                "receipt_hash": hash1,
                "previous_receipt_hash": None,
                "started_at": "2024-01-01T00:00:00Z",
                "completed_at": "2024-01-01T00:01:00Z",
            },
        ]

        is_valid, message = validate_chain(gates)
        assert not is_valid
        # First gate must have previous_receipt_hash=null
        assert "must have previous_receipt_hash=null" in message

    def test_chain_broken_by_tamper(self) -> None:
        """Modifying any receipt data breaks the chain."""
        hash1 = compute_receipt_hash(
            "lint", 0, "sha256:abc123", "sha256:def456", None
        )
        hash2 = compute_receipt_hash(
            "test", 1, "sha256:ghi789", "sha256:jkl012", hash1
        )

        gates = [
            {
                "gate_id": "lint",
                "sequence": 0,
                "status": "passed",
                "inputs_hash": "sha256:abc123",
                "outputs_hash": "sha256:def456",
                "receipt_hash": hash1,
                "previous_receipt_hash": None,
                "started_at": "2024-01-01T00:00:00Z",
                "completed_at": "2024-01-01T00:01:00Z",
            },
            {
                "gate_id": "test",
                "sequence": 1,
                "status": "passed",
                "inputs_hash": "sha256:TAMPERED",  # Changed after hash was computed
                "outputs_hash": "sha256:jkl012",
                "receipt_hash": hash2,  # Hash no longer matches
                "previous_receipt_hash": hash1,
                "started_at": "2024-01-01T00:01:00Z",
                "completed_at": "2024-01-01T00:02:00Z",
            },
        ]

        is_valid, message = validate_chain(gates)
        assert not is_valid
        assert "receipt_hash mismatch" in message
        assert "test" in message

    def test_chain_broken_by_first_gate_not_genesis(self) -> None:
        """First gate must have previous_receipt_hash=null."""
        hash1 = compute_receipt_hash(
            "lint", 0, "sha256:abc123", "sha256:def456", "sha256:fake_previous"
        )

        gates = [
            {
                "gate_id": "lint",
                "sequence": 0,
                "status": "passed",
                "inputs_hash": "sha256:abc123",
                "outputs_hash": "sha256:def456",
                "receipt_hash": hash1,
                "previous_receipt_hash": "sha256:fake_previous",  # Should be None
                "started_at": "2024-01-01T00:00:00Z",
                "completed_at": "2024-01-01T00:01:00Z",
            },
        ]

        is_valid, message = validate_chain(gates)
        assert not is_valid
        assert "must have previous_receipt_hash=null" in message

    def test_chain_broken_by_previous_hash_mismatch(self) -> None:
        """Second gate must link to first gate's actual receipt_hash."""
        hash1 = compute_receipt_hash(
            "lint", 0, "sha256:abc123", "sha256:def456", None
        )
        hash2 = compute_receipt_hash(
            "test", 1, "sha256:ghi789", "sha256:jkl012", "sha256:wrong_previous"
        )

        gates = [
            {
                "gate_id": "lint",
                "sequence": 0,
                "status": "passed",
                "inputs_hash": "sha256:abc123",
                "outputs_hash": "sha256:def456",
                "receipt_hash": hash1,
                "previous_receipt_hash": None,
                "started_at": "2024-01-01T00:00:00Z",
                "completed_at": "2024-01-01T00:01:00Z",
            },
            {
                "gate_id": "test",
                "sequence": 1,
                "status": "passed",
                "inputs_hash": "sha256:ghi789",
                "outputs_hash": "sha256:jkl012",
                "receipt_hash": hash2,
                "previous_receipt_hash": "sha256:wrong_previous",  # Not hash1
                "started_at": "2024-01-01T00:01:00Z",
                "completed_at": "2024-01-01T00:02:00Z",
            },
        ]

        is_valid, message = validate_chain(gates)
        assert not is_valid
        assert "previous_receipt_hash mismatch" in message


class TestCreateLedger:
    """Test ledger creation with validation."""

    def test_create_ledger_empty(self) -> None:
        """Empty ledger is valid."""
        ledger = create_ledger(
            "run123",
            "abi-control-core",
            "a" * 40,  # 40-char SHA-1
            [],
        )
        assert ledger["schema_version"] == "gate-ledger/1.0"
        assert ledger["run_id"] == "run123"
        assert ledger["repo"] == "abi-control-core"
        assert ledger["repo_head_sha"] == "a" * 40
        assert ledger["gates"] == []
        assert ledger["final_governance_hash"].startswith("sha256:")
        assert "created_at" in ledger
        assert "ledger_id" in ledger

    def test_create_ledger_with_gates(self) -> None:
        """Valid gate chain produces valid ledger."""
        hash1 = compute_receipt_hash(
            "lint", 0, "sha256:abc123", "sha256:def456", None
        )
        hash2 = compute_receipt_hash(
            "test", 1, "sha256:ghi789", "sha256:jkl012", hash1
        )

        gates = [
            {
                "gate_id": "lint",
                "gate_version": "1.0.0",
                "sequence": 0,
                "status": "passed",
                "inputs_hash": "sha256:abc123",
                "outputs_hash": "sha256:def456",
                "receipt_hash": hash1,
                "previous_receipt_hash": None,
                "started_at": "2024-01-01T00:00:00Z",
                "completed_at": "2024-01-01T00:01:00Z",
            },
            {
                "gate_id": "test",
                "gate_version": "1.0.0",
                "sequence": 1,
                "status": "passed",
                "inputs_hash": "sha256:ghi789",
                "outputs_hash": "sha256:jkl012",
                "receipt_hash": hash2,
                "previous_receipt_hash": hash1,
                "started_at": "2024-01-01T00:01:00Z",
                "completed_at": "2024-01-01T00:02:00Z",
            },
        ]

        ledger = create_ledger(
            "run123",
            "abi-control-core",
            "a" * 40,
            gates,
        )

        assert ledger["gates"] == gates
        # Final hash should be hash(hash2 + ledger_id)
        expected_final = compute_final_governance_hash(
            hash2, ledger["ledger_id"]
        )
        assert ledger["final_governance_hash"] == expected_final

    def test_create_ledger_with_custom_ledger_id(self) -> None:
        """Ledger accepts custom UUID."""
        custom_id = "550e8400-e29b-41d4-a716-446655440000"
        ledger = create_ledger(
            "run123",
            "abi-control-core",
            "a" * 40,
            [],
            ledger_id=custom_id,
        )
        assert ledger["ledger_id"] == custom_id

    def test_create_ledger_rejects_broken_chain(self) -> None:
        """Ledger creation fails if chain is invalid."""
        hash1 = compute_receipt_hash(
            "lint", 0, "sha256:abc123", "sha256:def456", None
        )

        # Create a gate with wrong sequence
        gates = [
            {
                "gate_id": "lint",
                "gate_version": "1.0.0",
                "sequence": 5,  # Should be 0
                "status": "passed",
                "inputs_hash": "sha256:abc123",
                "outputs_hash": "sha256:def456",
                "receipt_hash": hash1,
                "previous_receipt_hash": None,
                "started_at": "2024-01-01T00:00:00Z",
                "completed_at": "2024-01-01T00:01:00Z",
            },
        ]

        with pytest.raises(ValueError, match="Cannot create ledger"):
            create_ledger(
                "run123",
                "abi-control-core",
                "a" * 40,
                gates,
            )

    def test_final_governance_hash_matches_expected(self) -> None:
        """Final governance hash is computed correctly."""
        hash1 = compute_receipt_hash(
            "lint", 0, "sha256:abc123", "sha256:def456", None
        )

        gates = [
            {
                "gate_id": "lint",
                "gate_version": "1.0.0",
                "sequence": 0,
                "status": "passed",
                "inputs_hash": "sha256:abc123",
                "outputs_hash": "sha256:def456",
                "receipt_hash": hash1,
                "previous_receipt_hash": None,
                "started_at": "2024-01-01T00:00:00Z",
                "completed_at": "2024-01-01T00:01:00Z",
            },
        ]

        ledger_id = "550e8400-e29b-41d4-a716-446655440000"
        ledger = create_ledger(
            "run123",
            "abi-control-core",
            "a" * 40,
            gates,
            ledger_id=ledger_id,
        )

        # Manually compute expected final hash
        expected_final = compute_final_governance_hash(hash1, ledger_id)
        assert ledger["final_governance_hash"] == expected_final
