"""SQLAlchemy implementation of Unit of Work pattern."""

from __future__ import annotations

from collections.abc import Callable
from pathlib import Path
from typing import Self, cast

from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker

from noteflow.config.settings import Settings
from noteflow.domain.ports.repositories import PreferencesRepository, UsageEventRepository
from noteflow.infrastructure.persistence.database import (
    create_async_engine,
    get_async_session_factory,
)

from .repositories import (
    FileSystemAssetRepository,
    SqlAlchemyAnnotationRepository,
    SqlAlchemyDiarizationJobRepository,
    SqlAlchemyEntityRepository,
    SqlAlchemyIntegrationRepository,
    SqlAlchemyMeetingRepository,
    SqlAlchemyPreferencesRepository,
    SqlAlchemyProjectMembershipRepository,
    SqlAlchemyProjectRepository,
    SqlAlchemySegmentRepository,
    SqlAlchemySummaryRepository,
    SqlAlchemyUsageEventRepository,
    SqlAlchemyUserRepository,
    SqlAlchemyWebhookRepository,
    SqlAlchemyWorkspaceRepository,
)


def create_uow_from_settings(settings: Settings) -> SqlAlchemyUnitOfWork:
    """Create a unit of work from application settings.

    Builds an async engine and session factory using configured database
    settings (URL, pool size, echo), then returns a new unit of work
    instance bound to that factory.
    """
    engine = create_async_engine(settings)
    session_factory = get_async_session_factory(engine)
    return SqlAlchemyUnitOfWork(session_factory, settings.meetings_dir)


def create_uow_factory(settings: Settings) -> Callable[[], SqlAlchemyUnitOfWork]:
    """Create a reusable factory that yields fresh UoW instances.

    The factory reuses a shared async session factory (and engine) while
    returning a new `SqlAlchemyUnitOfWork` object each time. Useful when
    callers need independent UoW instances for sequential operations
    (e.g., retention cleanup) to avoid re-entrancy issues.
    """
    engine = create_async_engine(settings)
    session_factory = get_async_session_factory(engine)

    def _factory() -> SqlAlchemyUnitOfWork:
        return SqlAlchemyUnitOfWork(session_factory, settings.meetings_dir)

    return _factory


class SqlAlchemyUnitOfWork:
    """SQLAlchemy implementation of Unit of Work.

    Provides transactional consistency across repositories.
    Use as an async context manager for automatic commit/rollback.

    Example:
        async with SqlAlchemyUnitOfWork(session_factory, meetings_dir) as uow:
            meeting = await uow.meetings.get(meeting_id)
            await uow.segments.add(meeting_id, segment)
            await uow.commit()
    """

    def __init__(
        self,
        session_factory: async_sessionmaker[AsyncSession],
        meetings_dir: Path,
    ) -> None:
        """Initialize unit of work with session factory and meetings directory."""
        self._session_factory = session_factory
        self._meetings_dir = meetings_dir
        self._session: AsyncSession | None = None
        self._assets_repo: FileSystemAssetRepository | None = None
        self._annotations_repo: SqlAlchemyAnnotationRepository | None = None
        self._diarization_jobs_repo: SqlAlchemyDiarizationJobRepository | None = None
        self._entities_repo: SqlAlchemyEntityRepository | None = None
        self._integrations_repo: SqlAlchemyIntegrationRepository | None = None
        self._meetings_repo: SqlAlchemyMeetingRepository | None = None
        self._preferences_repo: SqlAlchemyPreferencesRepository | None = None
        self._project_memberships_repo: SqlAlchemyProjectMembershipRepository | None = None
        self._projects_repo: SqlAlchemyProjectRepository | None = None
        self._segments_repo: SqlAlchemySegmentRepository | None = None
        self._summaries_repo: SqlAlchemySummaryRepository | None = None
        self._usage_events_repo: SqlAlchemyUsageEventRepository | None = None
        self._users_repo: SqlAlchemyUserRepository | None = None
        self._webhooks_repo: SqlAlchemyWebhookRepository | None = None
        self._workspaces_repo: SqlAlchemyWorkspaceRepository | None = None

    @property
    def assets(self) -> FileSystemAssetRepository:
        """Get assets repository."""
        if self._assets_repo is None:
            raise RuntimeError("UnitOfWork not in context")
        return self._assets_repo

    @property
    def annotations(self) -> SqlAlchemyAnnotationRepository:
        """Get annotations repository."""
        if self._annotations_repo is None:
            raise RuntimeError("UnitOfWork not in context")
        return self._annotations_repo

    @property
    def diarization_jobs(self) -> SqlAlchemyDiarizationJobRepository:
        """Get diarization jobs repository."""
        if self._diarization_jobs_repo is None:
            raise RuntimeError("UnitOfWork not in context")
        return self._diarization_jobs_repo

    @property
    def entities(self) -> SqlAlchemyEntityRepository:
        """Get entities repository for NER results."""
        if self._entities_repo is None:
            raise RuntimeError("UnitOfWork not in context")
        return self._entities_repo

    @property
    def integrations(self) -> SqlAlchemyIntegrationRepository:
        """Get integrations repository for OAuth connections."""
        if self._integrations_repo is None:
            raise RuntimeError("UnitOfWork not in context")
        return self._integrations_repo

    @property
    def meetings(self) -> SqlAlchemyMeetingRepository:
        """Get meetings repository."""
        if self._meetings_repo is None:
            raise RuntimeError("UnitOfWork not in context")
        return self._meetings_repo

    @property
    def segments(self) -> SqlAlchemySegmentRepository:
        """Get segments repository."""
        if self._segments_repo is None:
            raise RuntimeError("UnitOfWork not in context")
        return self._segments_repo

    @property
    def preferences(self) -> PreferencesRepository:
        """Get preferences repository."""
        if self._preferences_repo is None:
            raise RuntimeError("UnitOfWork not in context")
        return cast(PreferencesRepository, self._preferences_repo)

    @property
    def summaries(self) -> SqlAlchemySummaryRepository:
        """Get summaries repository."""
        if self._summaries_repo is None:
            raise RuntimeError("UnitOfWork not in context")
        return self._summaries_repo

    @property
    def webhooks(self) -> SqlAlchemyWebhookRepository:
        """Get webhooks repository for event notifications."""
        if self._webhooks_repo is None:
            raise RuntimeError("UnitOfWork not in context")
        return self._webhooks_repo

    @property
    def usage_events(self) -> UsageEventRepository:
        """Get usage events repository for analytics."""
        if self._usage_events_repo is None:
            raise RuntimeError("UnitOfWork not in context")
        return self._usage_events_repo

    @property
    def users(self) -> SqlAlchemyUserRepository:
        """Get users repository for identity management."""
        if self._users_repo is None:
            raise RuntimeError("UnitOfWork not in context")
        return self._users_repo

    @property
    def workspaces(self) -> SqlAlchemyWorkspaceRepository:
        """Get workspaces repository for tenancy management."""
        if self._workspaces_repo is None:
            raise RuntimeError("UnitOfWork not in context")
        return self._workspaces_repo

    @property
    def projects(self) -> SqlAlchemyProjectRepository:
        """Get projects repository for project management."""
        if self._projects_repo is None:
            raise RuntimeError("UnitOfWork not in context")
        return self._projects_repo

    @property
    def project_memberships(self) -> SqlAlchemyProjectMembershipRepository:
        """Get project memberships repository for access control."""
        if self._project_memberships_repo is None:
            raise RuntimeError("UnitOfWork not in context")
        return self._project_memberships_repo

    # Feature capability flags - all True for database-backed implementation
    supports_annotations: bool = True
    supports_diarization_jobs: bool = True
    supports_preferences: bool = True
    supports_entities: bool = True
    supports_integrations: bool = True
    supports_webhooks: bool = True
    supports_usage_events: bool = True
    supports_users: bool = True
    supports_workspaces: bool = True
    supports_projects: bool = True

    async def __aenter__(self) -> Self:
        """Enter the unit of work context.

        Creates session and caches repository instances.

        Returns:
            Self for use in async with statement.
        """
        self._session = self._session_factory()
        self._assets_repo = FileSystemAssetRepository(self._meetings_dir)
        self._annotations_repo = SqlAlchemyAnnotationRepository(self._session)
        self._diarization_jobs_repo = SqlAlchemyDiarizationJobRepository(self._session)
        self._entities_repo = SqlAlchemyEntityRepository(self._session)
        self._integrations_repo = SqlAlchemyIntegrationRepository(self._session)
        self._meetings_repo = SqlAlchemyMeetingRepository(self._session)
        self._preferences_repo = SqlAlchemyPreferencesRepository(self._session)
        self._project_memberships_repo = SqlAlchemyProjectMembershipRepository(self._session)
        self._projects_repo = SqlAlchemyProjectRepository(self._session)
        self._segments_repo = SqlAlchemySegmentRepository(self._session)
        self._summaries_repo = SqlAlchemySummaryRepository(self._session)
        self._usage_events_repo = SqlAlchemyUsageEventRepository(self._session)
        self._users_repo = SqlAlchemyUserRepository(self._session)
        self._webhooks_repo = SqlAlchemyWebhookRepository(self._session)
        self._workspaces_repo = SqlAlchemyWorkspaceRepository(self._session)
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: object,
    ) -> None:
        """Exit the unit of work context.

        Rolls back on exception, otherwise does nothing (explicit commit required).

        Args:
            exc_type: Exception type if raised.
            exc_val: Exception value if raised.
            exc_tb: Exception traceback if raised.
        """
        self._assets_repo = None

        if self._session is None:
            return

        if exc_type is not None:
            await self.rollback()

        await self._session.close()
        self._session = None
        self._annotations_repo = None
        self._diarization_jobs_repo = None
        self._entities_repo = None
        self._integrations_repo = None
        self._meetings_repo = None
        self._preferences_repo = None
        self._project_memberships_repo = None
        self._projects_repo = None
        self._segments_repo = None
        self._summaries_repo = None
        self._usage_events_repo = None
        self._users_repo = None
        self._webhooks_repo = None
        self._workspaces_repo = None

    async def commit(self) -> None:
        """Commit the current transaction."""
        if self._session is None:
            raise RuntimeError("UnitOfWork not in context")
        await self._session.commit()

    async def rollback(self) -> None:
        """Rollback the current transaction."""
        if self._session is None:
            raise RuntimeError("UnitOfWork not in context")
        await self._session.rollback()
