"""Ollama summarization provider for local LLM inference."""

from __future__ import annotations

import asyncio
import os
import time
from collections.abc import Mapping, Sequence
from datetime import UTC, datetime
from typing import TYPE_CHECKING, Protocol, cast

from noteflow.domain.entities import Summary
from noteflow.domain.summarization import (
    InvalidResponseError,
    ProviderUnavailableError,
    SummarizationRequest,
    SummarizationResult,
    SummarizationTimeoutError,
)
from noteflow.infrastructure.logging import get_logger, log_timing
from noteflow.infrastructure.summarization._parsing import (
    SYSTEM_PROMPT,
    build_transcript_prompt,
    parse_llm_response,
)

if TYPE_CHECKING:
    import ollama
    from ollama import ChatResponse, ListResponse


class _OllamaClient(Protocol):
    def list(self) -> ListResponse: ...

    def chat(
        self,
        *,
        model: str,
        messages: Sequence[Mapping[str, str]],
        options: Mapping[str, object],
        format: str,
    ) -> ChatResponse: ...

logger = get_logger(__name__)


def _get_ollama_settings() -> tuple[str, float, float]:
    """Get Ollama settings with fallback defaults.

    Returns:
        Tuple of (host, timeout_seconds, temperature).
    """
    try:
        from noteflow.config.settings import get_settings

        settings = get_settings()
        return (
            settings.ollama_host,
            settings.ollama_timeout_seconds,
            settings.llm_temperature,
        )
    # INTENTIONAL BROAD HANDLER: Fallback for testing environments
    # - Settings may fail to load in unit tests without full config
    # - Use sensible defaults for local Ollama server
    except Exception:
        logger.warning(
            "Failed to load Ollama settings, using defaults: "
            "host=http://localhost:11434, timeout=120s, temperature=0.3"
        )
        return ("http://localhost:11434", 120.0, 0.3)


class OllamaSummarizer:
    """Ollama-based local LLM summarizer.

    Uses a local Ollama server for privacy-preserving summarization.
    No data is sent to external cloud services.
    """

    def __init__(
        self,
        model: str | None = None,
        host: str | None = None,
        timeout_seconds: float | None = None,
        temperature: float | None = None,
    ) -> None:
        """Initialize Ollama summarizer.

        Args:
            model: Ollama model name (e.g., 'llama3.2', 'mistral').
            host: Ollama server URL (uses settings if not provided).
            timeout_seconds: Request timeout (uses settings if not provided).
            temperature: LLM temperature (uses settings if not provided).
        """
        # Load defaults from settings
        default_host, default_timeout, default_temp = _get_ollama_settings()

        self._model = model or os.environ.get("OLLAMA_MODEL", "llama3.2")
        self._host = host or os.environ.get("OLLAMA_HOST") or default_host
        self._timeout = timeout_seconds if timeout_seconds is not None else default_timeout
        self._temperature = temperature if temperature is not None else default_temp
        self._client: ollama.Client | None = None

    def _get_client(self) -> ollama.Client:
        """Lazy-load Ollama client."""
        if self._client is None:
            try:
                import ollama

                logger.debug("Creating Ollama client for host=%s", self._host)
                self._client = ollama.Client(host=self._host)
            except ImportError as e:
                logger.error("Ollama package not installed")
                raise ProviderUnavailableError(
                    "ollama package not installed. Install with: pip install ollama"
                ) from e
        return self._client

    @property
    def provider_name(self) -> str:
        """Provider identifier."""
        return "ollama"

    @property
    def is_available(self) -> bool:
        """Check if Ollama server is reachable."""
        try:
            with log_timing(
                "ollama_availability_check",
                host=self._host,
                timeout_seconds=self._timeout,
            ):
                client = cast(_OllamaClient, self._get_client())
                # Try to list models to verify connectivity
                client.list()
            return True
        except TimeoutError:
            logger.warning(
                "ollama_availability_timeout",
                host=self._host,
                timeout_seconds=self._timeout,
            )
            return False
        except (
            ConnectionError,
            RuntimeError,
            OSError,
            ProviderUnavailableError,
        ) as e:
            logger.debug(
                "ollama_server_unreachable",
                host=self._host,
                error=str(e),
                error_type=type(e).__name__,
            )
            return False

    @property
    def requires_cloud_consent(self) -> bool:
        """Ollama runs locally, no cloud consent required."""
        return False

    def _create_empty_result(self, request: SummarizationRequest) -> SummarizationResult:
        """Create result for empty segment requests.

        Args:
            request: The summarization request.

        Returns:
            SummarizationResult indicating no content to summarize.
        """
        return SummarizationResult(
            summary=Summary(
                meeting_id=request.meeting_id,
                executive_summary="No transcript segments to summarize.",
                key_points=[],
                action_items=[],
                generated_at=datetime.now(UTC),
                provider_name=self.provider_name,
                model_name=self._model,
            ),
            model_name=self._model,
            provider_name=self.provider_name,
            tokens_used=None,
            latency_ms=0.0,
        )

    async def _call_ollama(
        self,
        client: _OllamaClient,
        effective_system_prompt: str,
        user_prompt: str,
    ) -> ChatResponse:
        """Execute Ollama chat request in worker thread.

        Args:
            client: Ollama client instance.
            effective_system_prompt: System prompt with optional style.
            user_prompt: User prompt with transcript.

        Returns:
            ChatResponse from Ollama.

        Raises:
            SummarizationTimeoutError: If request times out.
            ProviderUnavailableError: If connection fails.
            InvalidResponseError: For other errors.
        """
        try:
            return await asyncio.to_thread(
                client.chat,
                model=self._model,
                messages=[
                    {"role": "system", "content": effective_system_prompt},
                    {"role": "user", "content": user_prompt},
                ],
                options={"temperature": self._temperature},
                format="json",
            )
        except TimeoutError as e:
            raise SummarizationTimeoutError(f"Ollama request timed out: {e}") from e
        # INTENTIONAL BROAD HANDLER: Error translation layer
        # - Ollama can raise various exceptions (httpx, connection, etc.)
        # - Must translate to domain-specific errors for proper handling
        except Exception as e:
            err_str = str(e).lower()
            if "connection" in err_str or "refused" in err_str:
                raise ProviderUnavailableError(f"Cannot connect to Ollama: {e}") from e
            raise InvalidResponseError(f"Ollama error: {e}") from e

    async def summarize(self, request: SummarizationRequest) -> SummarizationResult:
        """Generate evidence-linked summary using Ollama.

        Args:
            request: Summarization request with segments.

        Returns:
            SummarizationResult with generated summary.

        Raises:
            ProviderUnavailableError: If Ollama is not accessible.
            SummarizationTimeoutError: If request times out.
            InvalidResponseError: If response cannot be parsed.
        """
        start = time.monotonic()

        if not request.segments:
            return self._create_empty_result(request)

        client = self._get_client()
        typed_client = cast(_OllamaClient, client)
        user_prompt = build_transcript_prompt(request)
        effective_system_prompt = (
            f"{request.style_prompt}\n\n{SYSTEM_PROMPT}" if request.style_prompt else SYSTEM_PROMPT
        )

        response = await self._call_ollama(typed_client, effective_system_prompt, user_prompt)

        content = response.message.content or ""
        if not content:
            raise InvalidResponseError("Empty response from Ollama")

        parsed = parse_llm_response(content, request)
        summary = Summary(
            meeting_id=parsed.meeting_id,
            executive_summary=parsed.executive_summary,
            key_points=parsed.key_points,
            action_items=parsed.action_items,
            generated_at=parsed.generated_at,
            provider_name=self.provider_name,
            model_name=self._model,
        )

        elapsed_ms = (time.monotonic() - start) * 1000
        eval_count = getattr(response, "eval_count", None)
        prompt_eval_count = getattr(response, "prompt_eval_count", None)
        tokens_used = (
            (eval_count or 0) + (prompt_eval_count or 0) if eval_count is not None else None
        )

        return SummarizationResult(
            summary=summary,
            model_name=self._model,
            provider_name=self.provider_name,
            tokens_used=tokens_used,
            latency_ms=elapsed_ms,
        )
