"""Analysis repository for AI MusiMuse.

This module defines :class:`AnalysisRepository`, which manages the
lifecycle of :class:`~database.models.AnalyzerRun` records.

Repositories are the only layer allowed to access SQLAlchemy.
No logging is performed inside repositories.
"""

from __future__ import annotations

from datetime import UTC, datetime

from sqlalchemy import select
from sqlalchemy.orm import Session

from database.models import AnalyzerRun, generate_uuid


class AnalysisRepository:
    """Repository for :class:`AnalyzerRun` persistence operations.

    Methods:
        create_run: Create and persist a new AnalyzerRun.
        finish_run: Mark a run as completed with success.
        mark_failed: Mark a run as failed.
        list_runs: List all runs for a track.
        find_run: Find a run by track, analyzer name, and version.
    """

    @staticmethod
    def create_run(
        session: Session,
        track_id: str,
        analyzer_name: str,
        analyzer_version: str,
    ) -> AnalyzerRun:
        """Create and persist a new AnalyzerRun.

        Args:
            session: An active SQLAlchemy session.
            track_id: UUID of the track being analyzed.
            analyzer_name: Name of the analyzer.
            analyzer_version: Version of the analyzer.

        Returns:
            The newly created :class:`AnalyzerRun` instance.
        """
        run = AnalyzerRun(
            id=generate_uuid(),
            track_id=track_id,
            analyzer_name=analyzer_name,
            analyzer_version=analyzer_version,
            started_at=datetime.now(UTC),
        )
        session.add(run)
        session.flush()
        return run

    @staticmethod
    def finish_run(
        session: Session,
        run: AnalyzerRun,
        execution_time_ms: float,
        success: bool,
        warnings: tuple[str, ...] = (),
    ) -> None:
        """Mark a run as completed.

        Args:
            session: An active SQLAlchemy session.
            run: The AnalyzerRun to finish.
            execution_time_ms: Wall-clock execution time.
            success: Whether the analyzer succeeded.
            warnings: Tuple of warning messages.
        """
        run.finished_at = datetime.now(UTC)
        run.execution_time_ms = execution_time_ms
        run.success = success
        run.warnings = "\n".join(warnings) if warnings else None
        session.flush()

    @staticmethod
    def mark_failed(
        session: Session,
        run: AnalyzerRun,
        execution_time_ms: float,
        error: str,
    ) -> None:
        """Mark a run as failed.

        Args:
            session: An active SQLAlchemy session.
            run: The AnalyzerRun to mark as failed.
            execution_time_ms: Wall-clock execution time before failure.
            error: Error message describing the failure.
        """
        run.finished_at = datetime.now(UTC)
        run.execution_time_ms = execution_time_ms
        run.success = False
        run.warnings = error
        session.flush()

    @staticmethod
    def list_runs(session: Session, track_id: str) -> list[AnalyzerRun]:
        """List all analyzer runs for a track.

        Args:
            session: An active SQLAlchemy session.
            track_id: UUID of the track.

        Returns:
            A list of :class:`AnalyzerRun` instances ordered by
            ``started_at`` ascending.
        """
        stmt = (
            select(AnalyzerRun)
            .where(AnalyzerRun.track_id == track_id)
            .order_by(AnalyzerRun.started_at)
        )
        return list(session.execute(stmt).scalars().all())

    @staticmethod
    def find_run(
        session: Session,
        track_id: str,
        analyzer_name: str,
        analyzer_version: str,
    ) -> AnalyzerRun | None:
        """Find a run by track, analyzer name, and version.

        Args:
            session: An active SQLAlchemy session.
            track_id: UUID of the track.
            analyzer_name: Name of the analyzer.
            analyzer_version: Version of the analyzer.

        Returns:
            The matching :class:`AnalyzerRun`, or ``None`` if not found.
        """
        stmt = select(AnalyzerRun).where(
            AnalyzerRun.track_id == track_id,
            AnalyzerRun.analyzer_name == analyzer_name,
            AnalyzerRun.analyzer_version == analyzer_version,
        )
        return session.execute(stmt).scalar_one_or_none()
