# [DEF:backend.src.services.clean_release.preparation_service:Module]
# @TIER: STANDARD
# @SEMANTICS: clean-release, preparation, manifest, policy-evaluation
# @PURPOSE: Prepare release candidate by policy evaluation and deterministic manifest creation.
# @LAYER: Domain
# @RELATION: DEPENDS_ON -> backend.src.services.clean_release.policy_engine
# @RELATION: DEPENDS_ON -> backend.src.services.clean_release.manifest_builder
# @RELATION: DEPENDS_ON -> backend.src.services.clean_release.repository
# @INVARIANT: Candidate preparation always persists manifest and candidate status deterministically.

from __future__ import annotations

from datetime import datetime, timezone
from typing import Dict, Iterable

from .manifest_builder import build_distribution_manifest
from .policy_engine import CleanPolicyEngine
from .repository import CleanReleaseRepository
from .enums import CandidateStatus


def prepare_candidate(
    repository: CleanReleaseRepository,
    candidate_id: str,
    artifacts: Iterable[Dict],
    sources: Iterable[str],
    operator_id: str,
) -> Dict:
    candidate = repository.get_candidate(candidate_id)
    if candidate is None:
        raise ValueError(f"Candidate not found: {candidate_id}")

    policy = repository.get_active_policy()
    if policy is None:
        raise ValueError("Active clean policy not found")

    registry = repository.get_registry(policy.registry_snapshot_id)
    if registry is None:
        raise ValueError("Registry not found for active policy")

    engine = CleanPolicyEngine(policy=policy, registry=registry)
    validation = engine.validate_policy()
    if not validation.ok:
        raise ValueError(f"Invalid policy: {validation.blocking_reasons}")

    classified, violations = engine.evaluate_candidate(artifacts=artifacts, sources=sources)

    manifest = build_distribution_manifest(
        manifest_id=f"manifest-{candidate_id}",
        candidate_id=candidate_id,
        policy_id=policy.policy_id,
        generated_by=operator_id,
        artifacts=classified,
    )
    repository.save_manifest(manifest)

    # Note: In the new model, BLOCKED is a ComplianceDecision, not a CandidateStatus.
    # CandidateStatus.PREPARED is the correct next state after preparation.
    candidate.transition_to(CandidateStatus.PREPARED)
    repository.save_candidate(candidate)

    status_value = candidate.status.value if hasattr(candidate.status, "value") else str(candidate.status)
    manifest_id_value = getattr(manifest, "manifest_id", None) or getattr(manifest, "id", "")
    return {
        "candidate_id": candidate_id,
        "status": status_value,
        "manifest_id": manifest_id_value,
        "violations": violations,
        "prepared_at": datetime.now(timezone.utc).isoformat(),
    }


# [DEF:prepare_candidate_legacy:Function]
# @PURPOSE: Legacy compatibility wrapper kept for migration period.
# @PRE: Same as prepare_candidate.
# @POST: Delegates to canonical prepare_candidate and preserves response shape.
def prepare_candidate_legacy(
    repository: CleanReleaseRepository,
    candidate_id: str,
    artifacts: Iterable[Dict],
    sources: Iterable[str],
    operator_id: str,
) -> Dict:
    return prepare_candidate(
        repository=repository,
        candidate_id=candidate_id,
        artifacts=artifacts,
        sources=sources,
        operator_id=operator_id,
    )
# [/DEF:prepare_candidate_legacy:Function]
# [/DEF:backend.src.services.clean_release.preparation_service:Module]