"""Cloud summarization provider for OpenAI/Anthropic APIs."""

from __future__ import annotations

import asyncio
import os
import time
from datetime import UTC, datetime
from enum import Enum
from typing import TYPE_CHECKING, cast

from noteflow.config.constants import PROVIDER_NAME_OPENAI
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 anthropic
    import openai

logger = get_logger(__name__)


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

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

        settings = get_settings()
        return (
            settings.llm_default_openai_model,
            settings.llm_default_anthropic_model,
            settings.llm_temperature,
            settings.llm_timeout_seconds,
        )
    # INTENTIONAL BROAD HANDLER: Fallback for testing environments
    # - Settings may fail to load in unit tests without full config
    # - Use sensible defaults for cloud providers
    except Exception:
        return ("gpt-4o-mini", "claude-3-haiku-20240307", 0.3, 60.0)


class CloudBackend(Enum):
    """Supported cloud LLM backends."""

    OPENAI = PROVIDER_NAME_OPENAI
    ANTHROPIC = "anthropic"


class CloudSummarizer:
    """Cloud-based LLM summarizer using OpenAI or Anthropic.

    Requires explicit user consent as data is sent to external services.
    """

    def __init__(
        self,
        backend: CloudBackend = CloudBackend.OPENAI,
        api_key: str | None = None,
        model: str | None = None,
        timeout_seconds: float | None = None,
        base_url: str | None = None,
        temperature: float | None = None,
    ) -> None:
        """Initialize cloud summarizer.

        Args:
            backend: Cloud provider backend (OpenAI or Anthropic).
            api_key: API key (defaults to env var if not provided).
            model: Model name (defaults per backend from settings if not provided).
            timeout_seconds: Request timeout (uses settings if not provided).
            base_url: Optional base URL (OpenAI only; defaults to OpenAI API).
            temperature: LLM temperature (uses settings if not provided).
        """
        # Load defaults from settings
        settings = _get_llm_settings()
        openai_model, anthropic_model, default_temp, default_timeout = settings

        self._backend = backend
        self._api_key = api_key
        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: openai.OpenAI | anthropic.Anthropic | None = None
        # Only used for OpenAI
        self._openai_base_url = (
            base_url
            if base_url is not None
            else os.environ.get("OPENAI_BASE_URL")
            if backend == CloudBackend.OPENAI
            else None
        )

        # Set default models per backend from settings
        if model is None:
            self._model = openai_model if backend == CloudBackend.OPENAI else anthropic_model
        else:
            self._model = model

    def _get_openai_client(self) -> openai.OpenAI:
        """Get or create OpenAI client."""
        if self._client is None:
            try:
                import openai

                self._client = openai.OpenAI(
                    api_key=self._api_key,
                    timeout=self._timeout,
                    base_url=self._openai_base_url,
                )
            except ImportError as e:
                raise ProviderUnavailableError(
                    "openai package not installed. Install with: pip install openai"
                ) from e
        return cast(openai.OpenAI, self._client)

    def _get_anthropic_client(self) -> anthropic.Anthropic:
        """Get or create Anthropic client."""
        if self._client is None:
            try:
                import anthropic

                self._client = anthropic.Anthropic(api_key=self._api_key, timeout=self._timeout)
            except ImportError as e:
                raise ProviderUnavailableError(
                    "anthropic package not installed. Install with: pip install anthropic"
                ) from e
        return cast(anthropic.Anthropic, self._client)

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

    @property
    def model(self) -> str:
        """Return the configured model name."""
        return self._model

    def get_openai_client(self) -> openai.OpenAI:
        """Expose OpenAI client for integrations and testing."""
        return self._get_openai_client()

    @property
    def is_available(self) -> bool:
        """Check if cloud provider is configured with an API key."""
        import os

        if self._api_key:
            return True

        # Check environment variables
        if self._backend == CloudBackend.OPENAI:
            return bool(os.environ.get("OPENAI_API_KEY"))
        return bool(os.environ.get("ANTHROPIC_API_KEY"))

    @property
    def requires_cloud_consent(self) -> bool:
        """Cloud providers require explicit user consent."""
        return True

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

        Args:
            request: Summarization request with segments.

        Returns:
            SummarizationResult with generated summary.

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

        # Handle empty segments
        if not request.segments:
            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,
            )

        user_prompt = build_transcript_prompt(request)

        # Build effective system prompt with optional style prefix
        effective_system_prompt = (
            f"{request.style_prompt}\n\n{SYSTEM_PROMPT}"
            if request.style_prompt
            else SYSTEM_PROMPT
        )

        if self._backend == CloudBackend.OPENAI:
            content, tokens_used = await asyncio.to_thread(
                self._call_openai, user_prompt, effective_system_prompt
            )
        else:
            content, tokens_used = await asyncio.to_thread(
                self._call_anthropic, user_prompt, effective_system_prompt
            )

        # Parse into Summary
        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

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

    def _call_openai(self, user_prompt: str, system_prompt: str) -> tuple[str, int | None]:
        """Call OpenAI API and return (content, tokens_used).

        Args:
            user_prompt: User message with transcript.
            system_prompt: System prompt (may include style prefix).

        Returns:
            Tuple of (response content, tokens used).
        """
        try:
            client = self._get_openai_client()
        except ProviderUnavailableError:
            raise

        try:
            with log_timing("openai_api_call", model=self._model):
                response = client.chat.completions.create(
                    model=self._model,
                    messages=[
                        {"role": "system", "content": system_prompt},
                        {"role": "user", "content": user_prompt},
                    ],
                    temperature=self._temperature,
                    response_format={"type": "json_object"},
                )
        except TimeoutError as e:
            raise SummarizationTimeoutError(f"OpenAI request timed out: {e}") from e
        # INTENTIONAL BROAD HANDLER: Error translation layer
        # - OpenAI SDK can raise various exceptions
        # - Must translate to domain-specific errors for proper handling
        except Exception as e:
            err_str = str(e).lower()
            if "api key" in err_str or "authentication" in err_str:
                raise ProviderUnavailableError(f"OpenAI authentication failed: {e}") from e
            if "rate limit" in err_str:
                raise SummarizationTimeoutError(f"OpenAI rate limited: {e}") from e
            raise InvalidResponseError(f"OpenAI error: {e}") from e

        content = response.choices[0].message.content or ""
        if not content:
            raise InvalidResponseError("Empty response from OpenAI")

        tokens_used = response.usage.total_tokens if response.usage else None
        logger.info(
            "openai_api_response",
            model=self._model,
            tokens_used=tokens_used,
            content_length=len(content),
        )
        return content, tokens_used

    def _call_anthropic(self, user_prompt: str, system_prompt: str) -> tuple[str, int | None]:
        """Call Anthropic API and return (content, tokens_used).

        Args:
            user_prompt: User message with transcript.
            system_prompt: System prompt (may include style prefix).

        Returns:
            Tuple of (response content, tokens used).
        """
        try:
            client = self._get_anthropic_client()
        except ProviderUnavailableError:
            raise

        try:
            with log_timing("anthropic_api_call", model=self._model):
                response = client.messages.create(
                    model=self._model,
                    max_tokens=4096,
                    system=system_prompt,
                    messages=[{"role": "user", "content": user_prompt}],
                )
        except TimeoutError as e:
            raise SummarizationTimeoutError(f"Anthropic request timed out: {e}") from e
        # INTENTIONAL BROAD HANDLER: Error translation layer
        # - Anthropic SDK can raise various exceptions
        # - Must translate to domain-specific errors for proper handling
        except Exception as e:
            err_str = str(e).lower()
            if "api key" in err_str or "authentication" in err_str:
                raise ProviderUnavailableError(f"Anthropic authentication failed: {e}") from e
            if "rate limit" in err_str:
                raise SummarizationTimeoutError(f"Anthropic rate limited: {e}") from e
            raise InvalidResponseError(f"Anthropic error: {e}") from e

        content_parts: list[str] = []
        for block in cast(list[object], response.content):
            text = getattr(block, "text", None)
            if isinstance(text, str):
                content_parts.append(text)
        content = "".join(content_parts)
        if not content:
            raise InvalidResponseError("Empty response from Anthropic")

        tokens_used = None
        if hasattr(response, "usage"):
            tokens_used = response.usage.input_tokens + response.usage.output_tokens

        logger.info(
            "anthropic_api_response",
            model=self._model,
            tokens_used=tokens_used,
            content_length=len(content),
        )
        return content, tokens_used
