from functools import lru_cache
from pathlib import Path

from pydantic_settings import BaseSettings, SettingsConfigDict

# backend/ (donde está config.py) — útil para resolver prompts relativos
_BACKEND_DIR = Path(__file__).resolve().parent
_PROJECT_DIR = _BACKEND_DIR.parent


class Settings(BaseSettings):
    model_config = SettingsConfigDict(
        env_file=(".env", "../.env"),
        env_file_encoding="utf-8",
        extra="ignore",
    )

    ari_base_url: str = "http://127.0.0.1:8088"
    ari_user: str = "admin"
    ari_password: str = ""
    stasis_app: str = "StasisApp"
    outbound_endpoint_template: str = "PJSIP/{number}"
    outbound_context: str = "SuperAdmin"
    outbound_extension: str = "1100"
    outbound_caller_id: str = "IA Bot <1000>"
    # Segunda pata del puente (ej. PJSIP/1000). Alternativa: WebRTC + externalMedia.
    agent_endpoint: str = ""
    agent_endpoint_template: str = "PJSIP/{extension}"
    # WebRTC en el navegador ↔ Asterisk vía externalMedia (RTP/PCMU)
    webrtc_enabled: bool = True
    external_media_bind_host: str = "0.0.0.0"
    # IP:puerto que Asterisk debe alcanzar (IP del backend si Asterisk es remoto)
    external_media_advertise_host: str = "127.0.0.1"
    external_media_format: str = "ulaw"
    webrtc_stun_url: str = "stun:stun.l.google.com:19302"
    cors_origins: str = "http://localhost:5173"
    host: str = "0.0.0.0"
    port: int = 8000
    # Recarga automática al guardar archivos (solo desarrollo; usar: python main.py)
    dev_reload: bool = False
    # Depuración ARI: logs detallados de eventos y canales
    ari_debug: bool = False
    ari_debug_full_events: bool = False
    # Reproduce sound:hello-world al conectar saliente (prueba de ruta RTP)
    ari_debug_play_sound: bool = False
    # Registro SIP WebRTC del operador en el navegador (PJSIP + WSS)
    sip_ws_url: str = ""
    sip_domain: str = ""

    # Bot conversacional (tras readout de documento)
    bot_enabled: bool = True
    # Providers intercambiables: stt = google|local ; tts = google|elevenlabs|local ; llm = openai|ollama
    stt_provider: str = "google"
    tts_provider: str = "google"
    llm_provider: str = "openai"
    bot_greeting: str = (
        "Hola, Qualia seguros. ¿En qué puedo ayudarte?"
    )
    # immediate = TTS ya + warmup LLM en paralelo | llm = espera saludo del modelo
    bot_opening_mode: str = "immediate"
    # Texto inline opcional (pisa el archivo si no está vacío)
    bot_system_prompt: str = ""
    # Archivo con el system prompt (recomendado; más cómodo que .env)
    bot_system_prompt_file: str = "prompts/bot_system_prompt.txt"
    # Frases del bot → acciones (transfer, etc.). JSON inline pisa el archivo.
    bot_action_triggers: str = ""
    bot_action_triggers_file: str = "config/bot_action_triggers.json"
    bot_silence_ms: int = 900
    bot_min_speech_ms: int = 400
    bot_max_utterance_ms: int = 12000
    bot_vad_energy: int = 400
    # ms de audio previos al umbral VAD (evita perder la 1.ª palabra en STT)
    bot_preroll_ms: int = 350
    # Ambiente de oficina en loop (RTP hacia el llamante, mezclado con TTS)
    bot_ambience_enabled: bool = True
    bot_ambience_file: str = "sonido_oficina.mp3"
    # Volumen relativo 0.0–1.0 (bajo para no tapar la voz)
    bot_ambience_gain: float = 0.18

    # Google Cloud Speech / TTS (ADC o GOOGLE_CREDENTIAL=ruta al JSON)
    google_credential: str = ""
    google_project_id: str = ""
    google_stt_language: str = "es-AR"
    google_stt_model: str = "default"
    google_tts_language: str = "es-US"
    google_tts_voice: str = "es-US-Neural2-A"
    google_tts_speaking_rate: float = 1.0

    # ElevenLabs TTS (TTS_PROVIDER=elevenlabs)
    elevenlabs_api_key: str = ""
    elevenlabs_voice_id: str = ""
    elevenlabs_model_id: str = "eleven_multilingual_v2"

    # OpenAI (LLM) — también usable contra proxies compatibles
    openai_api_key: str = ""
    openai_base_url: str = "https://api.openai.com/v1"
    openai_model: str = "gpt-4.1-mini"
    openai_temperature: float = 0.7
    openai_max_tokens: int = 1000
    openai_top_p: float = 1.0
    openai_frequency_penalty: float = 0.0
    openai_presence_penalty: float = 0.0
    openai_timeout: float = 60.0
    # Legacy: Assistants API deprecada. Se acepta en .env pero se ignora
    # (OpenAI cloud usa Responses API; personalidad = BOT_SYSTEM_PROMPT).
    openai_assistant_id: str = ""

    # Ollama local (API compatible OpenAI: /v1/chat/completions)
    ollama_base_url: str = "http://127.0.0.1:11434/v1"
    ollama_model: str = "qwen2.5:7b"
    ollama_api_key: str = "ollama"
    ollama_timeout: float = 120.0

    # API externa CRM / datos (MockAPI u otro backend REST)
    crm_api_enabled: bool = True
    # Project id MockAPI (ej. 6a57b0b0914a025dcff35cfd)
    crm_api_base_id: str = ""
    # Si está vacío, se arma con CRM_API_BASE_TEMPLATE + CRM_API_BASE_ID
    crm_api_base_url: str = ""
    crm_api_base_template: str = "https://{base_id}.mockapi.io/api/v1"
    crm_api_key: str = ""
    crm_api_timeout: float = 15.0
    # Paths relativos por recurso (sin slash inicial)
    crm_endpoint_clientes: str = "clientes"
    crm_endpoint_productos: str = "productos"
    crm_endpoint_polizas: str = "polizas"
    # JSON opcional: {"siniestros":"siniestro","coberturas":"cobertura"}
    crm_endpoints_extra: str = ""

    def resolve_prompt_path(self, path_str: str) -> Path | None:
        """Resuelve ruta relativa a backend/, proyecto/ o cwd."""
        raw = (path_str or "").strip()
        if not raw:
            return None
        path = Path(raw).expanduser()
        if path.is_file():
            return path.resolve()
        candidates = (
            _BACKEND_DIR / path,
            _PROJECT_DIR / path,
            Path.cwd() / path,
        )
        for candidate in candidates:
            if candidate.is_file():
                return candidate.resolve()
        return None

    @property
    def resolved_bot_system_prompt(self) -> str:
        """Prioridad: BOT_SYSTEM_PROMPT (.env) > archivo BOT_SYSTEM_PROMPT_FILE."""
        inline = (self.bot_system_prompt or "").strip()
        if inline:
            return inline
        path = self.resolve_prompt_path(self.bot_system_prompt_file)
        if not path:
            return ""
        try:
            return path.read_text(encoding="utf-8").strip()
        except OSError:
            return ""

    @property
    def resolved_sip_domain(self) -> str:
        if self.sip_domain.strip():
            return self.sip_domain.strip()
        from urllib.parse import urlparse

        host = urlparse(self.ari_base_url).hostname
        return host or ""

    def get_sip_config(self) -> dict[str, str]:
        ws_url = self.sip_ws_url.strip()
        if not ws_url and self.ari_base_url:
            from urllib.parse import urlparse

            parsed = urlparse(self.ari_base_url)
            if parsed.hostname:
                scheme = "wss" if parsed.scheme == "https" else "ws"
                ws_url = f"{scheme}://{parsed.hostname}:8081/ws"
        return {
            "ws_url": ws_url,
            "domain": self.resolved_sip_domain,
        }

    @property
    def cors_origin_list(self) -> list[str]:
        return [o.strip() for o in self.cors_origins.split(",") if o.strip()]

    @property
    def ari_ws_url(self) -> str:
        base = self.ari_base_url.rstrip("/")
        if base.startswith("https://"):
            return base.replace("https://", "wss://", 1)
        return base.replace("http://", "ws://", 1)

    def format_endpoint(self, number: str) -> str:
        return self.outbound_endpoint_template.format(number=number)


@lru_cache
def get_settings() -> Settings:
    return Settings()
