"""Summarization orchestration service.

Coordinate provider selection, consent handling, and citation verification.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum
from typing import TYPE_CHECKING

from noteflow.application.observability.ports import (
    NullUsageEventSink,
    UsageEventSink,
    UsageMetrics,
)
from noteflow.domain.summarization import (
    DEFAULT_MAX_ACTION_ITEMS,
    DEFAULT_MAX_KEY_POINTS,
    CitationVerificationResult,
    ProviderUnavailableError,
    SummarizationRequest,
    SummarizationResult,
)
from noteflow.infrastructure.logging import get_logger

if TYPE_CHECKING:
    from collections.abc import Awaitable, Callable, Sequence

    from noteflow.domain.entities import Segment, Summary
    from noteflow.domain.summarization import CitationVerifier, SummarizerProvider
    from noteflow.domain.value_objects import MeetingId

    # Type alias for persistence callback
    PersistCallback = Callable[[Summary], Awaitable[None]]

    # Type alias for consent persistence callback
    ConsentPersistCallback = Callable[[bool], Awaitable[None]]

logger = get_logger(__name__)


class SummarizationMode(Enum):
    """Available summarization modes."""

    MOCK = "mock"
    LOCAL = "local"  # Ollama
    CLOUD = "cloud"  # OpenAI/Anthropic


@dataclass
class SummarizationServiceSettings:
    """Configuration for summarization service.

    Attributes:
        default_mode: Default summarization mode.
        cloud_consent_granted: Whether user has consented to cloud processing.
        fallback_to_local: Fall back to local if cloud unavailable.
        verify_citations: Whether to verify citations after summarization.
        filter_invalid_citations: Remove invalid citations from result.
        max_key_points: Default maximum key points.
        max_action_items: Default maximum action items.
    """

    default_mode: SummarizationMode = SummarizationMode.LOCAL
    cloud_consent_granted: bool = False
    fallback_to_local: bool = True
    verify_citations: bool = True
    filter_invalid_citations: bool = True
    max_key_points: int = DEFAULT_MAX_KEY_POINTS
    max_action_items: int = DEFAULT_MAX_ACTION_ITEMS


@dataclass
class SummarizationServiceResult:
    """Result from summarization service.

    Attributes:
        result: The raw summarization result from the provider.
        verification: Citation verification result (if verification enabled).
        filtered_summary: Summary with invalid citations removed (if filtering enabled).
        provider_used: Which provider was actually used.
        fallback_used: Whether a fallback provider was used.
    """

    result: SummarizationResult
    verification: CitationVerificationResult | None = None
    filtered_summary: Summary | None = None
    provider_used: str = ""
    fallback_used: bool = False

    @property
    def summary(self) -> Summary:
        """Get the best available summary (filtered if available)."""
        return self.filtered_summary or self.result.summary

    @property
    def has_invalid_citations(self) -> bool:
        """Check if summary has invalid citations."""
        return self.verification is not None and not self.verification.is_valid


@dataclass
class SummarizationService:
    """Orchestrate summarization with provider selection and citation verification.

    Manages provider selection based on mode and availability, handles
    cloud consent requirements, and verifies/filters citation integrity.
    """

    providers: dict[SummarizationMode, SummarizerProvider] = field(default_factory=dict)
    verifier: CitationVerifier | None = None
    settings: SummarizationServiceSettings = field(default_factory=SummarizationServiceSettings)
    on_persist: PersistCallback | None = None
    on_consent_change: ConsentPersistCallback | None = None
    usage_events: UsageEventSink = field(default_factory=NullUsageEventSink)

    @property
    def cloud_consent_granted(self) -> bool:
        """Return whether cloud consent is currently granted."""
        return self.settings.cloud_consent_granted

    def register_provider(self, mode: SummarizationMode, provider: SummarizerProvider) -> None:
        """Register a provider for a specific mode.

        Args:
            mode: The mode this provider handles.
            provider: The provider implementation.
        """
        self.providers[mode] = provider
        logger.debug("Registered %s provider: %s", mode.value, provider.provider_name)

    def set_verifier(self, verifier: CitationVerifier) -> None:
        """Set the citation verifier.

        Args:
            verifier: Citation verifier implementation.
        """
        self.verifier = verifier

    def get_available_modes(self) -> list[SummarizationMode]:
        """Get list of currently available summarization modes.

        Returns:
            List of available modes based on registered providers.
        """
        available: list[SummarizationMode] = []
        for mode, provider in self.providers.items():
            if mode == SummarizationMode.CLOUD:
                if provider.is_available and self.settings.cloud_consent_granted:
                    available.append(mode)
            elif provider.is_available:
                available.append(mode)
        return available

    def is_mode_available(self, mode: SummarizationMode) -> bool:
        """Check if a specific mode is available.

        Args:
            mode: The mode to check.

        Returns:
            True if mode is available.
        """
        return mode in self.get_available_modes()

    async def grant_cloud_consent(self) -> None:
        """Grant consent for cloud processing."""
        self.settings.cloud_consent_granted = True
        logger.info("Cloud consent granted")
        if self.on_consent_change:
            await self.on_consent_change(True)

    async def revoke_cloud_consent(self) -> None:
        """Revoke consent for cloud processing."""
        self.settings.cloud_consent_granted = False
        logger.info("Cloud consent revoked")
        if self.on_consent_change:
            await self.on_consent_change(False)

    async def summarize(
        self,
        meeting_id: MeetingId,
        segments: Sequence[Segment],
        mode: SummarizationMode | None = None,
        max_key_points: int | None = None,
        max_action_items: int | None = None,
        style_prompt: str | None = None,
    ) -> SummarizationServiceResult:
        """Generate evidence-linked summary for meeting transcript.

        Args:
            meeting_id: The meeting ID.
            segments: Transcript segments to summarize.
            mode: Override default mode (None uses settings default).
            max_key_points: Override default max key points.
            max_action_items: Override default max action items.
            style_prompt: Optional style instruction to prepend to system prompt.

        Returns:
            SummarizationServiceResult with summary and verification.

        Raises:
            SummarizationError: If summarization fails and no fallback available.
            ProviderUnavailableError: If no provider is available for the mode.
        """
        target_mode = mode or self.settings.default_mode
        provider, actual_mode = self._get_provider_with_fallback(target_mode)
        fallback_used = actual_mode != target_mode
        if fallback_used:
            logger.info("Falling back from %s to %s mode", target_mode.value, actual_mode.value)
        # Build and execute request
        request = SummarizationRequest(
            meeting_id=meeting_id,
            segments=segments,
            max_key_points=max_key_points or self.settings.max_key_points,
            max_action_items=max_action_items or self.settings.max_action_items,
            style_prompt=style_prompt,
        )
        logger.info("Summarizing %d segments with %s provider", len(segments), provider.provider_name)
        result = await provider.summarize(request)
        result.summary.tokens_used = result.tokens_used
        result.summary.latency_ms = result.latency_ms
        self._emit_usage_event(result, meeting_id, len(segments), fallback_used)
        # Build and verify result
        service_result = SummarizationServiceResult(
            result=result, provider_used=provider.provider_name, fallback_used=fallback_used
        )
        self._apply_citation_verification(service_result, segments)
        if self.on_persist is not None:
            await self.on_persist(service_result.summary)
            logger.debug("Summary persisted for meeting %s", meeting_id)
        return service_result

    def _emit_usage_event(
        self, result: SummarizationResult, meeting_id: MeetingId, segment_count: int, fallback_used: bool
    ) -> None:
        """Emit usage event for observability."""
        metrics = UsageMetrics(
            provider_name=result.provider_name,
            model_name=result.model_name,
            tokens_input=result.tokens_used,
            latency_ms=result.latency_ms,
        )
        self.usage_events.record_simple(
            "summarization.completed",
            metrics,
            meeting_id=str(meeting_id),
            success=True,
            segment_count=segment_count,
            fallback_used=fallback_used,
        )

    def _apply_citation_verification(
        self, service_result: SummarizationServiceResult, segments: Sequence[Segment]
    ) -> None:
        """Apply citation verification and filtering if enabled."""
        if not self.settings.verify_citations or self.verifier is None:
            return
        verification = self.verifier.verify_citations(service_result.result.summary, list(segments))
        service_result.verification = verification
        if not verification.is_valid:
            logger.warning("Summary has %d invalid citations", verification.invalid_count)
            if self.settings.filter_invalid_citations:
                service_result.filtered_summary = self.filter_citations(
                    service_result.result.summary, list(segments)
                )

    def _get_provider_with_fallback(
        self, mode: SummarizationMode
    ) -> tuple[SummarizerProvider, SummarizationMode]:
        """Get provider for mode, with fallback if unavailable.

        Args:
            mode: Requested mode.

        Returns:
            Tuple of (provider, actual_mode).

        Raises:
            ProviderUnavailableError: If no provider available.
        """
        # Check requested mode
        if mode in self.providers:
            provider = self.providers[mode]

            # Check cloud consent
            if mode == SummarizationMode.CLOUD and not self.settings.cloud_consent_granted:
                logger.warning("Cloud mode requested but consent not granted")
                if self.settings.fallback_to_local:
                    return self._get_fallback_provider(mode)
                raise ProviderUnavailableError("Cloud consent not granted")

            if provider.is_available:
                return provider, mode

            # Provider exists but unavailable
            if self.settings.fallback_to_local and mode != SummarizationMode.MOCK:
                return self._get_fallback_provider(mode)

        raise ProviderUnavailableError(f"No provider available for mode: {mode.value}")

    def _get_fallback_provider(
        self, original_mode: SummarizationMode
    ) -> tuple[SummarizerProvider, SummarizationMode]:
        """Get fallback provider when primary unavailable.

        Fallback order: LOCAL -> MOCK

        Args:
            original_mode: The mode that was unavailable.

        Returns:
            Tuple of (provider, mode).

        Raises:
            ProviderUnavailableError: If no fallback available.
        """
        fallback_order = [SummarizationMode.LOCAL, SummarizationMode.MOCK]

        for fallback_mode in fallback_order:
            if fallback_mode == original_mode:
                continue
            if fallback_mode in self.providers:
                provider = self.providers[fallback_mode]
                if provider.is_available:
                    return provider, fallback_mode

        raise ProviderUnavailableError("No fallback provider available")

    def filter_citations(self, summary: Summary, segments: list[Segment]) -> Summary:
        """Filter invalid citations from summary.

        Args:
            summary: Summary to filter.
            segments: Available segments.

        Returns:
            Summary with invalid citations removed.
        """
        if self.verifier is None:
            return summary

        return self.verifier.filter_invalid_citations(summary, segments)

    def set_default_mode(self, mode: SummarizationMode) -> None:
        """Set the default summarization mode.

        Args:
            mode: New default mode.
        """
        self.settings.default_mode = mode
        logger.info("Default summarization mode set to %s", mode.value)

    def set_persist_callback(self, callback: PersistCallback | None) -> None:
        """Set callback for persisting summaries after generation.

        Args:
            callback: Async function that persists a Summary, or None to disable.
        """
        self.on_persist = callback
