# #region Test.CleanRelease.Facade [C:3] [TYPE Module] [SEMANTICS test,clean-release,facade,orchestration]
# @BRIEF Tests for CleanReleaseFacade — policy/registry resolution, candidate overview, list candidates.
# @RELATION BINDS_TO -> [clean_release_facade]

from datetime import datetime
from unittest.mock import MagicMock, PropertyMock
import pytest

from src.models.clean_release import CleanPolicySnapshot, SourceRegistrySnapshot
from src.services.clean_release.dto import CandidateOverviewDTO
from src.services.clean_release.enums import CandidateStatus, ComplianceDecision, RunStatus
from src.services.clean_release.facade import CleanReleaseFacade
from src.services.clean_release.repositories import (
    ApprovalRepository,
    ArtifactRepository,
    AuditRepository,
    CandidateRepository,
    ComplianceRepository,
    ManifestRepository,
    PolicyRepository,
    PublicationRepository,
    ReportRepository,
)


@pytest.fixture
def mock_repos():
    """Create mock repositories and config manager."""
    return {
        "candidate_repo": MagicMock(spec=CandidateRepository),
        "artifact_repo": MagicMock(spec=ArtifactRepository),
        "manifest_repo": MagicMock(spec=ManifestRepository),
        "policy_repo": MagicMock(spec=PolicyRepository),
        "compliance_repo": MagicMock(spec=ComplianceRepository),
        "report_repo": MagicMock(spec=ReportRepository),
        "approval_repo": MagicMock(spec=ApprovalRepository),
        "publication_repo": MagicMock(spec=PublicationRepository),
        "audit_repo": MagicMock(spec=AuditRepository),
        "config_manager": MagicMock(),
    }


@pytest.fixture
def facade(mock_repos):
    """Create CleanReleaseFacade with mock repos."""
    return CleanReleaseFacade(**mock_repos)


class TestResolveActivePolicySnapshot:
    """resolve_active_policy_snapshot — policy resolution from config."""

    # #region test_resolve_policy_found [C:2] [TYPE Function]
    def test_resolve_policy_found(self, facade, mock_repos):
        """Active policy_id found → returns snapshot."""
        mock_repos["config_manager"].get_config.return_value.settings.clean_release.active_policy_id = "policy-1"
        mock_repos["policy_repo"].get_policy_snapshot.return_value = MagicMock(spec=CleanPolicySnapshot)
        result = facade.resolve_active_policy_snapshot()
        assert result is not None
        mock_repos["policy_repo"].get_policy_snapshot.assert_called_with("policy-1")
    # #endregion test_resolve_policy_found

    # #region test_resolve_policy_missing_id [C:2] [TYPE Function]
    def test_resolve_policy_missing_id(self, facade, mock_repos):
        """No active_policy_id → returns None."""
        mock_repos["config_manager"].get_config.return_value.settings.clean_release.active_policy_id = None
        result = facade.resolve_active_policy_snapshot()
        assert result is None
    # #endregion test_resolve_policy_missing_id


class TestResolveActiveRegistrySnapshot:
    """resolve_active_registry_snapshot — registry resolution from config."""

    # #region test_resolve_registry_found [C:2] [TYPE Function]
    def test_resolve_registry_found(self, facade, mock_repos):
        """Active registry_id found → returns snapshot."""
        mock_repos["config_manager"].get_config.return_value.settings.clean_release.active_registry_id = "reg-1"
        mock_repos["policy_repo"].get_registry_snapshot.return_value = MagicMock(spec=SourceRegistrySnapshot)
        result = facade.resolve_active_registry_snapshot()
        assert result is not None
        mock_repos["policy_repo"].get_registry_snapshot.assert_called_with("reg-1")
    # #endregion test_resolve_registry_found

    # #region test_resolve_registry_missing_id [C:2] [TYPE Function]
    def test_resolve_registry_missing_id(self, facade, mock_repos):
        """No active_registry_id → returns None."""
        mock_repos["config_manager"].get_config.return_value.settings.clean_release.active_registry_id = None
        result = facade.resolve_active_registry_snapshot()
        assert result is None
    # #endregion test_resolve_registry_missing_id


class TestGetCandidateOverview:
    """get_candidate_overview — comprehensive candidate overview."""

    def _make_candidate(self, **kwargs):
        """Create a candidate mock with proper attribute values."""
        c = MagicMock()
        for k, v in kwargs.items():
            setattr(c, k, v)
        return c

    # #region test_get_overview_missing_candidate [C:2] [TYPE Function]
    def test_get_overview_missing_candidate(self, facade, mock_repos):
        """Candidate not found → returns None."""
        mock_repos["candidate_repo"].get_by_id.return_value = None
        result = facade.get_candidate_overview("nonexistent")
        assert result is None
    # #endregion test_get_overview_missing_candidate

    # #region test_get_overview_full [C:2] [TYPE Function]
    def test_get_overview_full(self, facade, mock_repos):
        """Full candidate overview with all data."""
        candidate = self._make_candidate(
            id="cand-1", version="1.0.0",
            source_snapshot_ref="git:sha", status=CandidateStatus.APPROVED.value,
        )

        manifest = self._make_candidate(
            id="man-1", manifest_digest="digest",
        )

        run = self._make_candidate(
            id="run-1", status=RunStatus.SUCCEEDED.value,
        )

        report = self._make_candidate(
            id="CCR-1", final_status=ComplianceDecision.PASSED.value,
        )

        approval = self._make_candidate(decision="APPROVED")

        publication = self._make_candidate(id="pub-1", status="ACTIVE")

        policy = MagicMock(spec=CleanPolicySnapshot)
        policy.id = "policy-1"
        policy.policy_version = "1.0"

        registry = MagicMock(spec=SourceRegistrySnapshot)
        registry.id = "reg-1"
        registry.registry_version = "1.0"

        mock_repos["candidate_repo"].get_by_id.return_value = candidate
        mock_repos["manifest_repo"].get_latest_for_candidate.return_value = manifest
        mock_repos["compliance_repo"].list_runs_by_candidate.return_value = [run]
        mock_repos["report_repo"].get_by_run.return_value = report
        mock_repos["approval_repo"].get_latest_for_candidate.return_value = approval
        mock_repos["publication_repo"].get_latest_for_candidate.return_value = publication
        mock_repos["config_manager"].get_config.return_value.settings.clean_release.active_policy_id = "policy-1"
        mock_repos["config_manager"].get_config.return_value.settings.clean_release.active_registry_id = "reg-1"
        mock_repos["policy_repo"].get_policy_snapshot.return_value = policy
        mock_repos["policy_repo"].get_registry_snapshot.return_value = registry

        result = facade.get_candidate_overview("cand-1")
        assert isinstance(result, CandidateOverviewDTO)
        assert result.candidate_id == "cand-1"
        assert result.latest_manifest_id == "man-1"
        assert result.latest_run_status == RunStatus.SUCCEEDED
        assert result.latest_report_final_status == ComplianceDecision.PASSED
        assert result.latest_approval_decision == "APPROVED"
    # #endregion test_get_overview_full

    # #region test_get_overview_no_runs [C:2] [TYPE Function]
    def test_get_overview_no_runs(self, facade, mock_repos):
        """Candidate without runs → report/run fields are None."""
        candidate = self._make_candidate(
            id="cand-1", version="1.0.0",
            source_snapshot_ref="git:sha", status=CandidateStatus.DRAFT.value,
        )
        mock_repos["candidate_repo"].get_by_id.return_value = candidate
        mock_repos["manifest_repo"].get_latest_for_candidate.return_value = None
        mock_repos["compliance_repo"].list_runs_by_candidate.return_value = []
        mock_repos["approval_repo"].get_latest_for_candidate.return_value = None
        mock_repos["publication_repo"].get_latest_for_candidate.return_value = None
        mock_repos["config_manager"].get_config.return_value.settings.clean_release.active_policy_id = None
        mock_repos["config_manager"].get_config.return_value.settings.clean_release.active_registry_id = None

        result = facade.get_candidate_overview("cand-1")
        assert result.latest_manifest_id is None
        assert result.latest_run_id is None
        assert result.latest_report_id is None
        assert result.latest_approval_decision is None
    # #endregion test_get_overview_no_runs


class TestListCandidates:
    """list_candidates — list all candidates with overviews."""

    # #region test_list_candidates_empty [C:2] [TYPE Function]
    def test_list_candidates_empty(self, facade, mock_repos):
        """No candidates → returns empty list."""
        mock_repos["candidate_repo"].list_all.return_value = []
        result = facade.list_candidates()
        assert result == []
    # #endregion test_list_candidates_empty

    # #region test_list_candidates_with_data [C:2] [TYPE Function]
    def test_list_candidates_with_data(self, facade, mock_repos):
        """Candidates exist → returns list of overviews."""
        candidate = MagicMock()
        candidate.id = "cand-1"
        candidate.version = "1.0.0"
        candidate.source_snapshot_ref = "ref"
        candidate.status = CandidateStatus.DRAFT.value

        mock_repos["candidate_repo"].list_all.return_value = [candidate]
        mock_repos["candidate_repo"].get_by_id.return_value = candidate
        mock_repos["manifest_repo"].get_latest_for_candidate.return_value = None
        mock_repos["compliance_repo"].list_runs_by_candidate.return_value = []
        mock_repos["approval_repo"].get_latest_for_candidate.return_value = None
        mock_repos["publication_repo"].get_latest_for_candidate.return_value = None
        mock_repos["config_manager"].get_config.return_value.settings.clean_release.active_policy_id = None
        mock_repos["config_manager"].get_config.return_value.settings.clean_release.active_registry_id = None

        result = facade.list_candidates()
        assert len(result) == 1
        assert result[0].candidate_id == "cand-1"
    # #endregion test_list_candidates_with_data
