"""Summarization mixin for gRPC service."""

from __future__ import annotations

from typing import TYPE_CHECKING, Protocol, cast

from noteflow.domain.entities import Segment, Summary
from noteflow.domain.summarization import ProviderUnavailableError
from noteflow.domain.value_objects import MeetingId
from noteflow.infrastructure.logging import get_logger
from noteflow.infrastructure.summarization._parsing import build_style_prompt

from ..proto import noteflow_pb2
from ._types import GrpcContext
from .converters import parse_meeting_id_or_abort, summary_to_proto
from .errors import ENTITY_MEETING, abort_failed_precondition, abort_not_found

if TYPE_CHECKING:
    from noteflow.application.services.summarization_service import (
        SummarizationService,
        SummarizationServiceResult,
    )
    from noteflow.application.services.webhook_service import WebhookService
    from noteflow.domain.ports.unit_of_work import UnitOfWork

logger = get_logger(__name__)


class _HasField(Protocol):
    def HasField(self, field_name: str) -> bool: ...


class SummarizationServicer(Protocol):
    summarization_service: SummarizationService | None
    webhook_service: WebhookService | None

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

    async def summarize_or_placeholder(
        self,
        meeting_id: MeetingId,
        segments: list[Segment],
        style_prompt: str | None = None,
    ) -> Summary: ...

    def generate_placeholder_summary(
        self,
        meeting_id: MeetingId,
        segments: list[Segment],
    ) -> Summary: ...


class SummarizationMixin:
    """Mixin providing summarization functionality.

    Requires host to implement SummarizationServicer protocol.
    Works with both database and memory backends via RepositoryProvider.
    """

    summarization_service: SummarizationService | None

    async def GenerateSummary(
        self: SummarizationServicer,
        request: noteflow_pb2.GenerateSummaryRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.Summary:
        """Generate meeting summary using SummarizationService with fallback.

        The potentially slow summarization step is executed outside the repository
        context to avoid holding connections while waiting on LLMs.
        """
        meeting_id = await parse_meeting_id_or_abort(request.meeting_id, context)

        # Build style prompt from proto options if provided
        style_prompt: str | None = None
        if cast(_HasField, request).HasField("options"):
            style_prompt = build_style_prompt(
                tone=request.options.tone or None,
                format_style=request.options.format or None,
                verbosity=request.options.verbosity or None,
            ) or None  # Convert empty string to None

        # 1) Load meeting, existing summary, and segments in a short transaction
        async with self.create_repository_provider() as repo:
            meeting = await repo.meetings.get(meeting_id)
            if meeting is None:
                await abort_not_found(context, ENTITY_MEETING, request.meeting_id)
                raise  # Unreachable but helps type checker

            existing = await repo.summaries.get_by_meeting(meeting.id)
            if existing and not request.force_regenerate:
                return summary_to_proto(existing)

            segments = list(await repo.segments.get_by_meeting(meeting.id))

        # 2) Run summarization outside repository context (slow LLM call)
        summary = await self.summarize_or_placeholder(meeting_id, segments, style_prompt)

        # 3) Persist in a fresh transaction
        async with self.create_repository_provider() as repo:
            saved = await repo.summaries.save(summary)
            await repo.commit()

        # Trigger summary.generated webhook (fire-and-forget)
        if self.webhook_service is not None:
            try:
                # Attach saved summary to meeting for webhook payload
                meeting.summary = saved
                await self.webhook_service.trigger_summary_generated(meeting)
            # INTENTIONAL BROAD HANDLER: Fire-and-forget webhook
            # - Webhook failures must never block summarization RPC
            except Exception:
                logger.exception("Failed to trigger summary.generated webhooks")

        return summary_to_proto(saved)

    async def summarize_or_placeholder(
        self: SummarizationServicer,
        meeting_id: MeetingId,
        segments: list[Segment],
        style_prompt: str | None = None,
    ) -> Summary:
        """Try to summarize via service, fallback to placeholder on failure."""
        if self.summarization_service is None:
            logger.warning("SummarizationService not configured; using placeholder summary")
            return self.generate_placeholder_summary(meeting_id, segments)

        try:
            result: SummarizationServiceResult = await self.summarization_service.summarize(
                meeting_id=meeting_id,
                segments=segments,
                style_prompt=style_prompt,
            )
            summary = result.summary
            logger.info(
                "Generated summary using %s/%s",
                result.result.provider_name,
                result.result.model_name,
            )
            return summary
        except ProviderUnavailableError as exc:
            logger.warning("Summarization provider unavailable; using placeholder: %s", exc)
        except (TimeoutError, RuntimeError, ValueError) as exc:
            logger.exception(
                "Summarization failed (%s); using placeholder summary", type(exc).__name__
            )
        return self.generate_placeholder_summary(meeting_id, segments)

    def generate_placeholder_summary(
        self: SummarizationServicer,
        meeting_id: MeetingId,
        segments: list[Segment],
    ) -> Summary:
        """Generate a lightweight placeholder summary when summarization fails."""
        full_text = " ".join(s.text for s in segments)
        executive = f"{full_text[:200]}..." if len(full_text) > 200 else full_text
        executive = executive or "No transcript available."

        return Summary(
            meeting_id=meeting_id,
            executive_summary=executive,
            key_points=[],
            action_items=[],
            provider_name="placeholder",
            model_name="v0",
        )

    async def GrantCloudConsent(
        self: SummarizationServicer,
        request: noteflow_pb2.GrantCloudConsentRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.GrantCloudConsentResponse:
        """Grant consent for cloud-based summarization."""
        if self.summarization_service is None:
            await abort_failed_precondition(
                context,
                "Summarization service not available",
            )
            raise  # Unreachable but helps type checker
        await self.summarization_service.grant_cloud_consent()
        logger.info("Cloud consent granted")
        return noteflow_pb2.GrantCloudConsentResponse()

    async def RevokeCloudConsent(
        self: SummarizationServicer,
        request: noteflow_pb2.RevokeCloudConsentRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.RevokeCloudConsentResponse:
        """Revoke consent for cloud-based summarization."""
        if self.summarization_service is None:
            await abort_failed_precondition(
                context,
                "Summarization service not available",
            )
            raise  # Unreachable but helps type checker
        await self.summarization_service.revoke_cloud_consent()
        logger.info("Cloud consent revoked")
        return noteflow_pb2.RevokeCloudConsentResponse()

    async def GetCloudConsentStatus(
        self: SummarizationServicer,
        request: noteflow_pb2.GetCloudConsentStatusRequest,
        context: GrpcContext,
    ) -> noteflow_pb2.GetCloudConsentStatusResponse:
        """Return current cloud consent status."""
        if self.summarization_service is None:
            # Default to not granted if service unavailable
            return noteflow_pb2.GetCloudConsentStatusResponse(consent_granted=False)
        return noteflow_pb2.GetCloudConsentStatusResponse(
            consent_granted=self.summarization_service.cloud_consent_granted,
        )
