# [DEF:backend.tests.services.clean_release.test_preparation_service:Module]
# @TIER: STANDARD
# @SEMANTICS: tests, clean-release, preparation, flow
# @PURPOSE: Validate release candidate preparation flow, including policy evaluation and manifest persisting.
# @LAYER: Domain
# @RELATION: TESTS -> backend.src.services.clean_release.preparation_service
# @INVARIANT: Candidate preparation always persists manifest and candidate status deterministically.

import pytest
from unittest.mock import MagicMock, patch
from datetime import datetime, timezone

from src.models.clean_release import (
    CleanProfilePolicy,
    ResourceSourceRegistry,
    ResourceSourceEntry,
    ReleaseCandidate,
    ReleaseCandidateStatus,
    ProfileType,
    DistributionManifest
)
from src.services.clean_release.preparation_service import prepare_candidate

def _mock_policy() -> CleanProfilePolicy:
    return CleanProfilePolicy(
        policy_id="pol-1",
        policy_version="1.0.0",
        active=True,
        prohibited_artifact_categories=["prohibited"],
        required_system_categories=["system"],
        external_source_forbidden=True,
        internal_source_registry_ref="reg-1",
        effective_from=datetime.now(timezone.utc),
        profile=ProfileType.ENTERPRISE_CLEAN,
    )

def _mock_registry() -> ResourceSourceRegistry:
    return ResourceSourceRegistry(
        registry_id="reg-1",
        name="Reg",
        entries=[ResourceSourceEntry(source_id="s1", host="nexus.internal", protocol="https", purpose="pkg", enabled=True)],
        updated_at=datetime.now(timezone.utc),
        updated_by="tester"
    )

def _mock_candidate(candidate_id: str) -> ReleaseCandidate:
    return ReleaseCandidate(
        candidate_id=candidate_id,
        version="1.0.0",
        profile=ProfileType.ENTERPRISE_CLEAN,
        created_at=datetime.now(timezone.utc),
        status=ReleaseCandidateStatus.DRAFT,
        created_by="tester",
        source_snapshot_ref="v1.0.0-snapshot"
    )

def test_prepare_candidate_success():
    # Setup
    repository = MagicMock()
    candidate_id = "cand-1"
    candidate = _mock_candidate(candidate_id)
    repository.get_candidate.return_value = candidate
    repository.get_active_policy.return_value = _mock_policy()
    repository.get_registry.return_value = _mock_registry()
    
    artifacts = [{"path": "file1.txt", "category": "system"}]
    sources = ["nexus.internal"]
    
    # Execute
    with patch("src.services.clean_release.preparation_service.CleanPolicyEngine") as MockEngine:
        mock_engine_instance = MockEngine.return_value
        mock_engine_instance.validate_policy.return_value.ok = True
        mock_engine_instance.evaluate_candidate.return_value = (
            [{"path": "file1.txt", "category": "system", "classification": "required-system", "reason": "system-core"}],
            []
        )
        
        result = prepare_candidate(repository, candidate_id, artifacts, sources, "operator-1")
        
    # Verify
    assert result["status"] == ReleaseCandidateStatus.PREPARED.value
    assert candidate.status == ReleaseCandidateStatus.PREPARED
    repository.save_manifest.assert_called_once()
    repository.save_candidate.assert_called_with(candidate)

def test_prepare_candidate_with_violations():
    # Setup
    repository = MagicMock()
    candidate_id = "cand-1"
    candidate = _mock_candidate(candidate_id)
    repository.get_candidate.return_value = candidate
    repository.get_active_policy.return_value = _mock_policy()
    repository.get_registry.return_value = _mock_registry()
    
    artifacts = [{"path": "bad.txt", "category": "prohibited"}]
    sources = []
    
    # Execute
    with patch("src.services.clean_release.preparation_service.CleanPolicyEngine") as MockEngine:
        mock_engine_instance = MockEngine.return_value
        mock_engine_instance.validate_policy.return_value.ok = True
        mock_engine_instance.evaluate_candidate.return_value = (
            [{"path": "bad.txt", "category": "prohibited", "classification": "excluded-prohibited", "reason": "test-data"}],
            [{"category": "data-purity", "blocked_release": True}]
        )
        
        result = prepare_candidate(repository, candidate_id, artifacts, sources, "operator-1")
        
    # Verify
    assert result["status"] == ReleaseCandidateStatus.BLOCKED.value
    assert candidate.status == ReleaseCandidateStatus.BLOCKED
    assert len(result["violations"]) == 1

def test_prepare_candidate_not_found():
    repository = MagicMock()
    repository.get_candidate.return_value = None
    
    with pytest.raises(ValueError, match="Candidate not found"):
        prepare_candidate(repository, "non-existent", [], [], "op")

def test_prepare_candidate_no_active_policy():
    repository = MagicMock()
    repository.get_candidate.return_value = _mock_candidate("cand-1")
    repository.get_active_policy.return_value = None
    
    with pytest.raises(ValueError, match="Active clean policy not found"):
        prepare_candidate(repository, "cand-1", [], [], "op")
