"""SQLAlchemy implementation of entity repository for named entities."""

from __future__ import annotations

from collections.abc import Sequence
from typing import TYPE_CHECKING
from uuid import UUID

from sqlalchemy import delete, select

from noteflow.domain.entities.named_entity import EntityCategory, NamedEntity
from noteflow.infrastructure.converters.ner_converters import NerConverter
from noteflow.infrastructure.persistence.models import NamedEntityModel
from noteflow.infrastructure.persistence.repositories._base import (
    BaseRepository,
    DeleteByIdMixin,
    GetByIdMixin,
)

if TYPE_CHECKING:
    from noteflow.domain.value_objects import MeetingId


class SqlAlchemyEntityRepository(
    BaseRepository,
    GetByIdMixin[NamedEntityModel, NamedEntity],
    DeleteByIdMixin[NamedEntityModel],
):
    """SQLAlchemy implementation of entity repository for NER results.

    Uses mixins for standardized get and delete operations.
    """

    _model_class: type[NamedEntityModel] = NamedEntityModel

    def _to_domain(self, model: NamedEntityModel) -> NamedEntity:
        """Convert ORM model to domain entity."""
        return NerConverter.orm_to_domain(model)

    async def save(self, entity: NamedEntity) -> NamedEntity:
        """Save or update a named entity.

        Uses merge to handle both insert and update cases.

        Args:
            entity: The entity to save.

        Returns:
            Saved entity with db_id populated.
        """
        kwargs = NerConverter.to_orm_kwargs(entity)
        model = NamedEntityModel(**kwargs)
        merged = await self._session.merge(model)
        await self._session.flush()
        entity.db_id = merged.id
        return entity

    async def save_batch(self, entities: Sequence[NamedEntity]) -> Sequence[NamedEntity]:
        """Save multiple entities efficiently.

        Uses individual merges to handle upsert semantics with the unique
        constraint on (meeting_id, normalized_text).

        Args:
            entities: List of entities to save.

        Returns:
            Saved entities with db_ids populated.
        """
        for entity in entities:
            kwargs = NerConverter.to_orm_kwargs(entity)
            model = NamedEntityModel(**kwargs)
            merged = await self._session.merge(model)
            entity.db_id = merged.id

        await self._session.flush()
        return entities

    async def get(self, entity_id: UUID) -> NamedEntity | None:
        """Get entity by ID.

        Args:
            entity_id: The entity UUID.

        Returns:
            Entity if found, None otherwise.
        """
        return await self._mixin_get_by_id(entity_id)

    async def get_by_meeting(self, meeting_id: MeetingId) -> Sequence[NamedEntity]:
        """Get all entities for a meeting.

        Args:
            meeting_id: The meeting UUID.

        Returns:
            List of entities ordered by category and text.
        """
        stmt = (
            select(NamedEntityModel)
            .where(NamedEntityModel.meeting_id == UUID(str(meeting_id)))
            .order_by(NamedEntityModel.category, NamedEntityModel.text)
        )
        models = await self._execute_scalars(stmt)
        return [NerConverter.orm_to_domain(m) for m in models]

    async def delete_by_meeting(self, meeting_id: MeetingId) -> int:
        """Delete all entities for a meeting.

        Args:
            meeting_id: The meeting UUID.

        Returns:
            Number of deleted entities.
        """
        stmt = delete(NamedEntityModel).where(
            NamedEntityModel.meeting_id == UUID(str(meeting_id))
        )
        return await self._execute_delete(stmt)

    async def update_pinned(self, entity_id: UUID, is_pinned: bool) -> bool:
        """Update the pinned status of an entity.

        Args:
            entity_id: The entity UUID.
            is_pinned: New pinned status.

        Returns:
            True if entity was found and updated.
        """
        stmt = select(NamedEntityModel).where(NamedEntityModel.id == entity_id)
        model = await self._execute_scalar(stmt)

        if model is None:
            return False

        model.is_pinned = is_pinned
        await self._session.flush()
        return True

    async def exists_for_meeting(self, meeting_id: MeetingId) -> bool:
        """Check if any entities exist for a meeting.

        More efficient than fetching all entities when only checking existence.

        Args:
            meeting_id: The meeting UUID.

        Returns:
            True if at least one entity exists.
        """
        stmt = select(NamedEntityModel).where(
            NamedEntityModel.meeting_id == UUID(str(meeting_id))
        )
        return await self._execute_exists(stmt)

    async def update(
        self,
        entity_id: UUID,
        text: str | None = None,
        category: str | None = None,
    ) -> NamedEntity | None:
        """Update an existing entity.

        Args:
            entity_id: Entity UUID.
            text: New text value (optional).
            category: New category value (optional).

        Returns:
            Updated entity if found, None otherwise.
        """
        stmt = select(NamedEntityModel).where(NamedEntityModel.id == entity_id)
        model = await self._execute_scalar(stmt)

        if model is None:
            return None

        if text is not None:
            model.text = text
            model.normalized_text = text.lower().strip()

        if category is not None:
            model.category = EntityCategory.from_string(category).value

        await self._session.flush()
        return NerConverter.orm_to_domain(model)

    async def delete(self, entity_id: UUID) -> bool:
        """Delete an entity by ID.

        Args:
            entity_id: Entity UUID.

        Returns:
            True if entity was found and deleted.
        """
        return await self._mixin_delete_by_id(entity_id)
