"""OpenAI adapter for PULSE Protocol.

Translates PULSE semantic messages to OpenAI API calls and back.
Supports chat completions, text generation, and analysis tasks.

Example:
    >>> adapter = OpenAIAdapter(api_key="sk-...")
    >>> msg = PulseMessage(
    ...     action="ACT.QUERY.DATA",
    ...     parameters={"query": "Explain quantum computing"}
    ... )
    >>> response = adapter.send(msg)
    >>> print(response.content["parameters"]["result"])
"""

from typing import Any, Dict, List, Optional

from openai import OpenAI, APIError, APIConnectionError, RateLimitError, AuthenticationError

from pulse.message import PulseMessage
from pulse.adapter import PulseAdapter, AdapterError, AdapterConnectionError


# Map PULSE actions to OpenAI behavior
ACTION_PROMPTS = {
    "ACT.ANALYZE.SENTIMENT": (
        "Analyze the sentiment of the following text. "
        "Respond with a JSON object: "
        '{{"sentiment": "positive|negative|neutral|mixed", '
        '"confidence": 0.0-1.0, "explanation": "brief reason"}}'
    ),
    "ACT.ANALYZE.PATTERN": (
        "Analyze the following data for patterns and trends. "
        "Respond with a JSON object: "
        '{{"patterns": ["pattern1", ...], "summary": "brief summary"}}'
    ),
    "ACT.CREATE.TEXT": (
        "Generate text based on the following instructions."
    ),
    "ACT.TRANSFORM.TRANSLATE": (
        "Translate the following text. "
        "Output only the translation, nothing else."
    ),
    "ACT.TRANSFORM.SUMMARIZE": (
        "Summarize the following text concisely."
    ),
}

# Default model for each action type
DEFAULT_MODELS = {
    "ACT.ANALYZE.SENTIMENT": "gpt-4o-mini",
    "ACT.ANALYZE.PATTERN": "gpt-4o",
    "ACT.CREATE.TEXT": "gpt-4o",
    "ACT.QUERY.DATA": "gpt-4o-mini",
    "ACT.TRANSFORM.TRANSLATE": "gpt-4o-mini",
    "ACT.TRANSFORM.SUMMARIZE": "gpt-4o-mini",
}


class OpenAIAdapter(PulseAdapter):
    """PULSE adapter for OpenAI API.

    Translates PULSE semantic actions to OpenAI chat completions.
    Any PULSE agent can use this adapter to access OpenAI models
    without knowing anything about the OpenAI API.

    Supported PULSE actions:
        - ACT.QUERY.DATA — ask a question, get an answer
        - ACT.CREATE.TEXT — generate text from instructions
        - ACT.ANALYZE.SENTIMENT — analyze text sentiment
        - ACT.ANALYZE.PATTERN — find patterns in data
        - ACT.TRANSFORM.TRANSLATE — translate text
        - ACT.TRANSFORM.SUMMARIZE — summarize text

    Args:
        api_key: OpenAI API key
        model: Default model to use (default: gpt-4o-mini)
        base_url: OpenAI API base URL (for proxies/compatible APIs)
        config: Additional configuration

    Example:
        >>> adapter = OpenAIAdapter(api_key="sk-...")
        >>> msg = PulseMessage(
        ...     action="ACT.ANALYZE.SENTIMENT",
        ...     parameters={"text": "I love this product!"}
        ... )
        >>> response = adapter.send(msg)
        >>> print(response.content["parameters"]["result"])
    """

    def __init__(
        self,
        api_key: Optional[str] = None,
        model: str = "gpt-4o-mini",
        base_url: Optional[str] = None,
        config: Optional[Dict[str, Any]] = None,
    ) -> None:
        super().__init__(
            name="openai",
            base_url=base_url or "https://api.openai.com",
            config=config or {},
        )
        self.default_model = model
        self._client: Optional[OpenAI] = None
        self._api_key = api_key

    def connect(self) -> None:
        """Initialize OpenAI client.

        Raises:
            AdapterConnectionError: If API key is missing
        """
        if not self._api_key:
            raise AdapterConnectionError(
                "OpenAI API key required. Pass api_key= or set OPENAI_API_KEY env var."
            )

        try:
            self._client = OpenAI(
                api_key=self._api_key,
                base_url=self.base_url if self.base_url != "https://api.openai.com" else None,
            )
            self.connected = True
        except Exception as e:
            raise AdapterConnectionError(f"Failed to initialize OpenAI client: {e}") from e

    def disconnect(self) -> None:
        """Close OpenAI client."""
        self._client = None
        self.connected = False

    def to_native(self, message: PulseMessage) -> Dict[str, Any]:
        """Convert PULSE message to OpenAI chat completion request.

        Args:
            message: PULSE message with action and parameters

        Returns:
            Dictionary ready for OpenAI chat.completions.create()

        Raises:
            AdapterError: If message cannot be converted
        """
        action = message.content["action"]
        params = message.content.get("parameters", {})

        # Build the user message from parameters
        user_content = self._build_user_content(action, params)

        # Build system prompt based on action
        system_prompt = self._build_system_prompt(action, params)

        # Select model
        model = params.get("model", DEFAULT_MODELS.get(action, self.default_model))

        # Build the request
        messages = []
        if system_prompt:
            messages.append({"role": "system", "content": system_prompt})
        messages.append({"role": "user", "content": user_content})

        request = {
            "model": model,
            "messages": messages,
        }

        # Optional parameters
        if "temperature" in params:
            request["temperature"] = params["temperature"]
        if "max_tokens" in params:
            request["max_tokens"] = params["max_tokens"]

        return request

    def call_api(self, native_request: Dict[str, Any]) -> Dict[str, Any]:
        """Call OpenAI chat completions API.

        Args:
            native_request: OpenAI-formatted request dict

        Returns:
            Dictionary with response data

        Raises:
            AdapterConnectionError: If OpenAI is unreachable
            AdapterError: If API call fails
        """
        if not self._client:
            self.connect()

        try:
            response = self._client.chat.completions.create(**native_request)

            return {
                "content": response.choices[0].message.content,
                "model": response.model,
                "usage": {
                    "prompt_tokens": response.usage.prompt_tokens,
                    "completion_tokens": response.usage.completion_tokens,
                    "total_tokens": response.usage.total_tokens,
                },
                "finish_reason": response.choices[0].finish_reason,
            }

        except AuthenticationError as e:
            raise AdapterError(
                f"OpenAI authentication failed. Check your API key. {e}"
            ) from e
        except RateLimitError as e:
            raise AdapterError(
                f"OpenAI rate limit exceeded. Retry later. {e}"
            ) from e
        except APIConnectionError as e:
            raise AdapterConnectionError(
                f"Cannot reach OpenAI API: {e}"
            ) from e
        except APIError as e:
            error_code = self.map_error_code(e.status_code) if e.status_code else "META.ERROR.UNKNOWN"
            raise AdapterError(
                f"OpenAI API error ({error_code}): {e}"
            ) from e

    def from_native(self, native_response: Dict[str, Any]) -> PulseMessage:
        """Convert OpenAI response to PULSE message.

        Args:
            native_response: Response dict from call_api()

        Returns:
            PULSE response message with result and metadata
        """
        return PulseMessage(
            action="ACT.RESPOND",
            parameters={
                "result": native_response["content"],
                "model": native_response["model"],
                "usage": native_response["usage"],
                "finish_reason": native_response["finish_reason"],
            },
            validate=False,
        )

    @property
    def supported_actions(self) -> List[str]:
        """Actions this adapter supports."""
        return [
            "ACT.QUERY.DATA",
            "ACT.CREATE.TEXT",
            "ACT.ANALYZE.SENTIMENT",
            "ACT.ANALYZE.PATTERN",
            "ACT.TRANSFORM.TRANSLATE",
            "ACT.TRANSFORM.SUMMARIZE",
        ]

    def _build_user_content(self, action: str, params: Dict[str, Any]) -> str:
        """Build user message content from PULSE parameters.

        Args:
            action: PULSE action concept
            params: Message parameters

        Returns:
            String content for the user message

        Raises:
            AdapterError: If required parameters are missing
        """
        # Direct query
        if "query" in params:
            return params["query"]

        # Text for analysis/transformation
        if "text" in params:
            text = params["text"]
            if action == "ACT.TRANSFORM.TRANSLATE":
                target_lang = params.get("target_language", "English")
                return f"Translate to {target_lang}:\n\n{text}"
            return text

        # Instructions for generation
        if "instructions" in params:
            return params["instructions"]

        # Data for pattern analysis
        if "data" in params:
            return f"Analyze this data:\n\n{params['data']}"

        # Prompt as fallback
        if "prompt" in params:
            return params["prompt"]

        raise AdapterError(
            f"Missing content parameter. For {action}, provide one of: "
            "'query', 'text', 'instructions', 'data', or 'prompt'."
        )

    def _build_system_prompt(self, action: str, params: Dict[str, Any]) -> Optional[str]:
        """Build system prompt based on PULSE action.

        Args:
            action: PULSE action concept
            params: Message parameters

        Returns:
            System prompt string, or None for general queries
        """
        # Custom system prompt overrides everything
        if "system_prompt" in params:
            return params["system_prompt"]

        # Action-specific prompts
        if action in ACTION_PROMPTS:
            return ACTION_PROMPTS[action]

        # General query — no system prompt needed
        return None

    def __repr__(self) -> str:
        """Return string representation."""
        return (
            f"OpenAIAdapter(model='{self.default_model}', "
            f"connected={self.connected})"
        )
