"""SQLAlchemy implementation of MeetingRepository."""

from collections.abc import Sequence
from datetime import datetime
from uuid import UUID

from sqlalchemy import func, select

from noteflow.config.constants import ERROR_MSG_MEETING_PREFIX
from noteflow.domain.entities import Meeting
from noteflow.domain.value_objects import MeetingId, MeetingState
from noteflow.infrastructure.converters import OrmConverter
from noteflow.infrastructure.persistence.models import MeetingModel
from noteflow.infrastructure.persistence.repositories._base import BaseRepository


class SqlAlchemyMeetingRepository(BaseRepository):
    """SQLAlchemy implementation of MeetingRepository."""

    async def create(self, meeting: Meeting) -> Meeting:
        """Persist a new meeting.

        Args:
            meeting: Meeting to create.

        Returns:
            Created meeting.
        """
        model = MeetingModel(
            id=UUID(str(meeting.id)),
            title=meeting.title,
            project_id=meeting.project_id,
            state=int(meeting.state),
            created_at=meeting.created_at,
            started_at=meeting.started_at,
            ended_at=meeting.ended_at,
            metadata_=meeting.metadata,
            wrapped_dek=meeting.wrapped_dek,
            asset_path=meeting.asset_path,
            version=meeting.version,
        )
        self._session.add(model)
        await self._session.flush()
        return meeting

    async def get(self, meeting_id: MeetingId) -> Meeting | None:
        """Retrieve a meeting by ID.

        Args:
            meeting_id: Meeting identifier.

        Returns:
            Meeting if found, None otherwise.
        """
        stmt = select(MeetingModel).where(MeetingModel.id == UUID(str(meeting_id)))
        model = await self._execute_scalar(stmt)

        return None if model is None else OrmConverter.meeting_to_domain(model)

    async def update(self, meeting: Meeting) -> Meeting:
        """Update an existing meeting.

        Args:
            meeting: Meeting with updated fields.

        Returns:
            Updated meeting.

        Raises:
            ValueError: If meeting does not exist.
        """
        stmt = select(MeetingModel).where(MeetingModel.id == UUID(str(meeting.id)))
        model = await self._execute_scalar(stmt)

        if model is None:
            raise ValueError(f"{ERROR_MSG_MEETING_PREFIX}{meeting.id} not found")

        if model.version != meeting.version:
            raise ValueError(f"{ERROR_MSG_MEETING_PREFIX}{meeting.id} has been modified concurrently")

        model.title = meeting.title
        model.project_id = meeting.project_id
        model.state = int(meeting.state)
        model.started_at = meeting.started_at
        model.ended_at = meeting.ended_at
        # ORM expects dict[str, object]; domain uses dict[str, str] (subtype)
        model.metadata_ = dict(meeting.metadata)
        model.wrapped_dek = meeting.wrapped_dek
        model.asset_path = meeting.asset_path
        model.version += 1
        meeting.version = model.version

        await self._session.flush()
        return meeting

    async def delete(self, meeting_id: MeetingId) -> bool:
        """Delete a meeting and all associated data.

        Args:
            meeting_id: Meeting identifier.

        Returns:
            True if deleted, False if not found.
        """
        stmt = select(MeetingModel).where(MeetingModel.id == UUID(str(meeting_id)))
        model = await self._execute_scalar(stmt)

        if model is None:
            return False

        await self._delete_and_flush(model)
        return True

    async def list_all(
        self,
        states: list[MeetingState] | None = None,
        limit: int = 100,
        offset: int = 0,
        sort_desc: bool = True,
        project_id: UUID | None = None,
    ) -> tuple[Sequence[Meeting], int]:
        """List meetings with optional filtering.

        Args:
            states: Optional list of states to filter by.
            limit: Maximum number of meetings to return.
            offset: Number of meetings to skip.
            sort_desc: Sort by created_at descending if True.

        Returns:
            Tuple of (meetings list, total count matching filter).
        """
        # Build base query
        stmt = select(MeetingModel)

        # Filter by states
        if states:
            state_values = [int(s) for s in states]
            stmt = stmt.where(MeetingModel.state.in_(state_values))

        # Filter by project if requested
        if project_id is not None:
            stmt = stmt.where(MeetingModel.project_id == project_id)

        # Count total
        count_stmt = select(func.count()).select_from(stmt.subquery())
        total_result = await self._session.execute(count_stmt)
        total = total_result.scalar() or 0

        # Sort and paginate
        order_col = MeetingModel.created_at.desc() if sort_desc else MeetingModel.created_at.asc()
        stmt = stmt.order_by(order_col).offset(offset).limit(limit)

        result = await self._session.execute(stmt)
        models = result.scalars().all()

        meetings = [OrmConverter.meeting_to_domain(m) for m in models]
        return meetings, total

    async def count_by_state(self, state: MeetingState) -> int:
        """Count meetings in a specific state.

        Args:
            state: Meeting state to count.

        Returns:
            Number of meetings in the specified state.
        """
        stmt = (
            select(func.count()).select_from(MeetingModel).where(MeetingModel.state == int(state))
        )
        result = await self._session.execute(stmt)
        return result.scalar() or 0

    async def find_older_than(self, cutoff: datetime) -> Sequence[Meeting]:
        """Find completed meetings older than cutoff date.

        Args:
            cutoff: Cutoff datetime; meetings ended before this are returned.

        Returns:
            Sequence of meetings with ended_at before cutoff.
        """
        # Only consider completed meetings (have ended_at set)
        stmt = (
            select(MeetingModel)
            .where(MeetingModel.ended_at.isnot(None))
            .where(MeetingModel.ended_at < cutoff)
            .order_by(MeetingModel.ended_at.asc())
        )
        result = await self._session.execute(stmt)
        models = result.scalars().all()
        return [OrmConverter.meeting_to_domain(m) for m in models]
