"""LLM Provider abstraction for multiple backends."""

import json
import os
from abc import ABC, abstractmethod
from typing import Generator, Optional

import requests


class LLMProvider(ABC):
    """Abstract base class for LLM providers."""

    @abstractmethod
    def chat(self, system: str, user: str, temperature: float = 0.7) -> str:
        """Generate a chat response.

        Args:
            system: System prompt
            user: User message
            temperature: Sampling temperature

        Returns:
            Generated response text

        Raises:
            RuntimeError: If API call fails
        """
        pass

    @abstractmethod
    def chat_stream(
        self, system: str, user: str, temperature: float = 0.7
    ) -> Generator[str, None, None]:
        """Generate a streaming chat response.

        Args:
            system: System prompt
            user: User message
            temperature: Sampling temperature

        Yields:
            Generated response chunks

        Raises:
            RuntimeError: If API call fails
        """
        pass


class OllamaProvider(LLMProvider):
    """Ollama LLM provider."""

    def __init__(
        self,
        model: str,
        base_url: str = "http://localhost:11434",
        timeout: int = 30,
    ):
        """Initialize Ollama provider.

        Args:
            model: Model name (e.g., "llama2")
            base_url: Ollama API base URL
            timeout: Request timeout in seconds
        """
        self.model = model
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout

    def chat(self, system: str, user: str, temperature: float = 0.7) -> str:
        """Generate a chat response using Ollama.

        Args:
            system: System prompt
            user: User message
            temperature: Sampling temperature

        Returns:
            Generated response text

        Raises:
            RuntimeError: If API call fails
        """
        url = f"{self.base_url}/api/generate"
        payload = {
            "model": self.model,
            "prompt": user,
            "system": system,
            "stream": False,
            "options": {"temperature": temperature},
        }

        try:
            response = requests.post(url, json=payload, timeout=self.timeout)
            response.raise_for_status()
            result = response.json()
            return result.get("response", "")
        except requests.exceptions.RequestException as e:
            raise RuntimeError(f"Ollama API call failed: {e}")

    def chat_stream(
        self, system: str, user: str, temperature: float = 0.7
    ) -> Generator[str, None, None]:
        """Generate a streaming chat response using Ollama.

        Args:
            system: System prompt
            user: User message
            temperature: Sampling temperature

        Yields:
            Generated response chunks

        Raises:
            RuntimeError: If API call fails
        """
        url = f"{self.base_url}/api/generate"
        payload = {
            "model": self.model,
            "prompt": user,
            "system": system,
            "stream": True,
            "options": {"temperature": temperature},
        }

        try:
            response = requests.post(
                url, json=payload, timeout=self.timeout, stream=True
            )
            response.raise_for_status()

            # Parse NDJSON stream
            for line in response.iter_lines():
                if line:
                    try:
                        data = json.loads(line)
                        # Check if stream is done
                        if data.get("done", False):
                            break
                        # Yield non-empty response chunks
                        chunk = data.get("response", "")
                        if chunk:
                            yield chunk
                    except json.JSONDecodeError:
                        continue

        except requests.exceptions.RequestException as e:
            raise RuntimeError(f"Ollama API streaming call failed: {e}")


class OpenRouterProvider(LLMProvider):
    """OpenRouter LLM provider (OpenAI-compatible API)."""

    def __init__(
        self,
        model: str,
        api_key: Optional[str] = None,
        base_url: str = "https://openrouter.ai/api/v1",
        timeout: int = 60,
    ):
        """Initialize OpenRouter provider.

        Args:
            model: Model name (e.g., "openai/gpt-4o-mini")
            api_key: OpenRouter API key (defaults to OPENROUTER_API_KEY env var)
            base_url: OpenRouter API base URL
            timeout: Request timeout in seconds
        """
        self.model = model
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self.api_key = api_key or os.getenv("OPENROUTER_API_KEY")

    def chat(self, system: str, user: str, temperature: float = 0.7) -> str:
        """Generate a chat response using OpenRouter.

        Args:
            system: System prompt
            user: User message
            temperature: Sampling temperature

        Returns:
            Generated response text

        Raises:
            RuntimeError: If API call fails
            ValueError: If API key is not configured
        """
        if not self.api_key:
            raise ValueError(
                "OpenRouter API key required. Set OPENROUTER_API_KEY environment variable "
                "or pass api_key parameter."
            )

        url = f"{self.base_url}/chat/completions"
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
        }
        payload = {
            "model": self.model,
            "messages": [
                {"role": "system", "content": system},
                {"role": "user", "content": user},
            ],
            "temperature": temperature,
        }

        try:
            response = requests.post(
                url, json=payload, headers=headers, timeout=self.timeout
            )
            response.raise_for_status()
            result = response.json()
            return result["choices"][0]["message"]["content"]
        except requests.exceptions.RequestException as e:
            raise RuntimeError(f"OpenRouter API call failed: {e}")
        except (KeyError, IndexError) as e:
            raise RuntimeError(f"Failed to parse OpenRouter response: {e}")

    def chat_stream(
        self, system: str, user: str, temperature: float = 0.7
    ) -> Generator[str, None, None]:
        """Generate a streaming chat response using OpenRouter.

        Args:
            system: System prompt
            user: User message
            temperature: Sampling temperature

        Yields:
            Generated response chunks

        Raises:
            RuntimeError: If API call fails after retries
            ValueError: If API key is not configured
        """
        if not self.api_key:
            raise ValueError(
                "OpenRouter API key required. Set OPENROUTER_API_KEY environment variable "
                "or pass api_key parameter."
            )

        url = f"{self.base_url}/chat/completions"
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
        }
        payload = {
            "model": self.model,
            "messages": [
                {"role": "system", "content": system},
                {"role": "user", "content": user},
            ],
            "temperature": temperature,
            "stream": True,
        }

        max_retries = 2
        for attempt in range(max_retries + 1):
            try:
                response = requests.post(
                    url, json=payload, headers=headers, timeout=self.timeout, stream=True
                )
                response.raise_for_status()

                # Parse SSE stream
                for line in response.iter_lines():
                    if line:
                        line_str = line.decode("utf-8")
                        # SSE format: "data: {...}"
                        if line_str.startswith("data: "):
                            data_str = line_str[6:]  # Remove "data: " prefix

                            # Check for [DONE] signal
                            if data_str.strip() == "[DONE]":
                                break

                            try:
                                data = json.loads(data_str)
                                # Extract delta content
                                delta = (
                                    data.get("choices", [{}])[0]
                                    .get("delta", {})
                                    .get("content", "")
                                )
                                if delta:
                                    yield delta
                            except (json.JSONDecodeError, IndexError, KeyError):
                                continue

                # If we successfully completed streaming, break out of retry loop
                break

            except (requests.exceptions.ChunkedEncodingError, RuntimeError) as e:
                if attempt < max_retries:
                    # Retry on network errors
                    continue
                else:
                    # Max retries exceeded
                    raise RuntimeError(
                        f"OpenRouter API streaming call failed after {max_retries + 1} attempts: {e}"
                    )
            except requests.exceptions.RequestException as e:
                # Don't retry other request exceptions
                raise RuntimeError(f"OpenRouter API streaming call failed: {e}")


def get_provider(
    provider_type: str,
    model: str,
    ollama_url: str = "http://localhost:11434",
    openrouter_api_key: Optional[str] = None,
    openrouter_url: str = "https://openrouter.ai/api/v1",
    timeout: int = 60,
) -> LLMProvider:
    """Factory function to create LLM provider instances.

    Args:
        provider_type: Provider type ("ollama" or "openrouter")
        model: Model name
        ollama_url: Ollama API URL (for Ollama provider)
        openrouter_api_key: OpenRouter API key (for OpenRouter provider)
        openrouter_url: OpenRouter API URL (for OpenRouter provider)
        timeout: Request timeout in seconds

    Returns:
        LLM provider instance

    Raises:
        ValueError: If provider_type is not supported
    """
    if provider_type == "ollama":
        return OllamaProvider(model=model, base_url=ollama_url, timeout=timeout)
    elif provider_type == "openrouter":
        return OpenRouterProvider(
            model=model,
            api_key=openrouter_api_key,
            base_url=openrouter_url,
            timeout=timeout,
        )
    else:
        raise ValueError(
            f"Unknown provider type: {provider_type}. "
            f"Supported types: 'ollama', 'openrouter'"
        )
