"""
SDK entry point for interacting with the LightReach/Compress API.

v0.2.0 is a breaking release:
- `complete()` is messages-first (defaults to async job endpoints; `mode="sync"` uses POST /api/v2/complete)
- Provider API keys are not accepted by this SDK (BYOK via dashboard)
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Dict, List, Literal, Optional, Sequence, Union

from .api_client import PcompresslrAPIClient, APIKeyError, RateLimitError, APIRequestError


MessageRole = Literal["system", "developer", "user", "assistant"]


@dataclass(frozen=True)
class Message:
    """One chat message."""

    role: MessageRole
    content: str

    def to_dict(self) -> Dict[str, str]:
        return {"role": self.role, "content": self.content}


@dataclass(frozen=True)
class CompressionConfig:
    """Per-role compression configuration (matches backend `CompleteRequest.CompressionConfig`)."""

    compress_system: bool = False
    compress_user: bool = True
    compress_assistant: bool = False
    compress_only_last_n_user: Optional[int] = 1

    def to_dict(self) -> Dict[str, Any]:
        return {
            "compress_system": self.compress_system,
            "compress_user": self.compress_user,
            "compress_assistant": self.compress_assistant,
            "compress_only_last_n_user": self.compress_only_last_n_user,
        }


class LightReach:
    """
    LightReach SDK client.

    Authentication uses your LightReach/Compress API key via `X-API-Key`
    (env var: `PCOMPRESLR_API_KEY`).
    """

    def __init__(
        self,
        api_key: Optional[str] = None,
        api_url: Optional[str] = None,
        *,
        default_model: str = "gpt-4",
        default_provider: Literal["openai", "anthropic", "google"] = "openai",
    ):
        self.default_model = default_model
        self.default_provider = default_provider
        self.api_client = PcompresslrAPIClient(api_key=api_key, api_url=api_url)

    def get_api_url(self) -> str:
        return self.api_client.api_url

    def complete(
        self,
        messages: Sequence[Union[Message, Dict[str, Any]]],
        *,
        provider: Optional[Literal["openai", "anthropic", "google"]] = None,
        desired_hle: Optional[float] = None,
        compress: bool = True,
        compression_config: Optional[Union[CompressionConfig, Dict[str, Any]]] = None,
        compress_output: bool = False,
        mode: Literal["async", "sync"] = "async",
        wait: bool = True,
        poll_interval_s: float = 1.0,
        max_wait_s: Optional[float] = None,
        # DEPRECATED HLE parameters (ignored)
        hle_target_percent: Optional[float] = None,
        min_hle_score: Optional[float] = None,
        auto_select_by_hle: Optional[bool] = None,
        same_provider_only: Optional[bool] = None,
        temperature: Optional[float] = None,
        max_tokens: Optional[int] = None,
        tags: Optional[Dict[str, str]] = None,
        max_history_messages: Optional[int] = None,
        # DEPRECATED (ignored)
        model: Optional[str] = None,
    ) -> Dict[str, Any]:
        """
        Complete a conversation with compression and intelligent model routing.

        Default (`mode="async"`): enqueue + poll async job endpoints.
        Legacy (`mode="sync"`): call POST /api/v2/complete directly (best-effort).

        Model routing is managed by your workspace admin via the dashboard.
        The system selects the cheapest model that meets the admin-configured
        quality ceiling (global, tag-level, or integration-level).

        Args:
            messages: Conversation history (Message objects or dicts with role/content).
            provider: Optional provider constraint. Omit for cross-provider optimization.
            tags: Tags for cost attribution and quality ceilings. Supported keys:
                  'team', 'environment', 'feature'. The 'integration' tag is reserved for system use.

        Provider keys are not accepted here. If you've configured provider keys
        in the LightReach dashboard, the server will use them automatically.
        """

        normalized: List[Dict[str, Any]] = []
        for m in messages:
            if isinstance(m, Message):
                normalized.append(m.to_dict())
            elif isinstance(m, dict):
                normalized.append({"role": m.get("role"), "content": m.get("content")})
            else:
                raise TypeError("Each message must be a Message or dict")

        cfg_dict: Optional[Dict[str, Any]] = None
        if compression_config is not None:
            if isinstance(compression_config, CompressionConfig):
                cfg_dict = compression_config.to_dict()
            elif isinstance(compression_config, dict):
                cfg_dict = dict(compression_config)
            else:
                raise TypeError("compression_config must be a CompressionConfig or dict")

        try:
            kwargs: Dict[str, Any] = {
                "messages": normalized,
                "llm_provider": provider or self.default_provider,
                "desired_hle": desired_hle,
                "compress": compress,
                "compression_config": cfg_dict,
                "compress_output": compress_output,
                "algorithm": "greedy",
                "mode": mode,
                "wait": wait,
                "poll_interval_s": poll_interval_s,
                "max_wait_s": max_wait_s,
                "temperature": temperature,
                "max_tokens": max_tokens,
                "tags": tags,
                "max_history_messages": max_history_messages,
            }
            # Only include deprecated/ignored parameters if explicitly set to avoid noisy warnings.
            if model is not None:
                kwargs["model"] = model
            if hle_target_percent is not None:
                kwargs["hle_target_percent"] = hle_target_percent
            if min_hle_score is not None:
                kwargs["min_hle_score"] = min_hle_score
            if auto_select_by_hle is not None:
                kwargs["auto_select_by_hle"] = auto_select_by_hle
            if same_provider_only is not None:
                kwargs["same_provider_only"] = same_provider_only

            resp = self.api_client.complete(**kwargs)
            # Add helpful aliases to better match the Feature 0.6 spec without breaking backend response.
            # We do NOT fabricate cost estimates here since the API response does not include pricing data.
            if isinstance(resp, dict):
                cs = resp.get("compression_stats") or {}
                ls = resp.get("llm_stats") or {}
                resp.setdefault("tokens_saved", cs.get("token_savings"))
                resp.setdefault("tokens_used", ls.get("total_tokens"))
                resp.setdefault("compression_ratio", cs.get("compression_ratio"))
                resp.setdefault("cost_estimate", None)
                resp.setdefault("savings_estimate", None)
            return resp
        except APIKeyError as e:
            raise APIKeyError(
                f"{str(e)}\n\n"
                "To get an API key, visit https://compress.lightreach.io or "
                "set the PCOMPRESLR_API_KEY environment variable."
            ) from e
        except RateLimitError as e:
            raise RateLimitError(
                f"{str(e)}\n\n"
                "You've exceeded your rate limit. Please wait before making more requests, "
                "or upgrade your subscription plan."
            ) from e
        except APIRequestError as e:
            raise APIRequestError(
                f"{str(e)}\n\n"
                "If this problem persists, please check https://compress.lightreach.io/status "
                "or contact support."
            ) from e

    def compress(
        self,
        text: str,
        *,
        model: Optional[str] = None,
        tags: Optional[Dict[str, str]] = None,
    ) -> Dict[str, Any]:
        """
        Compress text without making an LLM call (POST /api/v1/compress).

        Args:
            text: Text to compress.
            model: Model for tokenization (default: 'gpt-4').
            tags: Tags for attribution. Supported keys: 'team', 'environment', 'feature'.
        """

        return self.api_client.compress(prompt=text, model=model or self.default_model, algorithm="greedy", tags=tags)


# Backwards import name (the API is still a breaking change vs v0.1.x).
Pcompresslr = LightReach

