"""Takumi gateway provider.

OSS version - HTTP adapter for the Takumi Gateway API without local EB1 execution.

Forwards requests to the Takumi Gateway API which provides:
- Unified access to all major LLM providers (OpenAI, Anthropic, Google, Azure)
- Rate limiting, billing, and usage tracking
- Session affinity and intelligent routing
"""

from __future__ import annotations

import json
import logging
import os
import uuid
from contextlib import closing
from typing import Any, Dict, Generator, Mapping, MutableMapping, Optional, Sequence
from urllib.parse import urlparse

import requests

logger = logging.getLogger(__name__)

from ember._internal.exceptions import ProviderAPIError
from ember.models.providers.base import BaseProvider
from ember.models.providers.openai import usage_from_openai
from ember.models.providers.utils import (
    extract_first_choice_text,
    parse_usage_payload,
)
from ember.models.schemas import ChatResponse, UsageStats


class TakumiProvider(BaseProvider):
    """Takumi Gateway provider for unified LLM access."""

    requires_api_key = True
    supports_responses_api = True

    def __init__(
        self,
        api_key: str | None = None,
        base_url: str | None = None,
    ):
        """Initialize the Takumi provider.

        Args:
            api_key: Gateway API key (tkm_sk_*) override.
            base_url: Gateway base URL. Defaults to EMBER_TAKUMI_BASE_URL env var
                or the value in ``~/.ember/config.yaml``.
        """
        super().__init__(api_key)

        self.base_url = self._resolve_base_url(base_url)

        # Create a session for connection pooling and keep-alive
        self._session = requests.Session()
        adapter = requests.adapters.HTTPAdapter(
            pool_connections=10,
            pool_maxsize=10,
            max_retries=0,
            pool_block=False,
        )
        self._session.mount("http://", adapter)
        self._session.mount("https://", adapter)

    def _prepare_request(
        self,
        prompt: str,
        model: str,
        params: Dict[str, Any],
    ) -> tuple[Dict[str, Any], bool, Optional[float], str]:
        """Build request payload and extract shared arguments."""
        working = dict(params)
        messages = self._build_messages(prompt, working)
        payload: Dict[str, Any] = {"model": model, "messages": messages}

        stream_requested = bool(working.pop("stream", False))

        session_id = working.pop("session_id", None)
        if session_id is not None:
            payload["session_id"] = session_id

        policy = working.pop("policy", None)
        if policy is not None:
            if not isinstance(policy, dict):
                raise ValueError("policy must be a mapping when provided")
            payload["policy"] = policy

        passthrough_keys = (
            "temperature",
            "max_tokens",
            "top_p",
            "response_format",
            "tool_choice",
            "tools",
            "metadata",
            "modalities",
            "seed",
            "user",
            "reasoning",
            "text",
            "max_output_tokens",
        )
        for key in passthrough_keys:
            if key in working:
                value = working.pop(key)
                if value is not None:
                    payload[key] = value

        timeout_override = working.pop("timeout", None)
        if timeout_override is not None:
            try:
                timeout_override = float(timeout_override)
            except (TypeError, ValueError) as exc:
                raise ValueError("timeout must be numeric") from exc

        idempotency_key = working.pop("idempotency_key", None)
        if idempotency_key is None:
            idempotency_key = str(uuid.uuid4())

        if working:
            unsupported = ", ".join(sorted(working))
            raise ValueError(f"Unsupported TakumiProvider parameters: {unsupported}")

        return payload, stream_requested, timeout_override, idempotency_key

    def complete(self, prompt: str, model: str, **kwargs: Any) -> ChatResponse:
        """Generate completion via Takumi Gateway.

        Args:
            prompt: The user prompt
            model: Model identifier (e.g., 'gpt-4', 'claude-opus-4-1')
            **kwargs: Optional parameters:
                system: System message
                temperature: Sampling temperature (0-2)
                max_tokens: Maximum completion tokens
                stream: Enable streaming (bool, default False)
                session_id: Session affinity key
                policy: Request policy dict (allowed_providers, max_cost_usd, etc.)

        Returns:
            ChatResponse with text, usage stats, and metadata
        """
        payload, stream_requested, timeout_override, idempotency_key = self._prepare_request(
            prompt,
            model,
            kwargs,
        )

        if stream_requested:
            raise ProviderAPIError(
                "TakumiProvider.complete does not support stream=True; use stream_complete()",
                context={"model": model, "error_type": "invalid_parameter"},
            )

        timeout_plan = self._build_timeout_plan(
            prompt,
            payload,
            model=model,
            timeout_override=timeout_override,
        )

        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
            "Idempotency-Key": idempotency_key,
        }

        url = self._build_endpoint_url("/v1/chat/completions")

        resp: requests.Response | None = None
        attempts = len(timeout_plan)
        for attempt, (connect_timeout, read_timeout) in enumerate(timeout_plan, start=1):
            try:
                resp = self._session.post(
                    url,
                    json=payload,
                    headers=headers,
                    timeout=(connect_timeout, read_timeout),
                )
                break
            except requests.Timeout as exc:
                if attempt >= attempts:
                    raise ProviderAPIError(
                        (
                            "Gateway read timed out after "
                            f"{read_timeout:.0f}s (attempt {attempt} of {attempts}). "
                            "Pass timeout=... to override or ensure the gateway is responsive."
                        ),
                        context={
                            "model": model,
                            "error_type": "timeout",
                            "base_url": self.base_url,
                            "attempts": attempts,
                            "read_timeout_seconds": read_timeout,
                        },
                    ) from exc
                continue
            except requests.RequestException as exc:
                raise ProviderAPIError(
                    f"Gateway request failed: {exc}",
                    context={"model": model, "error_type": "network"},
                ) from exc

        if resp is None:
            raise ProviderAPIError(
                "Gateway request could not be completed (no response)",
                context={"model": model, "error_type": "unknown"},
            )

        if resp.status_code >= 400:
            context = {"model": model, "status": resp.status_code}
            if resp.status_code in (401, 403):
                context["error_type"] = "authentication"
                raise ProviderAPIError("Authentication failed", context=context)
            if resp.status_code == 429:
                context["error_type"] = "rate_limit"
                raise ProviderAPIError("Rate limit exceeded", context=context)
            if resp.status_code >= 500:
                context["error_type"] = "server"
                raise ProviderAPIError("Gateway error", context=context)

            context["error_type"] = "invalid_request"
            raise ProviderAPIError(f"Request failed: {resp.text}", context=context)

        try:
            data = resp.json()
        except ValueError as exc:
            raise ProviderAPIError(
                "Non-JSON response from gateway",
                context={"model": model, "error_type": "invalid_response"},
            ) from exc

        text_output = extract_first_choice_text(data, model=model)
        usage_stats = parse_usage_payload(data.get("usage"), model=model)

        return ChatResponse(
            data=text_output,
            usage=usage_stats,
            model_id=data.get("model") or model,
            raw_output=data,
        )

    def complete_responses_payload(
        self,
        payload: Mapping[str, Any],
        **kwargs: Any,
    ) -> ChatResponse:
        """Invoke Takumi Gateway's /v1/responses endpoint."""
        if not isinstance(payload, Mapping):
            raise ValueError("Responses payload must be a mapping")

        request_payload: Dict[str, Any] = dict(payload)
        request_payload.pop("stream", None)
        request_payload.pop("timeout", None)

        timeout_override = kwargs.pop("timeout", None)
        idempotency_key = kwargs.pop("idempotency_key", None) or request_payload.pop(
            "idempotency_key", None
        )
        if kwargs:
            unsupported = ", ".join(sorted(kwargs))
            raise ValueError(f"Unsupported Takumi Responses overrides: {unsupported}")

        model = str(request_payload.get("model") or "").strip()
        if not model:
            raise ValueError("Responses payload must include 'model'")

        timeout_payload = dict(request_payload)
        if "max_output_tokens" in request_payload and "max_tokens" not in timeout_payload:
            timeout_payload["max_tokens"] = request_payload["max_output_tokens"]

        prompt_summary = self._summarize_responses_prompt(request_payload)
        timeout_plan = self._build_timeout_plan(
            prompt_summary,
            timeout_payload,
            model=model,
            timeout_override=timeout_override,
        )

        if not idempotency_key:
            idempotency_key = str(uuid.uuid4())

        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
            "Idempotency-Key": idempotency_key,
        }

        url = self._build_endpoint_url("/v1/responses")

        resp: requests.Response | None = None
        attempts = len(timeout_plan)
        for attempt, (connect_timeout, read_timeout) in enumerate(timeout_plan, start=1):
            try:
                resp = self._session.post(
                    url,
                    json=request_payload,
                    headers=headers,
                    timeout=(connect_timeout, read_timeout),
                )
                break
            except requests.Timeout as exc:
                if attempt >= attempts:
                    raise ProviderAPIError(
                        (
                            "Gateway read timed out after "
                            f"{read_timeout:.0f}s (attempt {attempt} of {attempts}). "
                            "Pass timeout=... to override or ensure the gateway is responsive."
                        ),
                        context={
                            "model": model,
                            "error_type": "timeout",
                            "base_url": self.base_url,
                            "attempts": attempts,
                            "read_timeout_seconds": read_timeout,
                        },
                    ) from exc
                continue
            except requests.RequestException as exc:
                raise ProviderAPIError(
                    f"Gateway request failed: {exc}",
                    context={"model": model, "error_type": "network"},
                ) from exc

        if resp is None:
            raise ProviderAPIError(
                "Gateway request could not be completed (no response)",
                context={"model": model, "error_type": "unknown"},
            )

        if resp.status_code >= 400:
            context = {"model": model, "status": resp.status_code}
            if resp.status_code in (401, 403):
                context["error_type"] = "authentication"
                raise ProviderAPIError("Authentication failed", context=context)
            if resp.status_code == 429:
                context["error_type"] = "rate_limit"
                raise ProviderAPIError("Rate limit exceeded", context=context)
            if resp.status_code >= 500:
                context["error_type"] = "server"
                raise ProviderAPIError("Gateway error", context=context)

            context["error_type"] = "invalid_request"
            raise ProviderAPIError(f"Request failed: {resp.text}", context=context)

        try:
            data = resp.json()
        except ValueError as exc:
            raise ProviderAPIError(
                "Non-JSON response from gateway",
                context={"model": model, "error_type": "invalid_response"},
            ) from exc

        usage_stats = usage_from_openai(data.get("usage"))
        output_items = self._normalize_responses_output(data.get("output"))

        text_output = ""
        if isinstance(data.get("output_text"), str):
            text_output = str(data["output_text"])
        elif output_items:
            text_output = self._aggregate_responses_text(output_items)

        response_id = data.get("id")
        resolved_model = data.get("model") or model

        return ChatResponse(
            data=text_output,
            output=output_items,
            response_id=str(response_id) if isinstance(response_id, str) else None,
            usage=usage_stats,
            model_id=str(resolved_model),
            raw_output=data,
        )

    def stream_complete(
        self,
        prompt: str,
        model: str,
        **kwargs: Any,
    ) -> Generator[str, None, ChatResponse]:
        """Stream completion results via Takumi Gateway SSE."""
        payload, stream_requested, timeout_override, idempotency_key = self._prepare_request(
            prompt,
            model,
            kwargs,
        )

        if stream_requested:
            raise ProviderAPIError(
                "Do not pass stream=True to stream_complete; streaming is implied",
                context={"model": model, "error_type": "invalid_parameter"},
            )

        timeout_plan = self._build_timeout_plan(
            prompt,
            payload,
            model=model,
            timeout_override=timeout_override,
        )

        payload["stream"] = True
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
            "Accept": "text/event-stream",
            "Idempotency-Key": idempotency_key,
        }
        url = self._build_endpoint_url("/v1/chat/completions")

        response: requests.Response | None = None
        attempts = len(timeout_plan)
        for attempt, (connect_timeout, read_timeout) in enumerate(timeout_plan, start=1):
            try:
                response = self._session.post(
                    url,
                    json=payload,
                    headers=headers,
                    timeout=(connect_timeout, read_timeout),
                    stream=True,
                )
                break
            except requests.Timeout as exc:
                if attempt >= attempts:
                    raise ProviderAPIError(
                        (
                            "Gateway read timed out after "
                            f"{read_timeout:.0f}s (attempt {attempt} of {attempts}). "
                            "Pass timeout=... to override or ensure the gateway is responsive."
                        ),
                        context={
                            "model": model,
                            "error_type": "timeout",
                            "base_url": self.base_url,
                            "attempts": attempts,
                            "read_timeout_seconds": read_timeout,
                        },
                    ) from exc
                continue
            except requests.RequestException as exc:
                raise ProviderAPIError(
                    f"Gateway request failed: {exc}",
                    context={"model": model, "error_type": "network"},
                ) from exc

        if response is None:
            raise ProviderAPIError(
                "Gateway request could not be completed (no response)",
                context={"model": model, "error_type": "unknown"},
            )

        if response.status_code >= 400:
            context = {"model": model, "status": response.status_code}
            if response.status_code in (401, 403):
                context["error_type"] = "authentication"
                raise ProviderAPIError("Authentication failed", context=context)
            if response.status_code == 429:
                context["error_type"] = "rate_limit"
                raise ProviderAPIError("Rate limit exceeded", context=context)
            if response.status_code >= 500:
                context["error_type"] = "server"
                raise ProviderAPIError("Gateway error", context=context)

            body_preview = response.text
            context["error_type"] = "invalid_request"
            raise ProviderAPIError(f"Request failed: {body_preview}", context=context)

        def _generator() -> Generator[str, None, ChatResponse]:
            header_payload: Dict[str, Any] | None = None
            text_chunks: list[str] = []
            usage_totals = {"prompt": 0, "completion": 0, "total": 0}
            raw_events: list[Dict[str, Any]] = []

            with closing(response):
                for event_type, data_str, event_id in self._iter_sse_events(response):
                    if event_type == "heartbeat":
                        raw_events.append({"event": event_type, "id": event_id, "data": {}})
                        continue
                    if event_type == "done":
                        raw_events.append({"event": event_type, "id": event_id, "data": None})
                        break

                    parsed_data: Any = None
                    if data_str:
                        try:
                            parsed_data = json.loads(data_str)
                        except json.JSONDecodeError:
                            parsed_data = data_str

                    raw_events.append({"event": event_type, "id": event_id, "data": parsed_data})

                    if event_type == "header":
                        if isinstance(parsed_data, dict):
                            header_payload = parsed_data
                        continue

                    if not data_str:
                        continue

                    if isinstance(parsed_data, dict):
                        delta = parsed_data.get("delta")
                        if delta is None:
                            text_delta = parsed_data.get("text")
                            if isinstance(text_delta, str):
                                delta = text_delta
                            elif "arguments_delta" in parsed_data:
                                tool_delta: Dict[str, Any] = {
                                    "type": parsed_data.get("type") or "tool_call.delta",
                                    "arguments_delta": parsed_data["arguments_delta"],
                                }
                                if parsed_data.get("id"):
                                    tool_delta["id"] = parsed_data["id"]
                                if parsed_data.get("name"):
                                    tool_delta["name"] = parsed_data["name"]
                                delta = tool_delta
                        if isinstance(delta, str):
                            text_chunks.append(delta)
                            yield delta
                        elif isinstance(delta, dict):
                            chunk = json.dumps(delta)
                            text_chunks.append(chunk)
                            yield chunk
                        usage_delta = parsed_data.get("usage_delta")
                        if isinstance(usage_delta, dict):
                            try:
                                usage_totals["prompt"] += int(usage_delta.get("prompt_tokens", 0))
                                usage_totals["completion"] += int(
                                    usage_delta.get("completion_tokens", 0)
                                )
                                usage_totals["total"] += int(usage_delta.get("total_tokens", 0))
                            except (TypeError, ValueError):
                                logger.debug(
                                    "Non-numeric usage delta; skipping: %s",
                                    usage_delta,
                                )
                    elif isinstance(parsed_data, str):
                        text_chunks.append(parsed_data)
                        yield parsed_data
                    else:
                        text_chunks.append(data_str)
                        yield data_str

            final_text = "".join(text_chunks)
            usage_stats = None
            if header_payload is not None:
                usage_stats = parse_usage_payload(header_payload.get("usage"), model=model)
            if usage_stats is None and any(usage_totals.values()):
                usage_stats = UsageStats(
                    prompt_tokens=usage_totals["prompt"],
                    completion_tokens=usage_totals["completion"],
                    total_tokens=usage_totals["total"],
                    actual_cost_usd=None,
                )

            header_model = None
            if header_payload and isinstance(header_payload, dict):
                candidate = header_payload.get("model")
                if isinstance(candidate, str) and candidate:
                    header_model = candidate

            raw_output: Dict[str, Any] = {"events": raw_events}
            if header_payload is not None:
                raw_output["header"] = header_payload

            return ChatResponse(
                data=final_text,
                usage=usage_stats,
                model_id=header_model or model,
                raw_output=raw_output,
            )

        return _generator()

    def stream_responses_payload(
        self,
        payload: Mapping[str, Any],
        **kwargs: Any,
    ) -> Generator[str, None, ChatResponse]:
        """Stream Responses API events from Takumi Gateway."""
        if not isinstance(payload, Mapping):
            raise ValueError("Responses payload must be a mapping")

        request_payload: Dict[str, Any] = dict(payload)
        request_payload.pop("timeout", None)
        request_payload["stream"] = True

        timeout_override = kwargs.pop("timeout", None)
        idempotency_key = kwargs.pop("idempotency_key", None) or request_payload.pop(
            "idempotency_key", None
        )

        if kwargs:
            unsupported = ", ".join(sorted(kwargs))
            raise ValueError(f"Unsupported Takumi Responses overrides: {unsupported}")

        model = str(request_payload.get("model") or "").strip()
        if not model:
            raise ValueError("Responses payload must include 'model'")

        timeout_payload = dict(request_payload)
        if "max_output_tokens" in request_payload and "max_tokens" not in timeout_payload:
            timeout_payload["max_tokens"] = request_payload["max_output_tokens"]

        prompt_summary = self._summarize_responses_prompt(request_payload)
        timeout_plan = self._build_timeout_plan(
            prompt_summary,
            timeout_payload,
            model=model,
            timeout_override=timeout_override,
        )

        if not idempotency_key:
            idempotency_key = str(uuid.uuid4())

        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
            "Accept": "text/event-stream",
            "Idempotency-Key": idempotency_key,
        }

        url = self._build_endpoint_url("/v1/responses")

        response: requests.Response | None = None
        attempts = len(timeout_plan)
        for attempt, (connect_timeout, read_timeout) in enumerate(timeout_plan, start=1):
            try:
                response = requests.post(
                    url,
                    json=request_payload,
                    headers=headers,
                    timeout=(connect_timeout, read_timeout),
                    stream=True,
                )
                break
            except requests.Timeout as exc:
                if attempt >= attempts:
                    raise ProviderAPIError(
                        (
                            "Gateway read timed out after "
                            f"{read_timeout:.0f}s (attempt {attempt} of {attempts}). "
                            "Pass timeout=... to override or ensure the gateway is responsive."
                        ),
                        context={
                            "model": model,
                            "error_type": "timeout",
                            "base_url": self.base_url,
                            "attempts": attempts,
                            "read_timeout_seconds": read_timeout,
                        },
                    ) from exc
                continue
            except requests.RequestException as exc:
                raise ProviderAPIError(
                    f"Gateway request failed: {exc}",
                    context={"model": model, "error_type": "network"},
                ) from exc

        if response is None:
            raise ProviderAPIError(
                "Gateway request could not be completed (no response)",
                context={"model": model, "error_type": "unknown"},
            )

        if response.status_code >= 400:
            body = ""
            try:
                body = response.text
            except Exception:
                body = "<unavailable>"
            context = {"model": model, "status": response.status_code}
            if response.status_code in (401, 403):
                context["error_type"] = "authentication"
                raise ProviderAPIError("Authentication failed", context=context)
            if response.status_code == 429:
                context["error_type"] = "rate_limit"
                raise ProviderAPIError("Rate limit exceeded", context=context)
            if response.status_code >= 500:
                context["error_type"] = "server"
                raise ProviderAPIError("Gateway error", context=context)
            context["error_type"] = "invalid_request"
            raise ProviderAPIError(f"Request failed: {body}", context=context)

        def _generator() -> Generator[str, None, ChatResponse]:
            assert response is not None
            parts: list[str] = []
            usage_totals = UsageStats()
            raw_events: list[Dict[str, Any]] = []
            final_payload: Dict[str, Any] | None = None

            try:
                for event_type, data_str, event_id in self._iter_sse_events(response):
                    if data_str is None:
                        continue

                    raw_event: Dict[str, Any] = {"data": data_str}
                    if event_type is not None:
                        raw_event["type"] = event_type
                    if event_id is not None:
                        raw_event["id"] = event_id
                    raw_events.append(raw_event)

                    text_fragment: Optional[str] = None
                    parsed_payload: Dict[str, Any] | None = None
                    try:
                        parsed_candidate = json.loads(data_str)
                        if isinstance(parsed_candidate, dict):
                            parsed_payload = parsed_candidate
                    except ValueError:
                        parsed_payload = None

                    effective_type = event_type
                    if parsed_payload is not None:
                        effective_type = str(parsed_payload.get("type") or event_type or "")
                        if effective_type == "response.error":
                            error_obj = parsed_payload.get("error")
                            if isinstance(error_obj, dict):
                                message = str(error_obj.get("message") or "stream error")
                            else:
                                message = str(error_obj or "stream error")
                            raise ProviderAPIError(
                                f"Takumi streaming error: {message}",
                                context={"model": model, "error_type": "stream_error"},
                            )
                        if effective_type == "response.completed":
                            response_block = parsed_payload.get("response")
                            if isinstance(response_block, dict):
                                final_payload = response_block
                            else:
                                final_payload = parsed_payload

                        delta_value = parsed_payload.get("delta")
                        if isinstance(delta_value, str):
                            text_fragment = delta_value
                        usage_delta = parsed_payload.get("usage_delta")
                        if not isinstance(usage_delta, dict):
                            data_section = parsed_payload.get("data")
                            if isinstance(data_section, dict):
                                usage_delta = data_section.get("usage_delta")
                        if isinstance(usage_delta, dict):
                            usage_totals.prompt_tokens += self._safe_int(
                                usage_delta.get("prompt_tokens") or usage_delta.get("input_tokens")
                            )
                            usage_totals.completion_tokens += self._safe_int(
                                usage_delta.get("completion_tokens")
                                or usage_delta.get("output_tokens")
                            )
                            total_hint = usage_delta.get("total_tokens")
                            if total_hint is not None:
                                usage_totals.total_tokens += self._safe_int(total_hint)
                            else:
                                usage_totals.total_tokens = (
                                    usage_totals.prompt_tokens + usage_totals.completion_tokens
                                )

                    if text_fragment:
                        parts.append(text_fragment)

                    yield data_str
            finally:
                response.close()

            final_usage = None
            final_output = None
            response_id: Optional[str] = None
            resolved_model = model

            if final_payload is not None:
                final_usage = usage_from_openai(final_payload.get("usage"))
                final_output = self._normalize_responses_output(final_payload.get("output"))
                candidate_id = final_payload.get("id")
                if isinstance(candidate_id, str):
                    response_id = candidate_id
                candidate_model = final_payload.get("model")
                if isinstance(candidate_model, str) and candidate_model:
                    resolved_model = candidate_model

            if final_usage is None and (
                usage_totals.prompt_tokens
                or usage_totals.completion_tokens
                or usage_totals.total_tokens
            ):
                final_usage = usage_totals

            final_text = "".join(parts)
            if not final_text and final_output:
                final_text = self._aggregate_responses_text(final_output)

            raw_output: Dict[str, Any] = {"events": raw_events}
            if final_payload is not None:
                raw_output["response"] = final_payload

            return ChatResponse(
                data=final_text,
                output=final_output,
                response_id=response_id,
                usage=final_usage,
                model_id=str(resolved_model),
                raw_output=raw_output,
            )

        return _generator()

    def _summarize_responses_prompt(self, payload: Mapping[str, Any]) -> str:
        parts: list[str] = []
        instructions = payload.get("instructions")
        if isinstance(instructions, str):
            parts.append(instructions)
        parts.extend(self._collect_input_texts(payload.get("input")))
        return " ".join(parts)[:2000]

    def _collect_input_texts(self, value: Any) -> list[str]:
        texts: list[str] = []
        if isinstance(value, str):
            texts.append(value)
            return texts
        if isinstance(value, bytes):
            texts.append(value.decode("utf-8", errors="ignore"))
            return texts

        if isinstance(value, Sequence):
            for entry in value:
                if isinstance(entry, Mapping):
                    content = entry.get("content")
                    if isinstance(content, str):
                        texts.append(content)
                    elif isinstance(content, bytes):
                        texts.append(content.decode("utf-8", errors="ignore"))
                    elif isinstance(content, Sequence):
                        for part in content:
                            if isinstance(part, Mapping):
                                text_val = part.get("text")
                                if isinstance(text_val, str):
                                    texts.append(text_val)
                                elif isinstance(text_val, bytes):
                                    texts.append(text_val.decode("utf-8", errors="ignore"))
                            elif isinstance(part, str):
                                texts.append(part)
                            elif isinstance(part, bytes):
                                texts.append(part.decode("utf-8", errors="ignore"))
                    else:
                        text_val = entry.get("text")
                        if isinstance(text_val, str):
                            texts.append(text_val)
                        elif isinstance(text_val, bytes):
                            texts.append(text_val.decode("utf-8", errors="ignore"))
                elif isinstance(entry, str):
                    texts.append(entry)
                elif isinstance(entry, bytes):
                    texts.append(entry.decode("utf-8", errors="ignore"))
        elif isinstance(value, Mapping):
            text_val = value.get("text")
            if isinstance(text_val, str):
                texts.append(text_val)
            elif isinstance(text_val, bytes):
                texts.append(text_val.decode("utf-8", errors="ignore"))
        return texts

    @staticmethod
    def _normalize_responses_output(value: Any) -> Optional[list[Dict[str, Any]]]:
        if isinstance(value, list):
            return value
        return None

    @staticmethod
    def _aggregate_responses_text(output: Optional[list[Dict[str, Any]]]) -> str:
        if not output:
            return ""
        parts: list[str] = []
        for item in output:
            if not isinstance(item, dict):
                continue
            content = item.get("content")
            if isinstance(content, list):
                for part in content:
                    if isinstance(part, dict):
                        text_val = part.get("text")
                        if isinstance(text_val, str):
                            parts.append(text_val)
                    elif isinstance(part, str):
                        parts.append(part)
            elif isinstance(content, str):
                parts.append(content)
            text_val = item.get("text")
            if isinstance(text_val, str):
                parts.append(text_val)
        return "".join(parts)

    @staticmethod
    def _safe_int(value: Any) -> int:
        if value is None:
            return 0
        try:
            return int(value)
        except (TypeError, ValueError):
            return 0

    @staticmethod
    def _iter_sse_events(
        response: requests.Response,
    ) -> Generator[tuple[Optional[str], str, Optional[Any]], None, None]:
        """Yield (event_type, data, event_id) tuples from an SSE response."""
        event_type: Optional[str] = None
        data_parts: list[str] = []
        event_id: Optional[Any] = None

        for raw_line in response.iter_lines(decode_unicode=True):
            if raw_line is None:
                continue
            line = raw_line.rstrip("\r\n")
            if not line:
                if event_type is not None or data_parts:
                    yield event_type, "\n".join(data_parts), event_id
                event_type = None
                data_parts = []
                event_id = None
                continue
            if line.startswith(":"):
                continue
            if line.startswith("event:"):
                event_type = line[len("event:") :].strip() or None
                continue
            if line.startswith("data:"):
                data_parts.append(line[len("data:") :].lstrip())
                continue
            if line.startswith("id:"):
                candidate = line[len("id:") :].strip()
                try:
                    event_id = int(candidate)
                except ValueError:
                    event_id = candidate or None
                continue
        if event_type is not None or data_parts:
            yield event_type, "\n".join(data_parts), event_id

    def _get_api_key_from_env(self) -> str:
        """Resolve API key from the active Ember configuration."""
        from ember._internal.context.runtime import EmberContext

        return EmberContext.current().get_credential("takumi")

    def _resolve_base_url(self, explicit_base_url: Optional[str]) -> str:
        """Resolve base URL: explicit > EMBER_TAKUMI_BASE_URL env > config."""
        candidate = explicit_base_url or os.environ.get("EMBER_TAKUMI_BASE_URL")
        if not candidate:
            # Try to get from Ember config if available
            try:
                from ember._internal.context.runtime import EmberContext

                ctx = EmberContext.current()
                config = ctx.config
                if config and hasattr(config, "get"):
                    takumi_config = config.get("providers", {}).get("takumi", {})
                    candidate = takumi_config.get("base_url")
            except Exception:
                logger.debug("Config lookup for Takumi base_url failed", exc_info=True)

        if not isinstance(candidate, str) or not candidate.strip():
            raise ValueError(
                "Takumi base_url is not configured. Provide base_url=... or "
                "set EMBER_TAKUMI_BASE_URL."
            )
        return candidate.rstrip("/")

    def _build_endpoint_url(self, path: str) -> str:
        """Build full endpoint URL, avoiding double /v1 if already in base_url."""
        base = self.base_url.rstrip("/")
        if path.startswith("/v1/") and base.endswith("/v1"):
            path = path[3:]
        return f"{base}{path}"

    def _build_messages(
        self,
        prompt: str,
        kwargs: MutableMapping[str, Any],
    ) -> list[dict[str, str]]:
        system_prompt = kwargs.pop("system", None)
        context_prompt = kwargs.pop("context", None)

        messages: list[dict[str, str]] = []

        if system_prompt is not None:
            if not isinstance(system_prompt, str):
                raise ValueError("system prompt must be a string when provided")
            messages.append({"role": "system", "content": system_prompt})

        if context_prompt is not None:
            messages.extend(self._context_to_messages(context_prompt))

        messages.append({"role": "user", "content": prompt})
        return messages

    @staticmethod
    def _context_to_messages(context: Any) -> list[dict[str, str]]:
        """Normalise context payloads into chat messages."""
        if isinstance(context, str):
            return [{"role": "system", "content": context}]

        if isinstance(context, Sequence):
            normalised: list[dict[str, str]] = []
            for entry in context:
                if not isinstance(entry, Mapping):
                    raise ValueError("context entries must be mappings with role/content")
                role = _normalise_role(entry.get("role"))
                content = entry.get("content")
                if not isinstance(content, str):
                    raise ValueError("context message content must be text")
                normalised.append({"role": role, "content": content})
            return normalised

        raise ValueError("context must be a string or list of {role, content} objects")

    def _build_timeout_plan(
        self,
        prompt: str,
        payload: Dict[str, Any],
        *,
        model: str,
        timeout_override: Optional[float],
    ) -> list[tuple[float, float]]:
        """Return (connect, read) timeout pairs ordered by attempt."""
        connect_timeout = 10.0
        if timeout_override is not None:
            return [(connect_timeout, max(1.0, float(timeout_override)))]

        read_timeout = self._estimate_standard_timeout(prompt, payload)
        return [(connect_timeout, read_timeout)]

    def _estimate_standard_timeout(
        self,
        prompt: str,
        payload: Dict[str, Any],
    ) -> float:
        """Estimate a read timeout using lightweight heuristics."""
        est_prompt_tokens = max(1, len(prompt) // 4)
        max_tokens_hint = payload.get("max_tokens")
        try:
            completion_hint = int(max_tokens_hint) if max_tokens_hint else 1024
        except (TypeError, ValueError):
            completion_hint = 1024

        total_tokens = est_prompt_tokens + max(1, completion_hint)

        is_local = self._is_local_base_url()
        tokens_per_second = 45.0 if is_local else 55.0
        base_buffer = 60.0 if is_local else 45.0

        estimated = (total_tokens / tokens_per_second) + base_buffer
        lower_bound = 120.0 if is_local else 90.0
        upper_bound = 420.0 if is_local else 360.0

        return float(min(max(estimated, lower_bound), upper_bound))

    def _is_local_base_url(self) -> bool:
        """Return ``True`` when the provider points at a loopback gateway."""
        parsed = urlparse(self.base_url)
        host = (parsed.hostname or "").lower()
        return host in {"127.0.0.1", "localhost"}


def _normalise_role(raw: Any) -> str:
    if raw is None:
        return "user"
    if isinstance(raw, str):
        role = raw.strip()
        return role or "user"
    raise ValueError("context role must be text")
