# [DEF:backend.src.services.clean_release.candidate_service:Module]
# @TIER: CRITICAL
# @SEMANTICS: clean-release, candidate, artifacts, lifecycle, validation
# @PURPOSE: Register release candidates with validated artifacts and advance lifecycle through legal transitions.
# @LAYER: Domain
# @RELATION: DEPENDS_ON -> backend.src.services.clean_release.repository
# @RELATION: DEPENDS_ON -> backend.src.models.clean_release
# @PRE: candidate_id must be unique; artifacts input must be non-empty and valid.
# @POST: candidate and artifacts are persisted; candidate transitions DRAFT -> PREPARED only.
# @INVARIANT: Candidate lifecycle transitions are delegated to domain guard logic.

from __future__ import annotations

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

from ...models.clean_release import CandidateArtifact, ReleaseCandidate
from .enums import CandidateStatus
from .repository import CleanReleaseRepository


# [DEF:_validate_artifacts:Function]
# @PURPOSE: Validate raw artifact payload list for required fields and shape.
# @PRE: artifacts payload is provided by caller.
# @POST: Returns normalized artifact list or raises ValueError.
def _validate_artifacts(artifacts: Iterable[Dict[str, Any]]) -> List[Dict[str, Any]]:
    normalized = list(artifacts)
    if not normalized:
        raise ValueError("artifacts must not be empty")

    required_fields = ("id", "path", "sha256", "size")
    for index, artifact in enumerate(normalized):
        if not isinstance(artifact, dict):
            raise ValueError(f"artifact[{index}] must be an object")
        for field in required_fields:
            if field not in artifact:
                raise ValueError(f"artifact[{index}] missing required field '{field}'")
        if not str(artifact["id"]).strip():
            raise ValueError(f"artifact[{index}] field 'id' must be non-empty")
        if not str(artifact["path"]).strip():
            raise ValueError(f"artifact[{index}] field 'path' must be non-empty")
        if not str(artifact["sha256"]).strip():
            raise ValueError(f"artifact[{index}] field 'sha256' must be non-empty")
        if not isinstance(artifact["size"], int) or artifact["size"] <= 0:
            raise ValueError(f"artifact[{index}] field 'size' must be a positive integer")
    return normalized
# [/DEF:_validate_artifacts:Function]


# [DEF:register_candidate:Function]
# @PURPOSE: Register a candidate and persist its artifacts with legal lifecycle transition.
# @PRE: candidate_id must be unique and artifacts must pass validation.
# @POST: Candidate exists in repository with PREPARED status and artifacts persisted.
def register_candidate(
    repository: CleanReleaseRepository,
    candidate_id: str,
    version: str,
    source_snapshot_ref: str,
    created_by: str,
    artifacts: Iterable[Dict[str, Any]],
) -> ReleaseCandidate:
    if not candidate_id or not candidate_id.strip():
        raise ValueError("candidate_id must be non-empty")
    if not version or not version.strip():
        raise ValueError("version must be non-empty")
    if not source_snapshot_ref or not source_snapshot_ref.strip():
        raise ValueError("source_snapshot_ref must be non-empty")
    if not created_by or not created_by.strip():
        raise ValueError("created_by must be non-empty")

    existing = repository.get_candidate(candidate_id)
    if existing is not None:
        raise ValueError(f"candidate '{candidate_id}' already exists")

    validated_artifacts = _validate_artifacts(artifacts)

    candidate = ReleaseCandidate(
        id=candidate_id,
        version=version,
        source_snapshot_ref=source_snapshot_ref,
        created_by=created_by,
        created_at=datetime.now(timezone.utc),
        status=CandidateStatus.DRAFT.value,
    )
    repository.save_candidate(candidate)

    for artifact_payload in validated_artifacts:
        artifact = CandidateArtifact(
            id=str(artifact_payload["id"]),
            candidate_id=candidate_id,
            path=str(artifact_payload["path"]),
            sha256=str(artifact_payload["sha256"]),
            size=int(artifact_payload["size"]),
            detected_category=artifact_payload.get("detected_category"),
            declared_category=artifact_payload.get("declared_category"),
            source_uri=artifact_payload.get("source_uri"),
            source_host=artifact_payload.get("source_host"),
            metadata_json=artifact_payload.get("metadata_json", {}),
        )
        repository.save_artifact(artifact)

    candidate.transition_to(CandidateStatus.PREPARED)
    repository.save_candidate(candidate)
    return candidate
# [/DEF:register_candidate:Function]

# [/DEF:backend.src.services.clean_release.candidate_service:Module]