from __future__ import annotations

from typing import Any, Dict, Mapping


FAKE_AGENT_MIN_RESPONSES = 2
FAKE_AGENT_MAX_RESPONSES = 10
FAKE_AGENT_MIN_RESPONSE_CHARS = 40
FAKE_AGENT_MAX_RESPONSE_CHARS = 180
FAKE_AGENT_DEFAULT_DELAY_MS = 2500
FAKE_AGENT_MAX_DELAY_MS = 180000


def _as_str(value: Any) -> str:
    if value is None:
        return ""
    return str(value).strip()


def _section(payload: Mapping[str, Any], *keys: str) -> Dict[str, Any]:
    for key in keys:
        value = payload.get(key)
        if isinstance(value, Mapping):
            return dict(value)
    return {}


def _pick_str(payload: Mapping[str, Any], *keys: str) -> str:
    for key in keys:
        value = payload.get(key)
        if value not in (None, ""):
            return _as_str(value)
    return ""


def normalize_call_config(payload: Mapping[str, Any] | None) -> Dict[str, Any]:
    if not isinstance(payload, Mapping):
        return {
            "agent_backend": "",
            "stt": {},
            "tts": {},
            "vad": {},
            "vad_logging": {},
            "ws": {},
            "agent_fake": {},
        }

    stt = _section(payload, "stt")
    tts = _section(payload, "tts")
    vad = _section(payload, "vad")
    vad_logging = _section(payload, "vadLogging", "vad_logging")
    ws = _section(payload, "ws")
    agent_fake = _section(payload, "agentFake", "agent_fake")
    return {
        "agent_backend": _pick_str(payload, "agentBackend", "agent_backend"),
        "stt": {
            "provider": _pick_str(stt, "provider"),
            "language": _pick_str(stt, "language"),
            "api_key": _pick_str(stt, "apiKey", "api_key"),
            "initial_prompt": _pick_str(stt, "initialPrompt", "initial_prompt"),
            "config_override": _pick_str(stt, "configOverride", "config_override"),
            "min_prob_single_word": _pick_str(stt, "minProbSingleWord", "min_prob_single_word"),
            "disable_vosk": _pick_str(stt, "disableVosk", "disable_vosk"),
        },
        "tts": {
            "provider": _pick_str(tts, "provider"),
            "voice_id": _pick_str(tts, "voiceId", "voice_id"),
            "model_id": _pick_str(tts, "modelId", "model_id"),
            "language": _pick_str(tts, "language"),
        },
        "vad": {
            "min_speech_duration": _pick_str(vad, "minSpeechDuration", "min_speech_duration"),
            "activation_threshold": _pick_str(vad, "activationThreshold", "activation_threshold"),
            "deactivation_threshold": _pick_str(vad, "deactivationThreshold", "deactivation_threshold"),
            "min_silence_duration": _pick_str(vad, "minSilenceDuration", "min_silence_duration"),
            "prefix_padding_duration": _pick_str(vad, "prefixPaddingDuration", "prefix_padding_duration"),
            "pre_backend_wait_notice_fast_on_vad_pause": _pick_str(
                vad,
                "preBackendWaitNoticeFastOnVadPause",
                "pre_backend_wait_notice_fast_on_vad_pause",
            ),
            "deferred_interruption_min_audio_ms": (
                _pick_str(
                    vad,
                    "deferredInterruptionMinAudioMs",
                    "deferred_interruption_min_audio_ms",
                    "DEFERRED_INTERRUPTION_MIN_AUDIO_MS",
                )
                or _pick_str(
                    payload,
                    "deferredInterruptionMinAudioMs",
                    "deferred_interruption_min_audio_ms",
                    "DEFERRED_INTERRUPTION_MIN_AUDIO_MS",
                )
            ),
            "deferred_interruption_enabled": (
                _pick_str(
                    vad,
                    "deferredInterruptionEnabled",
                    "deferred_interruption_enabled",
                    "DEFERRED_INTERRUPTION_ENABLED",
                )
                or _pick_str(
                    payload,
                    "deferredInterruptionEnabled",
                    "deferred_interruption_enabled",
                    "DEFERRED_INTERRUPTION_ENABLED",
                )
            ),
        },
        "vad_logging": {
            "log_decisions": _pick_str(vad_logging, "logDecisions", "log_decisions"),
            "log_activity": _pick_str(vad_logging, "logActivity", "log_activity"),
            "activity_min_probability": _pick_str(
                vad_logging,
                "activityMinProbability",
                "activity_min_probability",
            ),
        },
        "ws": {
            "output_gain": _pick_str(ws, "outputGain", "output_gain"),
            "audio_in_backlog_shed_enabled": _pick_str(
                ws,
                "audioInputBacklogShedEnabled",
                "audio_in_backlog_shed_enabled",
            ),
            "audio_in_backlog_shed_threshold_ms": _pick_str(
                ws,
                "audioInputBacklogShedThresholdMs",
                "audio_in_backlog_shed_threshold_ms",
            ),
            "audio_in_backlog_shed_keep_ms": _pick_str(
                ws,
                "audioInputBacklogShedKeepMs",
                "audio_in_backlog_shed_keep_ms",
            ),
            "audio_in_latency_metrics_enabled": _pick_str(
                ws,
                "audioInputLatencyMetricsEnabled",
                "audio_in_latency_metrics_enabled",
            ),
            "audio_in_latency_alert_ms": _pick_str(
                ws,
                "audioInputLatencyAlertMs",
                "audio_in_latency_alert_ms",
            ),
            "audio_in_latency_log_interval_s": _pick_str(
                ws,
                "audioInputLatencyLogIntervalS",
                "audio_in_latency_log_interval_s",
            ),
            "livekit_audio_source_queue_size_ms": _pick_str(
                ws,
                "livekitAudioSourceQueueSizeMs",
                "livekit_audio_source_queue_size_ms",
            ),
            "livekit_audio_source_clear_on_shed": _pick_str(
                ws,
                "livekitAudioSourceClearOnShed",
                "livekit_audio_source_clear_on_shed",
            ),
            "audio_in_backlog_energy_shed_enabled": _pick_str(
                ws,
                "audioInputBacklogEnergyShedEnabled",
                "audio_in_backlog_energy_shed_enabled",
            ),
            "audio_in_backlog_energy_shed_max_excess_ms": _pick_str(
                ws,
                "audioInputBacklogEnergyShedMaxExcessMs",
                "audio_in_backlog_energy_shed_max_excess_ms",
            ),
            "audio_in_backlog_silence_dbfs": _pick_str(
                ws,
                "audioInputBacklogSilenceDbfs",
                "audio_in_backlog_silence_dbfs",
            ),
        },
        "agent_fake": {
            "delay_ms": _pick_str(agent_fake, "delayMs", "delay_ms"),
            "responses": _pick_str(agent_fake, "responses"),
        },
    }


def resolve_agent_backend_name(call_config: Mapping[str, Any] | None, default_backend: str) -> str:
    normalized = normalize_call_config(call_config)
    return normalized["agent_backend"] or _as_str(default_backend) or "remote_ws"


def resolve_stt_overrides(call_config: Mapping[str, Any] | None) -> Dict[str, str]:
    normalized = normalize_call_config(call_config)
    return dict(normalized["stt"])


def resolve_tts_overrides(call_config: Mapping[str, Any] | None) -> Dict[str, str]:
    normalized = normalize_call_config(call_config)
    return dict(normalized["tts"])


def resolve_vad_overrides(call_config: Mapping[str, Any] | None) -> Dict[str, str]:
    normalized = normalize_call_config(call_config)
    return dict(normalized["vad"])


def resolve_vad_logging_overrides(call_config: Mapping[str, Any] | None) -> Dict[str, str]:
    normalized = normalize_call_config(call_config)
    return dict(normalized["vad_logging"])


def resolve_ws_overrides(call_config: Mapping[str, Any] | None) -> Dict[str, str]:
    normalized = normalize_call_config(call_config)
    return dict(normalized["ws"])


def _parse_fake_agent_responses(value: Any) -> list[str]:
    raw = _as_str(value)
    if not raw:
        return []

    responses = [item.strip() for item in raw.split(";")]
    if any(not item for item in responses):
        raise ValueError("agentFake.responses nao pode conter itens vazios")
    if not FAKE_AGENT_MIN_RESPONSES <= len(responses) <= FAKE_AGENT_MAX_RESPONSES:
        raise ValueError(
            "agentFake.responses deve conter entre "
            f"{FAKE_AGENT_MIN_RESPONSES} e {FAKE_AGENT_MAX_RESPONSES} frases"
        )
    for index, response in enumerate(responses, start=1):
        if not FAKE_AGENT_MIN_RESPONSE_CHARS <= len(response) <= FAKE_AGENT_MAX_RESPONSE_CHARS:
            raise ValueError(
                f"agentFake.responses[{index}] deve conter entre "
                f"{FAKE_AGENT_MIN_RESPONSE_CHARS} e {FAKE_AGENT_MAX_RESPONSE_CHARS} caracteres"
            )
    return responses


def resolve_fake_agent_overrides(call_config: Mapping[str, Any] | None) -> Dict[str, Any]:
    normalized = normalize_call_config(call_config)
    agent_fake = dict(normalized["agent_fake"])
    responses = _parse_fake_agent_responses(agent_fake.get("responses"))
    if responses and normalized["agent_backend"].lower() != "remote_ws_fake":
        raise ValueError(
            "agentFake.responses exige callConfig.agentBackend=remote_ws_fake"
        )

    delay_raw = agent_fake.get("delay_ms")
    if delay_raw in (None, ""):
        delay_ms = FAKE_AGENT_DEFAULT_DELAY_MS if responses else None
    else:
        try:
            delay_ms = int(str(delay_raw).strip())
        except (TypeError, ValueError) as exc:
            raise ValueError("agentFake.delayMs deve ser um inteiro") from exc
        if not 0 <= delay_ms <= FAKE_AGENT_MAX_DELAY_MS:
            raise ValueError(
                f"agentFake.delayMs deve estar entre 0 e {FAKE_AGENT_MAX_DELAY_MS}"
            )

    return {"delay_ms": delay_ms, "responses": responses}


def is_fake_agent_stress_test(call_config: Mapping[str, Any] | None) -> bool:
    """Identify calls that explicitly opt into the scripted fake agent."""
    overrides = resolve_fake_agent_overrides(call_config)
    return bool(overrides.get("responses"))
