"""Tests for evidence pack validation."""

from __future__ import annotations

from pathlib import Path

import pytest

from abi_control_core.sdk.evidence_pack import (
    EvidencePackBuilder,
    EvidenceValidationError,
    ValidationResult,
    validate_before_promote,
    validate_evidence_pack,
)


@pytest.fixture
def valid_run_manifest() -> dict:
    """A valid run manifest for testing."""
    return {
        "schema_version": "run-manifest/1.0",
        "run_id": "test1234",
        "gate": "intent-gate",
        "mode": "exploration",
        "trigger": "manual",
        "started_at": "2026-03-07T10:00:00Z",
        "completed_at": "2026-03-07T10:05:00Z",
        "intent_gate_hash": "sha256:" + "b" * 64,
        "verdict": "pass",
        "summary": {"total": 2, "passed": 2, "failed": 0, "skipped": 0, "blocker": 0},
    }


@pytest.fixture
def valid_budget_receipts() -> dict:
    """A valid budget receipt for testing."""
    return {
        "schema_version": "budget-receipt/1.0",
        "run_id": "test1234",
        "gate": "intent-gate",
        "generated_at": "2026-03-07T10:05:00Z",
        "budget_policy": {"enforcement_mode": "warn", "total_planned": 1000, "total_max": 2000},
        "steps": [],
        "summary": {
            "total_planned": 1000,
            "total_reserved": 1000,
            "total_actual": 800,
            "total_released": 200,
            "breaches": 0,
            "verdict": "within_budget",
        },
    }


class TestValidateEvidencePack:
    """Test validate_evidence_pack function."""

    def test_valid_pack_passes(
        self, tmp_evidence_dir: Path, valid_run_manifest: dict, valid_budget_receipts: dict
    ) -> None:
        """A complete, valid evidence pack should pass all validation checks."""
        builder = EvidencePackBuilder(tmp_evidence_dir, "test1234")
        builder.set_manifest(valid_run_manifest)
        builder.set_budget_receipts(valid_budget_receipts)
        builder.add_artifact(b"test artifact data")
        summary = builder.finalize()

        # Validation should pass
        pack_path = Path(summary["pack_dir"])
        result = validate_evidence_pack(pack_path)

        assert result.valid is True
        assert len(result.errors) == 0

    def test_valid_pack_with_eval_results_passes(
        self, tmp_evidence_dir: Path, valid_run_manifest: dict
    ) -> None:
        """A pack with eval_results instead of budget_receipts should pass."""
        builder = EvidencePackBuilder(tmp_evidence_dir, "test1234")
        builder.set_manifest(valid_run_manifest)
        builder.set_eval_results({
            "schema_version": "eval-result/1.0",
            "suite_id": "suite-1",
            "run_id": "test1234",
            "evaluated_at": "2026-03-07T10:05:00Z",
            "results": [{"case_id": "tc-1", "status": "pass"}],
            "summary": {"total": 1, "passed": 1, "failed": 0},
        })
        summary = builder.finalize()

        pack_path = Path(summary["pack_dir"])
        result = validate_evidence_pack(pack_path)

        assert result.valid is True
        assert len(result.errors) == 0

    def test_missing_manifest_fails(self, tmp_evidence_dir: Path, valid_run_manifest: dict) -> None:
        """Pack without evidence_pack_manifest.json should fail validation."""
        builder = EvidencePackBuilder(tmp_evidence_dir, "test1234")
        builder.set_manifest(valid_run_manifest)
        summary = builder.finalize()

        # Remove the evidence pack manifest
        pack_path = Path(summary["pack_dir"])
        manifest_path = pack_path / "evidence_pack_manifest.json"
        manifest_path.unlink()

        result = validate_evidence_pack(pack_path)

        assert result.valid is False
        assert any("Missing evidence_pack_manifest.json" in err for err in result.errors)

    def test_hash_mismatch_fails(
        self, tmp_evidence_dir: Path, valid_run_manifest: dict, valid_budget_receipts: dict
    ) -> None:
        """Pack with modified file should fail hash verification."""
        builder = EvidencePackBuilder(tmp_evidence_dir, "test1234")
        builder.set_manifest(valid_run_manifest)
        builder.set_budget_receipts(valid_budget_receipts)
        summary = builder.finalize()

        # Modify a file after finalization
        pack_path = Path(summary["pack_dir"])
        manifest_path = pack_path / "manifest.json"

        # Corrupt the manifest by adding extra data
        original = manifest_path.read_text(encoding="utf-8")
        manifest_path.write_text(original + " ", encoding="utf-8")

        result = validate_evidence_pack(pack_path)

        assert result.valid is False
        assert any("Hash mismatch" in err and "manifest.json" in err for err in result.errors)

    def test_missing_required_component_fails(
        self, tmp_evidence_dir: Path, valid_budget_receipts: dict
    ) -> None:
        """Pack without run_manifest should fail validation."""
        builder = EvidencePackBuilder(tmp_evidence_dir, "test1234")
        # Only add budget receipts, skip the required run_manifest
        builder.set_budget_receipts(valid_budget_receipts)

        # finalize() will raise EvidenceValidationError due to missing run_manifest
        with pytest.raises(EvidenceValidationError) as exc_info:
            builder.finalize()

        assert "Missing required component: run_manifest" in exc_info.value.errors

    def test_missing_gate_receipt_warns(
        self, tmp_evidence_dir: Path, valid_run_manifest: dict
    ) -> None:
        """Pack without gate receipt should generate warning but pass."""
        builder = EvidencePackBuilder(tmp_evidence_dir, "test1234")
        builder.set_manifest(valid_run_manifest)
        summary = builder.finalize()

        pack_path = Path(summary["pack_dir"])
        result = validate_evidence_pack(pack_path)

        # Should pass but with warning
        assert result.valid is True
        assert any("gate receipt" in warn.lower() for warn in result.warnings)

    def test_missing_referenced_file_fails(
        self, tmp_evidence_dir: Path, valid_run_manifest: dict, valid_budget_receipts: dict
    ) -> None:
        """Pack with missing referenced file should fail."""
        builder = EvidencePackBuilder(tmp_evidence_dir, "test1234")
        builder.set_manifest(valid_run_manifest)
        builder.set_budget_receipts(valid_budget_receipts)
        summary = builder.finalize()

        # Remove a referenced file
        pack_path = Path(summary["pack_dir"])
        budget_path = pack_path / "budget_receipts.json"
        budget_path.unlink()

        result = validate_evidence_pack(pack_path)

        assert result.valid is False
        assert any("budget_receipts.json" in err for err in result.errors)

    def test_missing_artifact_fails(
        self, tmp_evidence_dir: Path, valid_run_manifest: dict, valid_budget_receipts: dict
    ) -> None:
        """Pack with missing artifact should fail."""
        builder = EvidencePackBuilder(tmp_evidence_dir, "test1234")
        builder.set_manifest(valid_run_manifest)
        builder.set_budget_receipts(valid_budget_receipts)
        digest = builder.add_artifact(b"test artifact")
        summary = builder.finalize()

        # Remove the artifact
        pack_path = Path(summary["pack_dir"])
        artifact_path = pack_path / "artifacts" / digest
        artifact_path.unlink()

        result = validate_evidence_pack(pack_path)

        assert result.valid is False
        assert any("Artifact missing" in err and digest in err for err in result.errors)

    def test_nonexistent_pack_dir_fails(self, tmp_evidence_dir: Path) -> None:
        """Validation of non-existent directory should fail."""
        pack_path = tmp_evidence_dir / "nonexistent"
        result = validate_evidence_pack(pack_path)

        assert result.valid is False
        assert any("does not exist" in err for err in result.errors)

    def test_invalid_json_manifest_fails(
        self, tmp_evidence_dir: Path, valid_run_manifest: dict
    ) -> None:
        """Pack with malformed JSON manifest should fail."""
        builder = EvidencePackBuilder(tmp_evidence_dir, "test1234")
        builder.set_manifest(valid_run_manifest)
        summary = builder.finalize()

        # Corrupt the evidence pack manifest
        pack_path = Path(summary["pack_dir"])
        manifest_path = pack_path / "evidence_pack_manifest.json"
        manifest_path.write_text("{ invalid json }", encoding="utf-8")

        result = validate_evidence_pack(pack_path)

        assert result.valid is False
        assert any("Invalid JSON" in err for err in result.errors)


class TestValidateBeforePromote:
    """Test validate_before_promote pre-promotion hook."""

    def test_valid_pack_returns_result(
        self, tmp_evidence_dir: Path, valid_run_manifest: dict, valid_budget_receipts: dict
    ) -> None:
        """validate_before_promote should return ValidationResult for valid pack."""
        builder = EvidencePackBuilder(tmp_evidence_dir, "test1234")
        builder.set_manifest(valid_run_manifest)
        builder.set_budget_receipts(valid_budget_receipts)
        summary = builder.finalize()

        pack_path = Path(summary["pack_dir"])
        result = validate_before_promote(pack_path)

        assert isinstance(result, ValidationResult)
        assert result.valid is True
        assert len(result.errors) == 0

    def test_invalid_pack_raises_exception(
        self, tmp_evidence_dir: Path, valid_run_manifest: dict
    ) -> None:
        """validate_before_promote should raise EvidenceValidationError for invalid pack."""
        builder = EvidencePackBuilder(tmp_evidence_dir, "test1234")
        builder.set_manifest(valid_run_manifest)
        summary = builder.finalize()

        # Remove manifest to make pack invalid
        pack_path = Path(summary["pack_dir"])
        manifest_path = pack_path / "evidence_pack_manifest.json"
        manifest_path.unlink()

        with pytest.raises(EvidenceValidationError) as exc_info:
            validate_before_promote(pack_path)

        assert len(exc_info.value.errors) > 0
        assert "Missing evidence_pack_manifest.json" in exc_info.value.errors


class TestEvidencePackBuilderValidation:
    """Test that EvidencePackBuilder.finalize() validates automatically."""

    def test_finalize_validates_pack(
        self, tmp_evidence_dir: Path, valid_run_manifest: dict, valid_budget_receipts: dict
    ) -> None:
        """finalize() should validate the pack and pass for valid packs."""
        builder = EvidencePackBuilder(tmp_evidence_dir, "test1234")
        builder.set_manifest(valid_run_manifest)
        builder.set_budget_receipts(valid_budget_receipts)

        # Should not raise
        summary = builder.finalize()
        assert summary["run_id"] == "test1234"

    def test_finalize_raises_on_invalid_pack(
        self, tmp_evidence_dir: Path, valid_budget_receipts: dict
    ) -> None:
        """finalize() should raise EvidenceValidationError if pack is invalid."""
        builder = EvidencePackBuilder(tmp_evidence_dir, "test1234")
        # Missing required run_manifest
        builder.set_budget_receipts(valid_budget_receipts)

        with pytest.raises(EvidenceValidationError) as exc_info:
            builder.finalize()

        assert "Missing required component: run_manifest" in exc_info.value.errors
