# #region Test.CleanRelease.ComplianceOrchestrator [C:3] [TYPE Module] [SEMANTICS test,clean-release,compliance,orchestrator]
# @BRIEF Unit tests for CleanComplianceOrchestrator — start, execute, finalize.
# @RELATION BINDS_TO -> [ComplianceOrchestrator]
# @TEST_EDGE: manifest_not_found -> ValueError
# @TEST_EDGE: missing_deps -> FAILED status
# @TEST_EDGE: finalize_derives_from_stages

from pathlib import Path
import sys
from unittest.mock import MagicMock, PropertyMock
from datetime import datetime, timezone

sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src"))

import pytest


def _make_repo() -> MagicMock:
    repo = MagicMock()
    repo.get_manifest.return_value = None
    repo.get_policy.return_value = MagicMock(id="policy-1", registry_snapshot_id="reg-1")
    repo.get_candidate.return_value = None
    repo.get_registry.return_value = MagicMock(id="registry-1")
    repo.save_check_run.side_effect = lambda r: r
    repo.stage_runs = {}
    return repo


# ── start_check_run ──

class TestStartCheckRun:
    def test_start_without_manifest(self):
        repo = _make_repo()
        repo.get_policy.return_value = MagicMock(id="policy-1")

        from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
        orch = CleanComplianceOrchestrator(repo)
        run = orch.start_check_run(
            candidate_id="cand-1",
            policy_id="policy-1",
            requested_by="tester",
        )
        assert run is not None
        assert run.candidate_id == "cand-1"
        assert run.status == "RUNNING"

    def test_start_with_manifest(self):
        repo = _make_repo()
        manifest = MagicMock()
        manifest.id = "manifest-1"
        manifest.manifest_digest = "digest-abc"
        repo.get_manifest.return_value = manifest
        repo.get_policy.return_value = MagicMock(id="policy-1", registry_snapshot_id="reg-1")

        from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
        orch = CleanComplianceOrchestrator(repo)
        run = orch.start_check_run(
            candidate_id="cand-1",
            policy_id="policy-1",
            requested_by="tester",
            manifest_id="manifest-1",
        )
        assert run is not None
        assert run.candidate_id == "cand-1"

    def test_start_manifest_not_found_raises(self):
        repo = _make_repo()
        repo.get_manifest.return_value = None
        repo.get_policy.return_value = MagicMock(id="policy-1")

        from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
        orch = CleanComplianceOrchestrator(repo)
        with pytest.raises(ValueError, match="Manifest or Policy not found"):
            orch.start_check_run(
                candidate_id="cand-1",
                policy_id="policy-1",
                manifest_id="manifest-missing",
            )

    def test_start_legacy_manifest_id_is_execution_mode(self):
        """When manifest_id looks like an execution mode string, treat it as execution_mode."""
        repo = _make_repo()

        from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
        orch = CleanComplianceOrchestrator(repo)
        run = orch.start_check_run(
            candidate_id="cand-1",
            policy_id="policy-1",
            manifest_id="tui",
        )
        # Should treat "tui" as execution_mode, not manifest_id
        assert run.manifest_id == "manifest-cand-1"

    def test_start_with_requested_by_none(self):
        repo = _make_repo()

        from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
        orch = CleanComplianceOrchestrator(repo)
        run = orch.start_check_run(
            candidate_id="cand-1",
            policy_id="policy-1",
            requested_by=None,
        )
        assert run is not None
        assert run.requested_by == "system"

    def test_start_with_policy_none(self):
        """Policy lookup returns None — should log explore but not raise (line 108)."""
        repo = _make_repo()
        repo.get_policy.return_value = None
        from unittest.mock import patch
        from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator

        orch = CleanComplianceOrchestrator(repo)
        with patch.object(orch.repository, "get_policy", return_value=None) as mock_get:
            run = orch.start_check_run(
                candidate_id="cand-1",
                policy_id="policy-missing",
                requested_by="tester",
            )
            assert run is not None
            assert run.policy_snapshot_id == "policy-missing"


# ── execute_stages ──

class TestExecuteStages:
    def test_forced_results_all_stages_pass(self):
        from src.services.clean_release.enums import ComplianceStageName, ComplianceDecision
        from src.models.clean_release import ComplianceStageRun

        repo = _make_repo()
        run = MagicMock()
        run.id = "check-1"
        run.candidate_id = "cand-1"
        run.status = "RUNNING"

        forced = [
            ComplianceStageRun(
                id="stage-1",
                run_id="check-1",
                stage_name=ComplianceStageName.DATA_PURITY.value,
                status="SUCCEEDED",
                decision=ComplianceDecision.PASSED.value,
                details_json={"message": "ok"},
            ),
            ComplianceStageRun(
                id="stage-2",
                run_id="check-1",
                stage_name=ComplianceStageName.INTERNAL_SOURCES_ONLY.value,
                status="SUCCEEDED",
                decision=ComplianceDecision.PASSED.value,
                details_json={"message": "ok"},
            ),
            ComplianceStageRun(
                id="stage-3",
                run_id="check-1",
                stage_name=ComplianceStageName.NO_EXTERNAL_ENDPOINTS.value,
                status="SUCCEEDED",
                decision=ComplianceDecision.PASSED.value,
                details_json={"message": "ok"},
            ),
            ComplianceStageRun(
                id="stage-4",
                run_id="check-1",
                stage_name=ComplianceStageName.MANIFEST_CONSISTENCY.value,
                status="SUCCEEDED",
                decision=ComplianceDecision.PASSED.value,
                details_json={"message": "ok"},
            ),
        ]

        from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
        orch = CleanComplianceOrchestrator(repo)
        result = orch.execute_stages(run, forced_results=forced)
        assert result.status == "SUCCEEDED"
        assert len(repo.stage_runs) == 4

    def test_forced_results_with_blocked(self):
        from src.services.clean_release.enums import ComplianceStageName, ComplianceDecision
        from src.models.clean_release import ComplianceStageRun

        repo = _make_repo()
        run = MagicMock()
        run.id = "check-1"
        run.candidate_id = "cand-1"
        run.status = "RUNNING"

        forced = [
            ComplianceStageRun(
                id="stage-1",
                run_id="check-1",
                stage_name=ComplianceStageName.DATA_PURITY.value,
                status="SUCCEEDED",
                decision=ComplianceDecision.PASSED.value,
                details_json={},
            ),
            ComplianceStageRun(
                id="stage-2",
                run_id="check-1",
                stage_name=ComplianceStageName.INTERNAL_SOURCES_ONLY.value,
                status="SUCCEEDED",
                decision=ComplianceDecision.PASSED.value,
                details_json={},
            ),
            ComplianceStageRun(
                id="stage-3",
                run_id="check-1",
                stage_name=ComplianceStageName.NO_EXTERNAL_ENDPOINTS.value,
                status="SUCCEEDED",
                decision=ComplianceDecision.BLOCKED.value,
                details_json={},
            ),
            ComplianceStageRun(
                id="stage-4",
                run_id="check-1",
                stage_name=ComplianceStageName.MANIFEST_CONSISTENCY.value,
                status="SUCCEEDED",
                decision=ComplianceDecision.PASSED.value,
                details_json={},
            ),
        ]

        from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
        orch = CleanComplianceOrchestrator(repo)
        result = orch.execute_stages(run, forced_results=forced)
        assert result.status == "SUCCEEDED"
        # final_status should be BLOCKED because one stage is blocked
        assert result.final_status == "BLOCKED"

    def test_no_forced_all_deps_missing(self):
        repo = _make_repo()
        repo.get_candidate.return_value = None
        repo.get_policy.return_value = None
        repo.get_registry.return_value = None
        repo.get_manifest.return_value = None

        run = MagicMock()
        run.id = "check-1"
        run.candidate_id = "cand-1"
        run.policy_snapshot_id = "policy-1"
        run.registry_snapshot_id = "reg-1"
        run.manifest_id = "manifest-1"

        from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
        orch = CleanComplianceOrchestrator(repo)
        result = orch.execute_stages(run)
        assert result.status == "FAILED"
        assert result.finished_at is not None

    def test_no_forced_with_all_deps(self):
        repo = _make_repo()
        manifest = MagicMock()
        manifest.content_json = {"summary": {"prohibited_detected_count": 0}}
        repo.get_manifest.return_value = manifest
        repo.get_candidate.return_value = MagicMock(id="cand-1")
        repo.get_policy.return_value = MagicMock(id="policy-1")
        repo.get_registry.return_value = MagicMock(id="reg-1")

        run = MagicMock()
        run.id = "check-1"
        run.candidate_id = "cand-1"
        run.policy_snapshot_id = "policy-1"
        run.registry_snapshot_id = "reg-1"
        run.manifest_id = "manifest-1"

        from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
        orch = CleanComplianceOrchestrator(repo)
        result = orch.execute_stages(run)
        assert result.status == "SUCCEEDED"
        assert result.final_status == "PASSED"

    def test_forced_with_non_compliance_stage_run_objects(self):
        """Forced results that are not ComplianceStageRun instances (lines 166-173)."""
        from src.services.clean_release.enums import ComplianceStageName, ComplianceDecision
        from src.models.clean_release import CheckStageName, CheckStageStatus, CheckStageResult

        repo = _make_repo()
        run = MagicMock()
        run.id = "check-1"
        run.candidate_id = "cand-1"
        run.status = "RUNNING"

        # Use CheckStageResult objects (not ComplianceStageRun) to hit the else branch
        forced = [
            CheckStageResult(stage=CheckStageName.DATA_PURITY, status=CheckStageStatus.PASS),
            CheckStageResult(stage=CheckStageName.INTERNAL_SOURCES_ONLY, status=CheckStageStatus.PASS),
            CheckStageResult(stage=CheckStageName.NO_EXTERNAL_ENDPOINTS, status=CheckStageStatus.FAIL),
            CheckStageResult(stage=CheckStageName.MANIFEST_CONSISTENCY, status=CheckStageStatus.PASS),
        ]

        from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
        orch = CleanComplianceOrchestrator(repo)
        result = orch.execute_stages(run, forced_results=forced)
        assert result.status == "SUCCEEDED"
        assert result.final_status == "BLOCKED"
        assert len(repo.stage_runs) == 4

    def test_forced_with_non_compliance_stage_run_error_status(self):
        """Forced result with unknown status hits the 'else' in the status check."""
        from src.services.clean_release.enums import ComplianceStageName, ComplianceDecision
        from src.models.clean_release import CheckStageName, CheckStageStatus, CheckStageResult

        repo = _make_repo()
        run = MagicMock()
        run.id = "check-1"
        run.candidate_id = "cand-1"
        run.status = "RUNNING"

        forced = [
            CheckStageResult(stage=CheckStageName.DATA_PURITY, status=CheckStageStatus.RUNNING),
        ]

        from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
        orch = CleanComplianceOrchestrator(repo)
        result = orch.execute_stages(run, forced_results=forced)
        assert result.status == "SUCCEEDED"
        # RUNNING status is neither PASS nor FAIL -> decision becomes ERROR -> stage SKIPPED -> FAILED
        assert result.final_status == "FAILED"


# ── finalize_run ──

class TestFinalizeRun:
    def test_finalize_failed_run(self):
        repo = _make_repo()

        run = MagicMock()
        run.id = "check-1"
        run.status = "FAILED"
        run.final_status = None

        from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
        orch = CleanComplianceOrchestrator(repo)
        result = orch.finalize_run(run)
        assert result.finished_at is not None

    def test_finalize_with_existing_final_status(self):
        repo = _make_repo()

        run = MagicMock()
        run.id = "check-1"
        run.status = "RUNNING"
        run.final_status = "PASSED"

        from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
        orch = CleanComplianceOrchestrator(repo)
        result = orch.finalize_run(run)
        assert result.status == "SUCCEEDED"

    def test_finalize_derives_from_stage_runs(self):
        from src.services.clean_release.enums import ComplianceStageName, ComplianceDecision
        from src.models.clean_release import ComplianceStageRun

        repo = _make_repo()
        repo.stage_runs = {
            "stage-1": ComplianceStageRun(
                id="stage-1", run_id="check-1",
                stage_name=ComplianceStageName.DATA_PURITY.value,
                status="SUCCEEDED", decision=ComplianceDecision.PASSED.value,
                details_json={},
            ),
        }

        run = MagicMock()
        run.id = "check-1"
        run.status = "RUNNING"
        run.final_status = None

        from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
        orch = CleanComplianceOrchestrator(repo)
        result = orch.finalize_run(run)
        assert result.status == "SUCCEEDED"

    def test_finalize_derives_no_stages(self):
        repo = _make_repo()
        repo.stage_runs = {}

        run = MagicMock()
        run.id = "check-1"
        run.status = "RUNNING"
        run.final_status = None

        from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
        orch = CleanComplianceOrchestrator(repo)
        result = orch.finalize_run(run)
        assert result.status == "SUCCEEDED"


# ── run_check_legacy ──

class TestRunCheckLegacy:
    def test_legacy_pipeline(self):
        repo = _make_repo()
        repo.get_candidate.return_value = MagicMock(id="cand-1")
        repo.get_policy.return_value = MagicMock(id="policy-1", registry_snapshot_id="reg-1")
        repo.get_manifest.return_value = MagicMock(
            id="manifest-1",
            manifest_digest="digest-abc",
        )

        from src.services.clean_release.compliance_orchestrator import run_check_legacy
        result = run_check_legacy(
            repository=repo,
            candidate_id="cand-1",
            policy_id="policy-1",
            requested_by="tester",
            manifest_id="manifest-1",
        )
        assert result is not None
        assert result.candidate_id == "cand-1"
# #endregion Test.CleanRelease.ComplianceOrchestrator
