"""Entity extraction gRPC mixin."""

from __future__ import annotations

from typing import TYPE_CHECKING, Protocol, Self

from noteflow.infrastructure.logging import get_logger

from ..proto import noteflow_pb2
from ._types import GrpcContext
from .converters import entity_to_proto, parse_meeting_id_or_abort
from .errors import (
    ENTITY_ENTITY,
    ENTITY_MEETING,
    abort_failed_precondition,
    abort_invalid_argument,
    abort_not_found,
    parse_entity_id,
    require_feature_entities,
    require_ner_service,
)

if TYPE_CHECKING:
    from noteflow.application.services.ner_service import NerService
    from noteflow.domain.ports.repositories import EntityRepository
    from noteflow.domain.ports.unit_of_work import UnitOfWork


logger = get_logger(__name__)


class EntitiesServicer(Protocol):
    """Protocol for hosts that support entity extraction operations."""

    ner_service: NerService | None

    def create_repository_provider(self) -> EntitiesRepositoryProvider | UnitOfWork: ...


class EntitiesRepositoryProvider(Protocol):
    """Minimal repository provider protocol for entity operations."""

    supports_entities: bool
    entities: EntityRepository

    async def commit(self) -> None: ...

    async def __aenter__(self) -> Self: ...

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: object,
    ) -> None: ...


class EntitiesMixin:
    """Mixin for entity extraction RPC methods.

    Implements the ExtractEntities RPC using the NerService application layer.
    Architecture: gRPC → NerService (application) → NerEngine (infrastructure)
    """

    ner_service: NerService | None

    async def ExtractEntities(
        self: EntitiesServicer,
        request: noteflow_pb2.ExtractEntitiesRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.ExtractEntitiesResponse:
        """Extract named entities from meeting transcript.

        Delegates to NerService for extraction, caching, and persistence.
        Returns cached results if available, unless force_refresh is True.
        """
        meeting_id = await parse_meeting_id_or_abort(request.meeting_id, context)
        ner_service = await require_ner_service(self.ner_service, context)

        try:
            result = await ner_service.extract_entities(
                meeting_id=meeting_id,
                force_refresh=request.force_refresh,
            )
        except ValueError:
            # Meeting not found
            await abort_not_found(context, ENTITY_MEETING, request.meeting_id)
            raise  # Unreachable: abort raises
        except RuntimeError as e:
            # Feature disabled
            await abort_failed_precondition(context, str(e))
            raise  # Unreachable: abort raises

        # Convert to proto
        proto_entities = [entity_to_proto(entity) for entity in result.entities]

        return noteflow_pb2.ExtractEntitiesResponse(
            entities=proto_entities,
            total_count=result.total_count,
            cached=result.cached,
        )

    async def UpdateEntity(
        self: EntitiesServicer,
        request: noteflow_pb2.UpdateEntityRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.UpdateEntityResponse:
        """Update an existing named entity.

        Updates text and/or category of an entity.
        Requires database persistence.
        """
        _meeting_id = await parse_meeting_id_or_abort(request.meeting_id, context)
        entity_id = await parse_entity_id(request.entity_id, context)

        uow = self.create_repository_provider()
        await require_feature_entities(uow, context)

        async with uow:
            entity = await uow.entities.get(entity_id)
            if entity is None or entity.meeting_id != _meeting_id:
                await abort_not_found(context, ENTITY_ENTITY, request.entity_id)

            # Update the entity (meeting_id validated for context)
            try:
                updated = await uow.entities.update(
                    entity_id=entity_id,
                    text=request.text or None,
                    category=request.category or None,
                )
            except ValueError as exc:
                await abort_invalid_argument(context, str(exc))
                return noteflow_pb2.UpdateEntityResponse()

            if updated is None:
                await abort_not_found(context, ENTITY_ENTITY, request.entity_id)
                raise  # Unreachable but helps type checker

            await uow.commit()

        return noteflow_pb2.UpdateEntityResponse(
            entity=entity_to_proto(updated),
        )

    async def DeleteEntity(
        self: EntitiesServicer,
        request: noteflow_pb2.DeleteEntityRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.DeleteEntityResponse:
        """Delete a named entity.

        Removes an entity from the meeting.
        Requires database persistence.
        """
        _meeting_id = await parse_meeting_id_or_abort(request.meeting_id, context)
        entity_id = await parse_entity_id(request.entity_id, context)

        uow = self.create_repository_provider()
        await require_feature_entities(uow, context)

        async with uow:
            entity = await uow.entities.get(entity_id)
            if entity is None or entity.meeting_id != _meeting_id:
                await abort_not_found(context, ENTITY_ENTITY, request.entity_id)

            deleted = await uow.entities.delete(entity_id)

            if not deleted:
                await abort_not_found(context, ENTITY_ENTITY, request.entity_id)

            await uow.commit()

        return noteflow_pb2.DeleteEntityResponse(success=True)
