# #region ComplianceExecutionService [C:5] [TYPE Module] [SEMANTICS clean-release, execution, compliance, report, stage]
# @BRIEF Create and execute compliance runs with trusted snapshots, deterministic stages, violations and immutable report persistence.
# @LAYER Domain
# @RELATION DEPENDS_ON -> RepositoryRelations
# @RELATION DEPENDS_ON -> PolicyResolutionService
# @RELATION DEPENDS_ON -> ComplianceStages
# @RELATION DEPENDS_ON -> ReportBuilder
# @PRE  Repository adapters, trusted policy resolution, and deterministic stage implementations are available for the run request.
# @POST  Candidate-scoped compliance runs persist stage evidence, terminal status, and immutable report artifacts when execution succeeds.
# @SIDE_EFFECT  Persists runs, stage results, violations, and reports through repository adapters and audit helpers.
# @DATA_CONTRACT  Input[candidate_id, requested_by, manifest_id?, policy snapshots, stage results] -> Output[ComplianceExecutionResult]
# @INVARIANT  A run binds to exactly one candidate/manifest/policy/registry snapshot set.

from __future__ import annotations

from collections.abc import Iterable
from dataclasses import dataclass
from datetime import UTC, datetime
from typing import Any, cast
from uuid import uuid4

from ...core.logger import belief_scope, logger
from ...models.clean_release import (
    ComplianceReport,
    ComplianceRun,
    ComplianceStageRun,
    ComplianceViolation,
    DistributionManifest,
)
from .enums import ComplianceDecision, RunStatus
from .exceptions import ComplianceRunError, PolicyResolutionError
from .policy_resolution_service import resolve_trusted_policy_snapshots
from .report_builder import ComplianceReportBuilder
from .repository import CleanReleaseRepository
from .stages import build_default_stages, derive_final_status
from .stages.base import ComplianceStage, ComplianceStageContext, build_stage_run_record

belief_logger = cast(Any, logger)


# #region ComplianceExecutionResult [TYPE Class]
# @BRIEF Return envelope for compliance execution with run/report and persisted stage artifacts.
@dataclass
class ComplianceExecutionResult:
    run: ComplianceRun
    report: ComplianceReport | None
    stage_runs: list[ComplianceStageRun]
    violations: list[ComplianceViolation]


# #endregion ComplianceExecutionResult


# #region ComplianceExecutionService [TYPE Class]
# @BRIEF Execute clean-release compliance lifecycle over trusted snapshots and immutable evidence.
# @PRE  Database session active, candidate registered
# @POST  Returns ComplianceReport with pass/fail status and violation details
# @SIDE_EFFECT  Updates compliance status in database, logs violations
# @DATA_CONTRACT  ComplianceCheckResult, ComplianceReport, Violation
class ComplianceExecutionService:
    TASK_PLUGIN_ID = "clean-release-compliance"

    def __init__(
        self,
        *,
        repository: CleanReleaseRepository,
        config_manager,
        stages: Iterable[ComplianceStage] | None = None,
    ):
        self.repository = repository
        self.config_manager = config_manager
        self.stages = list(stages) if stages is not None else build_default_stages()
        self.report_builder = ComplianceReportBuilder(repository)

    # region _resolve_manifest [TYPE Function]
    # @PURPOSE: Resolve explicit manifest or fallback to latest candidate manifest.
    # @PRE  candidate exists.
    # @POST  Returns manifest snapshot or raises ComplianceRunError.
    def _resolve_manifest(
        self, candidate_id: str, manifest_id: str | None
    ) -> DistributionManifest:
        with belief_scope("ComplianceExecutionService._resolve_manifest"):
            manifest_id_value = cast(str | None, manifest_id)
            if manifest_id_value is not None and manifest_id_value != "":
                manifest = self.repository.get_manifest(manifest_id_value)
                if manifest is None:
                    raise ComplianceRunError(
                        f"manifest '{manifest_id_value}' not found"
                    )
                if str(manifest.candidate_id) != candidate_id:
                    raise ComplianceRunError("manifest does not belong to candidate")
                return manifest

            manifests = self.repository.get_manifests_by_candidate(candidate_id)
            if not manifests:
                raise ComplianceRunError(f"candidate '{candidate_id}' has no manifest")
            return sorted(
                manifests, key=lambda item: item.manifest_version, reverse=True
            )[0]

    # endregion _resolve_manifest

    # region _persist_stage_run [TYPE Function]
    # @PURPOSE: Persist stage run if repository supports stage records.
    # @POST  Stage run is persisted when adapter is available, otherwise no-op.
    def _persist_stage_run(self, stage_run: ComplianceStageRun) -> None:
        self.repository.save_stage_run(stage_run)

    # endregion _persist_stage_run

    # region _persist_violations [TYPE Function]
    # @PURPOSE: Persist stage violations via repository adapters.
    # @POST  Violations are appended to repository evidence store.
    def _persist_violations(self, violations: list[ComplianceViolation]) -> None:
        for violation in violations:
            self.repository.save_violation(violation)

    # endregion _persist_violations

    # region execute_run [TYPE Function]
    # @PURPOSE: Execute compliance run stages and finalize immutable report on terminal success.
    # @PRE  candidate exists and trusted policy/registry snapshots are resolvable.
    # @POST  Run and evidence are persisted; report exists for SUCCEEDED runs.
    def execute_run(
        self,
        *,
        candidate_id: str,
        requested_by: str,
        manifest_id: str | None = None,
    ) -> ComplianceExecutionResult:
        with belief_scope("ComplianceExecutionService.execute_run"):
            belief_logger.reason(
                f"Starting compliance execution candidate_id={candidate_id}"
            )

            candidate = self.repository.get_candidate(candidate_id)
            if candidate is None:
                raise ComplianceRunError(f"candidate '{candidate_id}' not found")

            manifest = self._resolve_manifest(candidate_id, manifest_id)

            try:
                policy_snapshot, registry_snapshot = resolve_trusted_policy_snapshots(
                    config_manager=self.config_manager,
                    repository=self.repository,
                )
            except PolicyResolutionError as exc:
                raise ComplianceRunError(str(exc)) from exc

            run = ComplianceRun(
                id=f"run-{uuid4()}",
                candidate_id=candidate_id,
                manifest_id=str(manifest.id),
                manifest_digest=str(manifest.manifest_digest),
                policy_snapshot_id=str(policy_snapshot.id),
                registry_snapshot_id=str(registry_snapshot.id),
                requested_by=requested_by,
                requested_at=datetime.now(UTC),
                started_at=datetime.now(UTC),
                status=RunStatus.RUNNING.value,
            )
            self.repository.save_check_run(run)

            stage_runs: list[ComplianceStageRun] = []
            violations: list[ComplianceViolation] = []
            report: ComplianceReport | None = None

            context = ComplianceStageContext(
                run=run,
                candidate=candidate,
                manifest=manifest,
                policy=policy_snapshot,
                registry=registry_snapshot,
            )

            try:
                for stage in self.stages:
                    started = datetime.now(UTC)
                    result = stage.execute(context)
                    finished = datetime.now(UTC)

                    stage_run = build_stage_run_record(
                        run_id=str(run.id),
                        stage_name=stage.stage_name,
                        result=result,
                        started_at=started,
                        finished_at=finished,
                    )
                    self._persist_stage_run(stage_run)
                    stage_runs.append(stage_run)

                    if result.violations:
                        self._persist_violations(result.violations)
                        violations.extend(result.violations)

                run.final_status = derive_final_status(stage_runs).value
                run.status = RunStatus.SUCCEEDED.value
                run.finished_at = datetime.now(UTC)
                self.repository.save_check_run(run)

                report = self.report_builder.build_report_payload(run, violations)
                report = self.report_builder.persist_report(report)
                run.report_id = str(report.id)
                self.repository.save_check_run(run)
                belief_logger.reflect(
                    f"Compliance run completed run_id={run.id} final_status={getattr(run, 'final_status', None)}"
                )
            except Exception as exc:
                run.status = RunStatus.FAILED.value
                run.final_status = ComplianceDecision.ERROR.value
                run.failure_reason = str(exc)
                run.finished_at = datetime.now(UTC)
                self.repository.save_check_run(run)
                belief_logger.explore(
                    f"Compliance run failed run_id={run.id}: {exc}"
                )

            return ComplianceExecutionResult(
                run=run,
                report=report,
                stage_runs=stage_runs,
                violations=violations,
            )

    # endregion execute_run


# #endregion ComplianceExecutionService

# #endregion ComplianceExecutionService
