"""OpenAI provider implementation for LLM integration.

Provides ``OpenAIClient`` with chat completion, trace analysis,
measurement suggestion, and retry/rate-limiting support.
"""

from __future__ import annotations

import json
import os
import time
from typing import Any

from oscura.api.integrations.llm.cost import RateLimiter, _global_cost_tracker
from oscura.api.integrations.llm.types import LLMConfig, LLMError, LLMResponse


class OpenAIClient:
    """OpenAI client implementation.

    Full implementation:
    - chat_completion() with retry logic
    - analyze_trace() for trace analysis
    - suggest_measurements() for measurement recommendations
    - Error handling for API failures, rate limits, timeouts
    - API key from OPENAI_API_KEY environment variable
    """

    def __init__(self, config: LLMConfig) -> None:
        """Initialize OpenAI client.

        Args:
            config: LLM configuration

        Raises:
            LLMError: If openai package not available
        """
        self.config = config
        self.rate_limiter = RateLimiter(config.requests_per_minute)

        # Import and initialize OpenAI client
        try:
            import openai  # type: ignore[import-not-found]

            self._openai = openai
        except ImportError:
            raise LLMError("OpenAI package not installed. Install with: pip install openai")

        # Get API key from config or environment
        api_key = config.api_key or os.environ.get("OPENAI_API_KEY")
        if not api_key:
            raise LLMError(
                "OpenAI API key required. Set OPENAI_API_KEY environment variable "
                "or pass api_key to configure()"
            )

        # Initialize OpenAI client
        self.client = self._openai.OpenAI(api_key=api_key, timeout=config.timeout)

    def chat_completion(self, messages: list[dict[str, str]], **kwargs: Any) -> LLMResponse:
        """Send chat completion request with retry logic.

        Args:
            messages: List of message dicts with 'role' and 'content'
            **kwargs: Additional parameters for OpenAI API

        Returns:
            LLM response with answer and metadata

        Raises:
            LLMError: If API request fails after retries
        """
        self.rate_limiter.acquire()

        last_exception = None
        for attempt in range(self.config.max_retries):
            try:
                response = self.client.chat.completions.create(
                    model=self.config.model, messages=messages, **kwargs
                )

                # Extract response content
                answer = response.choices[0].message.content or ""

                # Track costs
                input_tokens = response.usage.prompt_tokens if response.usage else 0
                output_tokens = response.usage.completion_tokens if response.usage else 0
                estimated_cost = 0.0

                if self.config.track_costs:
                    estimated_cost = _global_cost_tracker.record(
                        response.model, input_tokens, output_tokens
                    )

                return LLMResponse(
                    answer=answer,
                    confidence=None,  # OpenAI doesn't provide confidence scores
                    suggested_commands=[],
                    metadata={
                        "model": response.model,
                        "usage": {
                            "prompt_tokens": input_tokens,
                            "completion_tokens": output_tokens,
                            "total_tokens": (response.usage.total_tokens if response.usage else 0),
                        },
                        "finish_reason": response.choices[0].finish_reason,
                    },
                    raw_response={
                        "id": response.id,
                        "created": response.created,
                    },
                    estimated_cost=estimated_cost,
                )

            except self._openai.RateLimitError as e:
                last_exception = e
                if attempt < self.config.max_retries - 1:
                    wait_time = 2**attempt
                    time.sleep(wait_time)
                    continue
                raise LLMError(f"OpenAI rate limit exceeded: {e}")

            except self._openai.APITimeoutError as e:
                last_exception = e
                if attempt < self.config.max_retries - 1:
                    time.sleep(1)
                    continue
                raise LLMError(f"OpenAI request timeout: {e}")

            except self._openai.APIError as e:
                last_exception = e
                if attempt < self.config.max_retries - 1:
                    time.sleep(1)
                    continue
                raise LLMError(f"OpenAI API error: {e}")

            except Exception as e:
                last_exception = e
                raise LLMError(f"OpenAI request failed: {e}")

        raise LLMError(
            f"OpenAI request failed after {self.config.max_retries} retries: {last_exception}"
        )

    def analyze_trace(self, trace: Any, question: str) -> LLMResponse:
        """Analyze trace with question.

        Args:
            trace: Trace object
            question: Natural language question about the trace

        Returns:
            LLM response with analysis
        """
        trace_summary = self._summarize_trace(trace)

        messages = [
            {
                "role": "system",
                "content": (
                    "You are an expert in signal analysis and oscilloscope data. "
                    "Analyze the provided trace data and answer questions accurately. "
                    "Provide specific, actionable insights."
                ),
            },
            {
                "role": "user",
                "content": f"Trace Summary:\n{trace_summary}\n\nQuestion: {question}",
            },
        ]

        return self.chat_completion(messages)

    def suggest_measurements(self, trace: Any) -> LLMResponse:
        """Suggest measurements based on trace characteristics.

        Args:
            trace: Trace object

        Returns:
            LLM response with measurement suggestions
        """
        trace_summary = self._summarize_trace(trace)

        messages = [
            {
                "role": "system",
                "content": (
                    "You are an expert in signal analysis. Based on trace characteristics, "
                    "suggest relevant measurements. Provide 3-5 specific measurement "
                    "recommendations with brief explanations."
                ),
            },
            {
                "role": "user",
                "content": (
                    f"Trace Summary:\n{trace_summary}\n\n"
                    "What measurements would be most informative for this trace?"
                ),
            },
        ]

        response = self.chat_completion(messages)

        suggested_commands = self._extract_commands(response.answer)
        response.suggested_commands = suggested_commands

        return response

    def _summarize_trace(self, trace: Any) -> str:
        """Create a text summary of trace for LLM context.

        Args:
            trace: Trace object

        Returns:
            Text summary of trace characteristics
        """
        summary_parts = [f"Trace Type: {type(trace).__name__}"]

        if hasattr(trace, "metadata"):
            meta = trace.metadata
            if hasattr(meta, "sample_rate"):
                summary_parts.append(f"Sample Rate: {meta.sample_rate:.2e} Hz")
            if hasattr(meta, "num_samples"):
                summary_parts.append(f"Number of Samples: {meta.num_samples:,}")
            if hasattr(meta, "duration"):
                summary_parts.append(f"Duration: {meta.duration:.6f} s")

        if hasattr(trace, "data"):
            import numpy as np

            data = trace.data
            summary_parts.extend(
                [
                    f"Mean: {np.mean(data):.6e}",
                    f"Std Dev: {np.std(data):.6e}",
                    f"Min: {np.min(data):.6e}",
                    f"Max: {np.max(data):.6e}",
                    f"Peak-to-Peak: {np.ptp(data):.6e}",
                ]
            )

        return "\n".join(summary_parts)

    def _extract_commands(self, text: str) -> list[str]:
        """Extract suggested Oscura commands from LLM response.

        Args:
            text: LLM response text

        Returns:
            List of extracted command strings
        """
        commands: list[str] = []
        measurement_keywords = [
            "rise_time",
            "fall_time",
            "frequency",
            "period",
            "amplitude",
            "rms",
            "thd",
            "snr",
            "fft",
            "psd",
            "peak",
            "duty_cycle",
        ]

        text_lower = text.lower()
        for keyword in measurement_keywords:
            if keyword in text_lower:
                commands.append(f"measure {keyword}")

        return commands

    def query(self, prompt: str, context: dict[str, Any]) -> LLMResponse:
        """Send query to LLM with context.

        Args:
            prompt: User prompt
            context: Analysis context

        Returns:
            LLM response
        """
        context_str = json.dumps(context, indent=2)
        messages = [
            {
                "role": "system",
                "content": "You are a helpful assistant for signal analysis.",
            },
            {
                "role": "user",
                "content": f"Context:\n{context_str}\n\nQuery: {prompt}",
            },
        ]
        return self.chat_completion(messages)

    def analyze(self, trace: Any, question: str) -> LLMResponse:
        """Analyze trace with natural language question.

        Args:
            trace: Trace object
            question: Natural language question

        Returns:
            Analysis response
        """
        return self.analyze_trace(trace, question)

    def explain(self, measurement: Any) -> str:
        """Explain a measurement result.

        Args:
            measurement: Measurement result

        Returns:
            Explanation text
        """
        messages = [
            {
                "role": "system",
                "content": (
                    "You are an expert in signal measurement interpretation. "
                    "Explain measurement results clearly and concisely."
                ),
            },
            {
                "role": "user",
                "content": f"Explain this measurement result: {measurement}",
            },
        ]
        response = self.chat_completion(messages)
        return response.answer
