"""Repository layer for AI MusiMuse.

This module provides :class:`ProjectRepository` and
:class:`TrackRepository`, which are the only components allowed to
communicate with SQLAlchemy.  Business logic must use repositories
rather than executing raw SQL.
"""

from __future__ import annotations

from sqlalchemy import select
from sqlalchemy.orm import Session

from database.models import Project, Track, TrackStatus, generate_uuid

DEFAULT_PROJECT_NAME = "Default Project"


class ProjectRepository:
    """Repository for :class:`Project` persistence operations.

    Methods:
        get_default_project: Retrieve or create the default project.
        create_default_project: Create a new default project.
    """

    @staticmethod
    def get_default_project(session: Session) -> Project:
        """Return the default project, creating it if it does not exist.

        Args:
            session: An active SQLAlchemy session.

        Returns:
            The default :class:`Project` instance.
        """
        stmt = select(Project).where(Project.name == DEFAULT_PROJECT_NAME)
        project = session.execute(stmt).scalar_one_or_none()
        if project is None:
            project = ProjectRepository.create_default_project(session)
        return project

    @staticmethod
    def create_default_project(session: Session) -> Project:
        """Create and persist a new default project.

        Args:
            session: An active SQLAlchemy session.

        Returns:
            The newly created :class:`Project` instance.
        """
        project = Project(
            id=generate_uuid(),
            name=DEFAULT_PROJECT_NAME,
        )
        session.add(project)
        session.flush()
        return project


class TrackRepository:
    """Repository for :class:`Track` persistence operations.

    Methods:
        create_track: Persist a new track.
        find_by_path: Find a track by its relative path.
        find_by_sha256: Find all tracks matching a SHA-256 hash.
        list_tracks: List all tracks for a project.
        mark_as_missing: Set a track's status to MISSING.
    """

    @staticmethod
    def create_track(
        session: Session,
        project_id: str,
        relative_path: str,
        original_filename: str,
        sha256: str,
        file_size: int,
    ) -> Track:
        """Create and persist a new track.

        Args:
            session: An active SQLAlchemy session.
            project_id: UUID of the parent project.
            relative_path: Path relative to the music directory.
            original_filename: The file's name without directory.
            sha256: SHA-256 hash (hex digest).
            file_size: File size in bytes.

        Returns:
            The newly created :class:`Track` instance.
        """
        track = Track(
            id=generate_uuid(),
            project_id=project_id,
            relative_path=relative_path,
            original_filename=original_filename,
            sha256=sha256,
            file_size=file_size,
            status=TrackStatus.ACTIVE,
        )
        session.add(track)
        session.flush()
        return track

    @staticmethod
    def find_by_path(session: Session, relative_path: str) -> Track | None:
        """Find a track by its relative path.

        Args:
            session: An active SQLAlchemy session.
            relative_path: The relative path to search for.

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

    @staticmethod
    def find_by_sha256(session: Session, sha256: str) -> list[Track]:
        """Find all tracks matching a SHA-256 hash.

        Args:
            session: An active SQLAlchemy session.
            sha256: The SHA-256 hash to search for.

        Returns:
            A list of :class:`Track` instances with the given hash.
            Multiple results indicate duplicate files.
        """
        stmt = select(Track).where(Track.sha256 == sha256)
        return list(session.execute(stmt).scalars().all())

    @staticmethod
    def list_tracks(session: Session, project_id: str) -> list[Track]:
        """List all tracks belonging to a project.

        Args:
            session: An active SQLAlchemy session.
            project_id: UUID of the project.

        Returns:
            A list of :class:`Track` instances.
        """
        stmt = select(Track).where(Track.project_id == project_id)
        return list(session.execute(stmt).scalars().all())

    @staticmethod
    def mark_as_missing(session: Session, track: Track) -> None:
        """Set a track's status to MISSING.

        Args:
            session: An active SQLAlchemy session.
            track: The track to mark as missing.
        """
        track.status = TrackStatus.MISSING
        session.flush()


class DecoderRepository:
    """Repository for decoder metadata operations.

    Methods:
        update_metadata: Update a track with decoded audio metadata.
        list_tracks_without_metadata: List active tracks lacking metadata.
    """

    @staticmethod
    def update_metadata(
        session: Session,
        track: Track,
        duration: float,
        sample_rate: int,
        channels: int,
        bit_depth: int | None,
    ) -> None:
        """Update a track with decoded audio metadata.

        Only metadata fields are modified.  SHA-256, path, and filename
        are never changed.

        Args:
            session: An active SQLAlchemy session.
            track: The track to update.
            duration: Duration in seconds.
            sample_rate: Sample rate in Hz.
            channels: Number of audio channels.
            bit_depth: Bit depth, or ``None`` if unknown.
        """
        track.duration = duration
        track.sample_rate = sample_rate
        track.channels = channels
        track.bit_depth = bit_depth
        session.flush()

    @staticmethod
    def list_tracks_without_metadata(
        session: Session,
        project_id: str,
    ) -> list[Track]:
        """List active tracks that have not yet been decoded.

        Args:
            session: An active SQLAlchemy session.
            project_id: UUID of the project.

        Returns:
            A list of :class:`Track` instances with ``duration IS NULL``
            and ``status == ACTIVE``.
        """
        stmt = select(Track).where(
            Track.project_id == project_id,
            Track.duration.is_(None),
            Track.status == TrackStatus.ACTIVE,
        )
        return list(session.execute(stmt).scalars().all())
