# #region PreparationService [C:5] [TYPE Module] [SEMANTICS clean-release, prepare, validate, policy, manifest]
# @BRIEF Prepare release candidate by policy evaluation and deterministic manifest creation.
# @LAYER Domain
# @RELATION DEPENDS_ON -> [PolicyEngine]
# @RELATION DEPENDS_ON -> [ManifestBuilder]
# @RELATION DEPENDS_ON -> [RepositoryRelations]
# @INVARIANT  Candidate preparation always persists manifest and candidate status deterministically.
# @SIDE_EFFECT  Persists candidate and manifest
# @DATA_CONTRACT  PrepareRequest -> PrepareResult

from __future__ import annotations

from collections.abc import Iterable
from datetime import UTC, datetime

from ...models.clean_release import ReleaseCandidateStatus
from .enums import CandidateStatus
from .manifest_builder import build_distribution_manifest
from .policy_engine import CleanPolicyEngine
from .repository import CleanReleaseRepository


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_ref = getattr(policy, "registry_snapshot_id", None) or getattr(
        policy, "internal_source_registry_ref", None
    )
    registry = repository.get_registry(registry_ref) if registry_ref else None
    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=getattr(policy, "policy_id", None) or getattr(policy, "id", ""),
        generated_by=operator_id,
        artifacts=classified,
    )
    repository.save_manifest(manifest)

    current_status = getattr(candidate, "status", None)
    if violations:
        candidate.status = ReleaseCandidateStatus.BLOCKED.value
        repository.save_candidate(candidate)
        response_status = ReleaseCandidateStatus.BLOCKED.value
    else:
        if current_status in {
            CandidateStatus.DRAFT,
            CandidateStatus.DRAFT.value,
            "DRAFT",
        }:
            candidate.transition_to(CandidateStatus.PREPARED)
        else:
            candidate.status = ReleaseCandidateStatus.PREPARED.value
        repository.save_candidate(candidate)
        response_status = ReleaseCandidateStatus.PREPARED.value

    manifest_id_value = getattr(manifest, "manifest_id", None) or getattr(
        manifest, "id", ""
    )
    return {
        "candidate_id": candidate_id,
        "status": response_status,
        "manifest_id": manifest_id_value,
        "violations": violations,
        "prepared_at": datetime.now(UTC).isoformat(),
    }


# #region prepare_candidate_legacy [TYPE Function]
# @BRIEF 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,
    )


# #endregion prepare_candidate_legacy
# #endregion PreparationService
