"""Voice processing configuration models."""

from __future__ import annotations

from typing import Any, Literal, Self
from urllib.parse import urlparse

from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator

from mindroom.credentials import validate_service_name
from mindroom.model_defaults import OPENAI_TRANSCRIPTION

_RESERVED_SPEECH_OPTION_NAMES = frozenset({"api_key", "base_url", "client", "model"})


def _default_voice_stt_credentials_service(validated_data: dict[str, Any]) -> str | None:
    """Use the OpenAI credential service only for cloud STT."""
    return "openai" if validated_data["provider"] == "openai" else None


def normalize_speech_base_url(host: str | None) -> str | None:
    """Normalize a speech service root to an OpenAI-compatible ``/v1`` URL."""
    normalized = host.strip().rstrip("/") if host else ""
    if not normalized:
        return None
    return normalized if normalized.endswith("/v1") else f"{normalized}/v1"


class SpeechServiceConfig(BaseModel):
    """Configuration for one OpenAI-compatible speech service."""

    model_config = ConfigDict(extra="forbid")

    provider: Literal["openai", "openai_compatible"] = Field(
        default="openai",
        description="Speech provider adapter (OpenAI or an OpenAI-compatible endpoint)",
    )
    model: str = Field(description="Provider speech model name")
    api_key: str | None = Field(default=None, description="Optional service-specific API key")
    credentials_service: str | None = Field(
        default=None,
        description="Optional named credential service containing the speech API key",
    )
    host: str | None = Field(default=None, description="Optional service root or /v1 base URL")
    extra_kwargs: dict[str, Any] = Field(
        default_factory=dict,
        description="Provider-specific options passed to the speech adapter",
    )

    @field_validator("api_key", "host", mode="before")
    @classmethod
    def normalize_optional_string(cls, value: object) -> object:
        """Treat blank optional form fields as omitted configuration."""
        if not isinstance(value, str):
            return value
        normalized = value.strip()
        return normalized or None

    @field_validator("credentials_service")
    @classmethod
    def _validate_credentials_service(cls, value: str | None) -> str | None:
        """Normalize an explicitly selected speech credential service."""
        return None if value is None else validate_service_name(value)

    @field_validator("host")
    @classmethod
    def validate_host(cls, value: str | None) -> str | None:
        """Require explicit HTTP endpoints and reject cloud-fallback-shaped blanks."""
        if value is None:
            return None
        normalized = value.rstrip("/")
        parsed = urlparse(normalized)
        if parsed.scheme not in {"http", "https"} or not parsed.netloc:
            msg = "Speech host must be an HTTP(S) URL"
            raise ValueError(msg)
        return normalized

    @field_validator("extra_kwargs")
    @classmethod
    def validate_extra_kwargs(cls, value: dict[str, Any]) -> dict[str, Any]:
        """Keep common connection fields on their typed configuration surface."""
        reserved = sorted(set(value).intersection(_RESERVED_SPEECH_OPTION_NAMES))
        if reserved:
            msg = "Speech extra_kwargs must not redefine: " + ", ".join(reserved)
            raise ValueError(msg)
        return value

    @model_validator(mode="after")
    def _validate_connection(self) -> Self:
        """Require an explicit endpoint and credential source where applicable."""
        if self.provider == "openai_compatible" and self.host is None:
            msg = "OpenAI-compatible speech services require host"
            raise ValueError(msg)
        if self.provider == "openai" and self.api_key is None and self.credentials_service is None:
            msg = "OpenAI speech services require credentials_service or api_key"
            raise ValueError(msg)
        return self


class VoiceSTTConfig(SpeechServiceConfig):
    """Voice-message STT configuration with its historical model default."""

    model: str = Field(default=OPENAI_TRANSCRIPTION, description="STT model name")
    credentials_service: str | None = Field(
        default_factory=_default_voice_stt_credentials_service,
        description="Named speech credential service, defaulting to OpenAI for cloud STT",
    )


class _VoiceLLMConfig(BaseModel):
    """Configuration for voice command intelligence."""

    model: str = Field(default="default", description="Model for command recognition")


class VoiceConfig(BaseModel):
    """Configuration for voice message handling."""

    enabled: bool = Field(default=False, description="Enable voice message processing")
    visible_router_echo: bool = Field(
        default=True,
        description="Show router voice progress, or direct fallback text when transcription is disabled",
    )
    stt: VoiceSTTConfig = Field(
        default_factory=VoiceSTTConfig,
        description="STT configuration",
    )
    intelligence: _VoiceLLMConfig = Field(
        default_factory=_VoiceLLMConfig,
        description="Command intelligence configuration",
    )
