"""Prompts resource for SDK-first local execution."""

from __future__ import annotations

import re
import time
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Dict, Optional

import httpx

from .wrappers.base import calculate_cost

if TYPE_CHECKING:
    from .client import Mandatum


@dataclass
class PromptRunResponse:
    """Response from running a prompt locally."""

    output: str
    model: str
    provider: str
    input_tokens: int
    output_tokens: int
    cost: float
    latency_ms: int
    prompt_id: str
    version: int
    request_id: Optional[str] = None


@dataclass
class PromptTemplate:
    """Template data fetched from Mandatum."""

    prompt_id: str
    prompt_name: str
    version: int
    version_id: str
    content: str
    variables: Optional[Dict[str, Any]]
    provider: Optional[str]
    model: Optional[str]
    temperature: Optional[float]
    max_tokens: Optional[int]


class PromptsResource:
    """
    Prompts resource for fetching and executing prompts locally.

    This enables SDK-first architecture where LLM API keys stay on the user's
    system and never touch Mandatum servers.
    """

    def __init__(self, client: "Mandatum"):
        self._client = client
        self._api_url = self._build_api_url()

    def _build_api_url(self) -> str:
        base = self._client.base_url
        if not base.endswith("/api/v1"):
            base = f"{base}/api/v1"
        return base

    def _get_headers(self) -> Dict[str, str]:
        return {
            "X-API-Key": self._client.api_key,
            "Content-Type": "application/json",
        }

    def get_template(
        self,
        prompt_id: str,
        version: Optional[int] = None,
        label: Optional[str] = None,
    ) -> PromptTemplate:
        """
        Fetch a prompt template from Mandatum.

        Args:
            prompt_id: The prompt ID
            version: Specific version number (optional)
            label: Version label like 'production' (optional)

        Returns:
            PromptTemplate with all data needed for local execution
        """
        if label:
            url = f"{self._api_url}/prompts/{prompt_id}/versions/by-label/{label}/template"
        elif version:
            url = f"{self._api_url}/prompts/{prompt_id}/versions/{version}/template"
        else:
            url = f"{self._api_url}/prompts/{prompt_id}/template"

        with httpx.Client(timeout=10.0) as http_client:
            response = http_client.get(url, headers=self._get_headers())
            response.raise_for_status()
            data = response.json()

        return PromptTemplate(
            prompt_id=data["prompt_id"],
            prompt_name=data["prompt_name"],
            version=data["version"],
            version_id=data["version_id"],
            content=data["content"],
            variables=data.get("variables"),
            provider=data.get("provider"),
            model=data.get("model"),
            temperature=data.get("temperature"),
            max_tokens=data.get("max_tokens"),
        )

    def _interpolate_variables(self, content: str, variables: Dict[str, str]) -> str:
        """Replace {{variable}} placeholders with values."""
        result = content
        for key, value in variables.items():
            pattern = r"\{\{\s*" + re.escape(key) + r"\s*\}\}"
            result = re.sub(pattern, str(value), result)
        return result

    def _execute_openai(
        self,
        client: Any,
        system_prompt: str,
        user_input: str,
        model: str,
        temperature: float,
        max_tokens: int,
    ) -> tuple[str, int, int, int]:
        """Execute using OpenAI client."""
        start_time = time.time()

        response = client.chat.completions.create(
            model=model,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_input},
            ],
            temperature=temperature,
            max_tokens=max_tokens,
        )

        latency_ms = int((time.time() - start_time) * 1000)
        content = response.choices[0].message.content
        input_tokens = response.usage.prompt_tokens
        output_tokens = response.usage.completion_tokens

        return content, input_tokens, output_tokens, latency_ms

    def _execute_anthropic(
        self,
        client: Any,
        system_prompt: str,
        user_input: str,
        model: str,
        temperature: float,
        max_tokens: int,
    ) -> tuple[str, int, int, int]:
        """Execute using Anthropic client."""
        start_time = time.time()

        response = client.messages.create(
            model=model,
            system=system_prompt,
            messages=[{"role": "user", "content": user_input}],
            temperature=temperature,
            max_tokens=max_tokens,
        )

        latency_ms = int((time.time() - start_time) * 1000)
        content = response.content[0].text
        input_tokens = response.usage.input_tokens
        output_tokens = response.usage.output_tokens

        return content, input_tokens, output_tokens, latency_ms

    def _detect_client_type(self, client: Any) -> str:
        """Detect the LLM client type."""
        module = type(client).__module__
        if "openai" in module.lower():
            return "openai"
        elif "anthropic" in module.lower():
            return "anthropic"
        else:
            raise ValueError(
                f"Unsupported LLM client type: {type(client).__name__}. "
                "Supported: OpenAI, Anthropic"
            )

    def _log_to_mandatum(
        self,
        template: PromptTemplate,
        provider: str,
        model: str,
        user_input: str,
        interpolated_content: str,
        output: str,
        input_tokens: int,
        output_tokens: int,
        cost: float,
        latency_ms: int,
        environment: Optional[str],
        tags: Optional[list],
        metadata: Optional[Dict[str, Any]],
    ) -> Optional[str]:
        """Log the request to Mandatum and return request_id."""
        try:
            url = f"{self._api_url}/llm/track-request"

            payload = {
                "provider": provider,
                "model": model,
                "messages": [
                    {"role": "system", "content": interpolated_content},
                    {"role": "user", "content": user_input},
                ],
                "response": output,
                "input_tokens": input_tokens,
                "output_tokens": output_tokens,
                "cost": cost,
                "latency_ms": latency_ms,
                "prompt_id": template.prompt_id,
                "prompt_version_id": template.version_id,
                "environment": environment,
                "tags": tags or [],
                "metadata": metadata or {},
                "status": "success",
            }

            with httpx.Client(timeout=5.0) as http_client:
                response = http_client.post(
                    url, json=payload, headers=self._get_headers()
                )
                response.raise_for_status()
                return response.json().get("request_id")

        except Exception as e:
            if self._client.debug:
                print(f"[Mandatum] Failed to log request: {e}")
            return None

    def run(
        self,
        prompt_id: str,
        user_input: str,
        llm_client: Any,
        variables: Optional[Dict[str, str]] = None,
        version: Optional[int] = None,
        label: Optional[str] = None,
        model: Optional[str] = None,
        temperature: Optional[float] = None,
        max_tokens: Optional[int] = None,
        environment: Optional[str] = None,
        tags: Optional[list] = None,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> PromptRunResponse:
        """
        Fetch a prompt template and execute it locally with your LLM client.

        Args:
            prompt_id: The Mandatum prompt ID
            user_input: User message to send with the prompt
            llm_client: Your LLM client (OpenAI or Anthropic)
            variables: Variable values to interpolate in the template
            version: Specific version number (optional)
            label: Version label like 'production' (optional)
            model: Override the template's model
            temperature: Override the template's temperature
            max_tokens: Override the template's max_tokens
            environment: Environment tag for logging
            tags: Additional tags for logging
            metadata: Additional metadata for logging

        Returns:
            PromptRunResponse with the LLM output and execution details
        """
        template = self.get_template(prompt_id, version=version, label=label)

        interpolated_content = template.content
        if variables:
            interpolated_content = self._interpolate_variables(
                template.content, variables
            )

        use_model = model or template.model
        use_temperature = (
            temperature if temperature is not None else (template.temperature or 0.7)
        )
        use_max_tokens = (
            max_tokens if max_tokens is not None else (template.max_tokens or 1000)
        )

        if not use_model:
            raise ValueError(
                "Model must be specified either in the template or as a parameter"
            )

        client_type = self._detect_client_type(llm_client)

        if client_type == "openai":
            output, input_tokens, output_tokens, latency_ms = self._execute_openai(
                llm_client,
                interpolated_content,
                user_input,
                use_model,
                use_temperature,
                use_max_tokens,
            )
            provider = "openai"
        elif client_type == "anthropic":
            output, input_tokens, output_tokens, latency_ms = self._execute_anthropic(
                llm_client,
                interpolated_content,
                user_input,
                use_model,
                use_temperature,
                use_max_tokens,
            )
            provider = "anthropic"
        else:
            raise ValueError(f"Unsupported client type: {client_type}")

        cost = calculate_cost(provider, use_model, input_tokens, output_tokens)

        request_id = None
        if self._client.async_logging:
            request_id = self._log_to_mandatum(
                template=template,
                provider=provider,
                model=use_model,
                user_input=user_input,
                interpolated_content=interpolated_content,
                output=output,
                input_tokens=input_tokens,
                output_tokens=output_tokens,
                cost=cost,
                latency_ms=latency_ms,
                environment=environment,
                tags=tags,
                metadata=metadata,
            )

        return PromptRunResponse(
            output=output,
            model=use_model,
            provider=provider,
            input_tokens=input_tokens,
            output_tokens=output_tokens,
            cost=cost,
            latency_ms=latency_ms,
            prompt_id=template.prompt_id,
            version=template.version,
            request_id=request_id,
        )
