"""
BrainstormRouter Python SDK — AI gateway with memory, guardrails, and governance.

Drop-in replacement for the OpenAI Python SDK that adds typed access to
BrainstormRouter-specific APIs: memory, guardrails, observability, governance,
prompt management, presets, MCP gateway, and model leaderboard.

Usage::

    from brainstormrouter import BrainstormRouter
    client = BrainstormRouter(api_key="br_live_...")

    # OpenAI-compatible (works with LangChain, CrewAI, etc.)
    response = client.chat.completions.create(
        model="anthropic/claude-sonnet-4",
        messages=[{"role": "user", "content": "Hello"}],
    )

    # BrainstormRouter-specific
    client.providers.register("groq", api_key="gsk_...")
    entries = client.memory.entries()
    leaderboard = client.br_models.leaderboard()

Async usage::

    from brainstormrouter import AsyncBrainstormRouter
    client = AsyncBrainstormRouter(api_key="br_live_...")
    entries = await client.memory.entries()
"""

from __future__ import annotations

import json
import os
from dataclasses import dataclass
from typing import Any, Dict, Optional

from openai import AsyncOpenAI, OpenAI

from ._resource import BrainstormRouterError
from .resources.agent_profiles import AgentProfiles, AsyncAgentProfiles
from .resources.governance import AsyncGovernance, Governance
from .resources.guardrails import AsyncGuardrails, Guardrails
from .resources.mcp import MCP, AsyncMCP
from .resources.memory import AsyncMemory, Memory
from .resources.models import AsyncModels, Models
from .resources.observability import AsyncObservability, Observability
from .resources.presets import AsyncPresets, Presets
from .resources.prompts import AsyncPrompts, Prompts
from .resources.providers import AsyncProviders, Providers
from .resources.usage import AsyncUsage, Usage

DEFAULT_BASE_URL = "https://api.brainstormrouter.com"


# ── Guardian ──────────────────────────────────────────────────────────


@dataclass
class GuardianInfo:
    """Guardian Intelligence metadata from response headers."""

    status: Optional[str] = None
    estimated_cost: Optional[float] = None
    estimated_cost_cents: Optional[int] = None
    cache_hit: Optional[str] = None
    savings: Optional[float] = None
    cost_warning: Optional[str] = None
    efficiency: Optional[float] = None
    actual_cost: Optional[float] = None
    overhead_ms: Optional[float] = None


def _parse_guardian_headers(headers: Any) -> GuardianInfo:
    """Parse X-BR-* Guardian headers from response."""

    def _get(name: str) -> Optional[str]:
        try:
            return headers.get(name)
        except Exception:
            return None

    def _float(val: Optional[str]) -> Optional[float]:
        if not val:
            return None
        try:
            return float(val.replace("$", ""))
        except ValueError:
            return None

    def _int(val: Optional[str]) -> Optional[int]:
        if not val:
            return None
        try:
            return int(val)
        except ValueError:
            return None

    return GuardianInfo(
        status=_get("x-br-guardian-status"),
        estimated_cost=_float(_get("x-br-estimated-cost")),
        estimated_cost_cents=_int(_get("x-br-estimated-cost-cents")),
        cache_hit=_get("x-br-cache-hit"),
        savings=_float(_get("x-br-savings")),
        cost_warning=_get("x-br-cost-warning"),
        efficiency=_float(_get("x-br-efficiency")),
        actual_cost=_float(_get("x-br-actual-cost")),
        overhead_ms=_float(_get("x-br-guardian-overhead-ms")),
    )


def parse_guardian_stream_done(data: str) -> Optional[GuardianInfo]:
    """Parse Guardian payload from SSE [DONE] event.

    The [DONE] line may contain: [DONE] {"guardian":{...}}
    """
    if not data.startswith("[DONE]"):
        return None
    json_part = data[len("[DONE]"):].strip()
    if not json_part:
        return None

    try:
        parsed = json.loads(json_part)
        g = parsed.get("guardian", {})
        return GuardianInfo(
            efficiency=g.get("efficiency"),
            actual_cost=g.get("actualCost"),
            estimated_cost=g.get("estimatedCost"),
            cache_hit=g.get("cacheHit"),
            savings=g.get("savings"),
        )
    except (json.JSONDecodeError, KeyError):
        return None


# ── Sync Client ───────────────────────────────────────────────────────


class BrainstormRouter(OpenAI):
    """BrainstormRouter client — OpenAI SDK with Guardian Intelligence + typed resources.

    Args:
        api_key: BrainstormRouter API key (br_live_... or br_test_...).
            Falls back to BRAINSTORMROUTER_API_KEY env var.
        base_url: API base URL. Defaults to https://api.brainstormrouter.com.
        max_estimated_cost: Max estimated cost per request in USD.
        guardian_off: Disable Guardian for all requests.
        **kwargs: Passed to OpenAI client.
    """

    providers: Providers
    presets: Presets
    prompts: Prompts
    memory: Memory
    guardrails: Guardrails
    observability: Observability
    governance: Governance
    mcp: MCP
    br_models: Models
    agent_profiles: AgentProfiles
    usage: Usage

    def __init__(
        self,
        *,
        api_key: Optional[str] = None,
        base_url: str = DEFAULT_BASE_URL,
        max_estimated_cost: Optional[float] = None,
        guardian_off: bool = False,
        client_cert: Optional[str] = None,
        client_key: Optional[str] = None,
        **kwargs: Any,
    ):
        resolved_key = api_key or os.environ.get("BRAINSTORMROUTER_API_KEY", "")

        default_headers: Dict[str, str] = {}
        if guardian_off:
            default_headers["X-BR-Guardian"] = "off"
        if max_estimated_cost is not None:
            default_headers["X-BR-Max-Estimated-Cost"] = str(max_estimated_cost)

        super().__init__(
            api_key=resolved_key,
            base_url=base_url,
            default_headers=default_headers,
            **kwargs,
        )

        self._br_api_key = resolved_key
        self._br_base_url = base_url

        def headers() -> Dict[str, str]:
            h: Dict[str, str] = {}
            if self._br_api_key:
                h["Authorization"] = f"Bearer {self._br_api_key}"
            return h

        self.providers = Providers(base_url, headers)
        self.presets = Presets(base_url, headers)
        self.prompts = Prompts(base_url, headers)
        self.memory = Memory(base_url, headers)
        self.guardrails = Guardrails(base_url, headers)
        self.observability = Observability(base_url, headers)
        self.governance = Governance(base_url, headers)
        self.mcp = MCP(base_url, headers)
        self.br_models = Models(base_url, headers)
        self.agent_profiles = AgentProfiles(base_url, headers)
        self.usage = Usage(base_url, headers)

    def create_guardian_completion(
        self,
        extra_headers: Optional[Dict[str, str]] = None,
        **kwargs: Any,
    ) -> Any:
        """Create a chat completion with Guardian headers parsed.

        Returns a tuple of (ChatCompletion, GuardianInfo).
        """
        headers: Dict[str, str] = {}
        if extra_headers:
            headers.update(extra_headers)

        response = self.chat.completions.with_raw_response.create(
            extra_headers=headers, **kwargs
        )

        guardian = _parse_guardian_headers(response.headers)
        completion = response.parse()

        return completion, guardian


# ── Async Client ──────────────────────────────────────────────────────


class AsyncBrainstormRouter(AsyncOpenAI):
    """Async BrainstormRouter client with the same API as the sync version."""

    providers: AsyncProviders
    presets: AsyncPresets
    prompts: AsyncPrompts
    memory: AsyncMemory
    guardrails: AsyncGuardrails
    observability: AsyncObservability
    governance: AsyncGovernance
    mcp: AsyncMCP
    br_models: AsyncModels
    agent_profiles: AsyncAgentProfiles
    usage: AsyncUsage

    def __init__(
        self,
        *,
        api_key: Optional[str] = None,
        base_url: str = DEFAULT_BASE_URL,
        max_estimated_cost: Optional[float] = None,
        guardian_off: bool = False,
        client_cert: Optional[str] = None,
        client_key: Optional[str] = None,
        **kwargs: Any,
    ):
        resolved_key = api_key or os.environ.get("BRAINSTORMROUTER_API_KEY", "")

        default_headers: Dict[str, str] = {}
        if guardian_off:
            default_headers["X-BR-Guardian"] = "off"
        if max_estimated_cost is not None:
            default_headers["X-BR-Max-Estimated-Cost"] = str(max_estimated_cost)

        super().__init__(
            api_key=resolved_key,
            base_url=base_url,
            default_headers=default_headers,
            **kwargs,
        )

        self._br_api_key = resolved_key
        self._br_base_url = base_url

        def headers() -> Dict[str, str]:
            h: Dict[str, str] = {}
            if self._br_api_key:
                h["Authorization"] = f"Bearer {self._br_api_key}"
            return h

        self.providers = AsyncProviders(base_url, headers)
        self.presets = AsyncPresets(base_url, headers)
        self.prompts = AsyncPrompts(base_url, headers)
        self.memory = AsyncMemory(base_url, headers)
        self.guardrails = AsyncGuardrails(base_url, headers)
        self.observability = AsyncObservability(base_url, headers)
        self.governance = AsyncGovernance(base_url, headers)
        self.mcp = AsyncMCP(base_url, headers)
        self.br_models = AsyncModels(base_url, headers)
        self.agent_profiles = AsyncAgentProfiles(base_url, headers)
        self.usage = AsyncUsage(base_url, headers)


# ── Tool Firewall Refusal Detection ──────────────────────────────────


class ToolBlockedError(Exception):
    """Raised when the tool firewall blocks a tool call mid-stream."""

    def __init__(self, reason: str):
        super().__init__(f"Tool blocked: {reason}")
        self.reason = reason


def is_tool_blocked(chunk: Any) -> Optional[str]:
    """Check if a streaming chunk is a synthetic denial from the tool firewall.

    Returns the denial reason string if blocked, None otherwise.

    The gateway emits a chunk with ``finish_reason="content_filter"`` and
    ``delta.content`` starting with ``[TOOL BLOCKED]`` when a tool call is denied.

    Usage::

        stream = client.chat.completions.create(stream=True, ...)
        for chunk in stream:
            reason = is_tool_blocked(chunk)
            if reason:
                print(f"Tool blocked: {reason}")
                continue
            # normal chunk processing
    """
    try:
        choices = getattr(chunk, "choices", None) or []
        if not choices:
            return None
        choice = choices[0]
        finish_reason = getattr(choice, "finish_reason", None)
        delta = getattr(choice, "delta", None)
        content = getattr(delta, "content", None) if delta else None
        if (
            finish_reason == "content_filter"
            and isinstance(content, str)
            and content.startswith("[TOOL BLOCKED]")
        ):
            return content[len("[TOOL BLOCKED] "):]
    except (IndexError, AttributeError):
        pass
    return None


__all__ = [
    "BrainstormRouter",
    "AsyncBrainstormRouter",
    "BrainstormRouterError",
    "GuardianInfo",
    "ToolBlockedError",
    "is_tool_blocked",
    "parse_guardian_stream_done",
]
