"""Annotation management mixin for gRPC service."""

from __future__ import annotations

from collections.abc import Sequence
from typing import TYPE_CHECKING, Protocol, Self, cast
from uuid import uuid4

from noteflow.config.constants import (
    LOG_EVENT_ANNOTATION_NOT_FOUND,
    LOG_EVENT_DATABASE_REQUIRED_FOR_ANNOTATIONS,
    LOG_EVENT_INVALID_ANNOTATION_ID,
)
from noteflow.domain.entities import Annotation
from noteflow.domain.value_objects import AnnotationId
from noteflow.infrastructure.logging import get_logger

from ..proto import noteflow_pb2
from .converters import (
    annotation_to_proto,
    parse_annotation_id,
    parse_meeting_id_or_abort,
    proto_to_annotation_type,
)
from .errors import abort_database_required, abort_invalid_argument, abort_not_found

if TYPE_CHECKING:
    from noteflow.domain.ports.repositories import AnnotationRepository, MeetingRepository
    from noteflow.domain.ports.unit_of_work import UnitOfWork

    from ._types import GrpcContext

logger = get_logger(__name__)

# Entity type names for error messages
_ENTITY_ANNOTATION = "Annotation"
_ENTITY_ANNOTATIONS = "Annotations"


class AnnotationRepositoryProvider(Protocol):
    """Minimal repository provider protocol for annotation operations."""

    supports_annotations: bool
    annotations: AnnotationRepository
    meetings: MeetingRepository

    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 AnnotationServicer(Protocol):
    """Protocol for hosts that support annotation operations."""

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


class AnnotationMixin:
    """Mixin providing annotation CRUD functionality.

    Requires host to implement AnnotationServicer protocol.
    Annotations require database persistence.
    """

    async def AddAnnotation(
        self: AnnotationServicer,
        request: noteflow_pb2.AddAnnotationRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.Annotation:
        """Add an annotation to a meeting."""
        async with self.create_repository_provider() as repo:
            if not repo.supports_annotations:
                logger.error(
                    LOG_EVENT_DATABASE_REQUIRED_FOR_ANNOTATIONS,
                    meeting_id=request.meeting_id,
                )
                await abort_database_required(context, _ENTITY_ANNOTATIONS)

            meeting_id = await parse_meeting_id_or_abort(request.meeting_id, context)
            annotation_type = proto_to_annotation_type(request.annotation_type)
            annotation = Annotation(
                id=AnnotationId(uuid4()),
                meeting_id=meeting_id,
                annotation_type=annotation_type,
                text=request.text,
                start_time=request.start_time,
                end_time=request.end_time,
                segment_ids=list(cast(Sequence[int], request.segment_ids)),
            )

            saved = await repo.annotations.add(annotation)
            await repo.commit()
            logger.info(
                "annotation_added",
                annotation_id=str(saved.id),
                meeting_id=str(meeting_id),
                annotation_type=annotation_type.value,
                start_time=saved.start_time,
                end_time=saved.end_time,
            )
            return annotation_to_proto(saved)

    async def GetAnnotation(
        self: AnnotationServicer,
        request: noteflow_pb2.GetAnnotationRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.Annotation:
        """Get an annotation by ID."""
        async with self.create_repository_provider() as repo:
            if not repo.supports_annotations:
                logger.error(
                    LOG_EVENT_DATABASE_REQUIRED_FOR_ANNOTATIONS,
                    annotation_id=request.annotation_id,
                )
                await abort_database_required(context, _ENTITY_ANNOTATIONS)

            try:
                annotation_id = parse_annotation_id(request.annotation_id)
            except ValueError:
                logger.error(
                    LOG_EVENT_INVALID_ANNOTATION_ID,
                    annotation_id=request.annotation_id,
                )
                await abort_invalid_argument(context, "Invalid annotation_id")
                raise  # Unreachable but helps type checker

            annotation = await repo.annotations.get(annotation_id)
            if annotation is None:
                logger.error(
                    LOG_EVENT_ANNOTATION_NOT_FOUND,
                    annotation_id=request.annotation_id,
                )
                await abort_not_found(context, _ENTITY_ANNOTATION, request.annotation_id)
                raise  # Unreachable but helps type checker
            logger.debug(
                "annotation_retrieved",
                annotation_id=str(annotation_id),
                meeting_id=str(annotation.meeting_id),
                annotation_type=annotation.annotation_type.value,
            )
            return annotation_to_proto(annotation)

    async def ListAnnotations(
        self: AnnotationServicer,
        request: noteflow_pb2.ListAnnotationsRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.ListAnnotationsResponse:
        """List annotations for a meeting."""
        async with self.create_repository_provider() as repo:
            if not repo.supports_annotations:
                logger.error(
                    LOG_EVENT_DATABASE_REQUIRED_FOR_ANNOTATIONS,
                    meeting_id=request.meeting_id,
                )
                await abort_database_required(context, _ENTITY_ANNOTATIONS)

            meeting_id = await parse_meeting_id_or_abort(request.meeting_id, context)
            # Check if time range filter is specified
            has_time_filter = request.start_time > 0 or request.end_time > 0
            if has_time_filter:
                annotations = await repo.annotations.get_by_time_range(
                    meeting_id,
                    request.start_time,
                    request.end_time,
                )
            else:
                annotations = await repo.annotations.get_by_meeting(meeting_id)

            logger.debug(
                "annotations_listed",
                meeting_id=str(meeting_id),
                count=len(annotations),
                has_time_filter=has_time_filter,
                start_time=request.start_time if has_time_filter else None,
                end_time=request.end_time if has_time_filter else None,
            )
            return noteflow_pb2.ListAnnotationsResponse(
                annotations=[annotation_to_proto(a) for a in annotations]
            )

    async def UpdateAnnotation(
        self: AnnotationServicer,
        request: noteflow_pb2.UpdateAnnotationRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.Annotation:
        """Update an existing annotation."""
        async with self.create_repository_provider() as repo:
            if not repo.supports_annotations:
                logger.error(
                    LOG_EVENT_DATABASE_REQUIRED_FOR_ANNOTATIONS,
                    annotation_id=request.annotation_id,
                )
                await abort_database_required(context, _ENTITY_ANNOTATIONS)

            try:
                annotation_id = parse_annotation_id(request.annotation_id)
            except ValueError:
                logger.error(
                    LOG_EVENT_INVALID_ANNOTATION_ID,
                    annotation_id=request.annotation_id,
                )
                await abort_invalid_argument(context, "Invalid annotation_id")
                raise  # Unreachable but helps type checker

            annotation = await repo.annotations.get(annotation_id)
            if annotation is None:
                logger.error(
                    LOG_EVENT_ANNOTATION_NOT_FOUND,
                    annotation_id=request.annotation_id,
                )
                await abort_not_found(context, _ENTITY_ANNOTATION, request.annotation_id)
                raise  # Unreachable but helps type checker

            # Update fields if provided
            if request.annotation_type != noteflow_pb2.ANNOTATION_TYPE_UNSPECIFIED:
                annotation.annotation_type = proto_to_annotation_type(request.annotation_type)
            if request.text:
                annotation.text = request.text
            if request.start_time > 0:
                annotation.start_time = request.start_time
            if request.end_time > 0:
                annotation.end_time = request.end_time
            if segment_ids := cast(Sequence[int], request.segment_ids):
                annotation.segment_ids = list(segment_ids)

            updated = await repo.annotations.update(annotation)
            await repo.commit()
            logger.info(
                "annotation_updated",
                annotation_id=str(annotation_id),
                meeting_id=str(updated.meeting_id),
                annotation_type=updated.annotation_type.value,
            )
            return annotation_to_proto(updated)

    async def DeleteAnnotation(
        self: AnnotationServicer,
        request: noteflow_pb2.DeleteAnnotationRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.DeleteAnnotationResponse:
        """Delete an annotation."""
        async with self.create_repository_provider() as repo:
            if not repo.supports_annotations:
                logger.error(
                    LOG_EVENT_DATABASE_REQUIRED_FOR_ANNOTATIONS,
                    annotation_id=request.annotation_id,
                )
                await abort_database_required(context, _ENTITY_ANNOTATIONS)

            try:
                annotation_id = parse_annotation_id(request.annotation_id)
            except ValueError:
                logger.error(
                    LOG_EVENT_INVALID_ANNOTATION_ID,
                    annotation_id=request.annotation_id,
                )
                await abort_invalid_argument(context, "Invalid annotation_id")
                raise  # Unreachable but helps type checker

            success = await repo.annotations.delete(annotation_id)
            if success:
                await repo.commit()
                logger.info(
                    "annotation_deleted",
                    annotation_id=str(annotation_id),
                )
                return noteflow_pb2.DeleteAnnotationResponse(success=True)
            logger.error(
                LOG_EVENT_ANNOTATION_NOT_FOUND,
                annotation_id=request.annotation_id,
            )
            await abort_not_found(context, _ENTITY_ANNOTATION, request.annotation_id)
            raise  # Unreachable but helps type checker
