"""
TDD tests for resolve_effective_config().

This function deep-merges workflow-level model_overrides onto the global
EffectiveAIModelConfiguration. Fields not overridden inherit from global.

Module under test: api.services.configuration.resolve
"""

import pytest

from api.schemas.ai_model_configuration import EffectiveAIModelConfiguration
from api.services.configuration.masking import (
    contains_masked_key,
    mask_workflow_configurations,
)
from api.services.configuration.merge import merge_workflow_configuration_secrets
from api.services.configuration.registry import (
    DeepgramSTTConfiguration,
    ElevenlabsTTSConfiguration,
    GoogleRealtimeLLMConfiguration,
    GoogleVertexLLMConfiguration,
    GrokRealtimeLLMConfiguration,
    OpenAILLMService,
    UltravoxRealtimeLLMConfiguration,
)
from api.services.configuration.resolve import (
    enrich_overrides_with_api_keys,
    resolve_effective_config,
)

# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
def global_config() -> EffectiveAIModelConfiguration:
    """A realistic global user configuration."""
    return EffectiveAIModelConfiguration(
        llm=OpenAILLMService(
            provider="openai", api_key="sk-global-llm", model="gpt-4.1"
        ),
        tts=ElevenlabsTTSConfiguration(
            provider="elevenlabs",
            api_key="el-global-tts",
            voice="Rachel",
            model="eleven_flash_v2_5",
        ),
        stt=DeepgramSTTConfiguration(
            provider="deepgram",
            api_key="dg-global-stt",
            model="nova-3-general",
            language="multi",
        ),
        is_realtime=False,
        realtime=None,
    )


@pytest.fixture
def global_config_realtime() -> EffectiveAIModelConfiguration:
    """Global config with realtime enabled."""
    return EffectiveAIModelConfiguration(
        llm=OpenAILLMService(
            provider="openai", api_key="sk-global-llm", model="gpt-4.1"
        ),
        tts=ElevenlabsTTSConfiguration(
            provider="elevenlabs",
            api_key="el-global-tts",
            voice="Rachel",
            model="eleven_flash_v2_5",
        ),
        stt=DeepgramSTTConfiguration(
            provider="deepgram",
            api_key="dg-global-stt",
            model="nova-3-general",
            language="multi",
        ),
        is_realtime=True,
        realtime=GoogleRealtimeLLMConfiguration(
            provider="google_realtime",
            api_key="goog-global-rt",
            model="gemini-3.1-flash-live-preview",
            voice="Puck",
            language="en",
        ),
    )


# ---------------------------------------------------------------------------
# No overrides → global returned unchanged
# ---------------------------------------------------------------------------


class TestNoOverrides:
    def test_none_overrides_returns_global(self, global_config):
        result = resolve_effective_config(global_config, None)
        assert result.llm.model == "gpt-4.1"
        assert result.tts.voice == "Rachel"
        assert result.stt.model == "nova-3-general"
        assert result.is_realtime is False

    def test_empty_dict_overrides_returns_global(self, global_config):
        result = resolve_effective_config(global_config, {})
        assert result.llm.model == "gpt-4.1"
        assert result.tts.voice == "Rachel"

    def test_does_not_mutate_original(self, global_config):
        """The original config object must not be modified."""
        resolve_effective_config(global_config, {"llm": {"model": "gpt-4.1-mini"}})
        assert global_config.llm.model == "gpt-4.1"


# ---------------------------------------------------------------------------
# Single-field overrides within a section (same provider)
# ---------------------------------------------------------------------------


class TestSingleFieldOverride:
    def test_override_llm_model_only(self, global_config):
        result = resolve_effective_config(
            global_config, {"llm": {"model": "gpt-4.1-mini"}}
        )
        assert result.llm.model == "gpt-4.1-mini"
        assert result.llm.provider == "openai"  # inherited
        assert result.llm.api_key == "sk-global-llm"  # inherited

    def test_override_tts_voice_only(self, global_config):
        result = resolve_effective_config(global_config, {"tts": {"voice": "shimmer"}})
        assert result.tts.voice == "shimmer"
        assert result.tts.provider == "elevenlabs"  # inherited
        assert result.tts.api_key == "el-global-tts"  # inherited

    def test_override_stt_language_only(self, global_config):
        result = resolve_effective_config(global_config, {"stt": {"language": "en"}})
        assert result.stt.language == "en"
        assert result.stt.model == "nova-3-general"  # inherited
        assert result.stt.provider == "deepgram"  # inherited


# ---------------------------------------------------------------------------
# Provider change (requires full section replacement)
# ---------------------------------------------------------------------------


class TestProviderChange:
    def test_override_llm_to_different_provider(self, global_config):
        result = resolve_effective_config(
            global_config,
            {
                "llm": {
                    "provider": "groq",
                    "api_key": "groq-key",
                    "model": "llama-3.3-70b-versatile",
                }
            },
        )
        assert result.llm.provider == "groq"
        assert result.llm.model == "llama-3.3-70b-versatile"
        assert result.llm.api_key == "groq-key"

    def test_provider_change_does_not_affect_other_sections(self, global_config):
        result = resolve_effective_config(
            global_config,
            {
                "llm": {
                    "provider": "groq",
                    "api_key": "groq-key",
                    "model": "llama-3.3-70b-versatile",
                }
            },
        )
        # TTS and STT unchanged
        assert result.tts.provider == "elevenlabs"
        assert result.stt.provider == "deepgram"

    def test_override_llm_to_google_vertex(self, global_config):
        result = resolve_effective_config(
            global_config,
            {
                "llm": {
                    "provider": "google_vertex",
                    "model": "gemini-3.5-flash",
                    "project_id": "demo-project",
                    "location": "us-east4",
                    "credentials": '{"type":"service_account"}',
                }
            },
        )
        assert isinstance(result.llm, GoogleVertexLLMConfiguration)
        assert result.llm.provider == "google_vertex"
        assert result.llm.project_id == "demo-project"


# ---------------------------------------------------------------------------
# API key inheritance
# ---------------------------------------------------------------------------


class TestAPIKeyInheritance:
    def test_no_api_key_in_override_inherits_global(self, global_config):
        """When override omits api_key, global key is used."""
        result = resolve_effective_config(
            global_config, {"llm": {"model": "gpt-4.1-mini"}}
        )
        assert result.llm.api_key == "sk-global-llm"

    def test_explicit_api_key_in_override_wins(self, global_config):
        """When override includes api_key, it takes precedence."""
        result = resolve_effective_config(
            global_config,
            {"llm": {"model": "gpt-4.1-mini", "api_key": "sk-override-key"}},
        )
        assert result.llm.api_key == "sk-override-key"


# ---------------------------------------------------------------------------
# is_realtime override
# ---------------------------------------------------------------------------


class TestRealtimeOverride:
    def test_enable_realtime_on_non_realtime_global(self, global_config):
        result = resolve_effective_config(
            global_config,
            {
                "is_realtime": True,
                "realtime": {
                    "provider": "google_realtime",
                    "api_key": "goog-override",
                    "model": "gemini-3.1-flash-live-preview",
                    "voice": "Charon",
                    "language": "en",
                },
            },
        )
        assert result.is_realtime is True
        assert result.realtime.provider == "google_realtime"
        assert result.realtime.voice == "Charon"

    def test_disable_realtime_on_realtime_global(self, global_config_realtime):
        result = resolve_effective_config(
            global_config_realtime, {"is_realtime": False}
        )
        assert result.is_realtime is False
        # Realtime config may still be present but is_realtime flag controls usage

    def test_override_realtime_voice_only(self, global_config_realtime):
        result = resolve_effective_config(
            global_config_realtime, {"realtime": {"voice": "Kore"}}
        )
        assert result.realtime.voice == "Kore"
        assert result.realtime.provider == "google_realtime"  # inherited
        assert result.realtime.api_key == "goog-global-rt"  # inherited

    def test_override_realtime_temperature(self, global_config_realtime):
        result = resolve_effective_config(
            global_config_realtime, {"realtime": {"temperature": 0.4}}
        )
        assert result.realtime.temperature == 0.4
        assert result.realtime.provider == "google_realtime"  # inherited

    def test_switch_realtime_provider_to_grok(self, global_config_realtime):
        result = resolve_effective_config(
            global_config_realtime,
            {
                "realtime": {
                    "provider": "grok_realtime",
                    "api_key": "xai-key",
                    "model": "grok-voice-think-fast-1.0",
                    "voice": "Sal",
                }
            },
        )
        assert isinstance(result.realtime, GrokRealtimeLLMConfiguration)
        assert result.realtime.provider == "grok_realtime"
        assert result.realtime.voice == "Sal"

    def test_switch_realtime_provider_to_ultravox(self, global_config_realtime):
        result = resolve_effective_config(
            global_config_realtime,
            {
                "realtime": {
                    "provider": "ultravox_realtime",
                    "api_key": "ultra-key",
                    "model": "ultravox-v0.7",
                    "voice": "Mark",
                }
            },
        )
        assert isinstance(result.realtime, UltravoxRealtimeLLMConfiguration)
        assert result.realtime.provider == "ultravox_realtime"
        assert result.realtime.voice == "Mark"

    def test_override_is_realtime_only_without_realtime_section(self, global_config):
        """Override is_realtime=True but provide no realtime config.
        Should set the flag; realtime section stays None from global."""
        result = resolve_effective_config(global_config, {"is_realtime": True})
        assert result.is_realtime is True
        assert result.realtime is None  # no config provided


# ---------------------------------------------------------------------------
# Section override when global has None for that section
# ---------------------------------------------------------------------------


class TestOverrideOnNullGlobal:
    def test_override_stt_when_global_is_none(self):
        """When global has no STT config, override creates one from scratch."""
        config = EffectiveAIModelConfiguration(
            llm=OpenAILLMService(provider="openai", api_key="sk-key", model="gpt-4.1"),
            stt=None,
            tts=None,
            is_realtime=False,
        )
        result = resolve_effective_config(
            config,
            {
                "stt": {
                    "provider": "deepgram",
                    "api_key": "dg-new",
                    "model": "nova-3-general",
                    "language": "en",
                }
            },
        )
        assert result.stt is not None
        assert result.stt.provider == "deepgram"
        assert result.stt.model == "nova-3-general"

    def test_override_realtime_when_global_is_none(self):
        """Realtime section can be created from override even if global has none."""
        config = EffectiveAIModelConfiguration(
            llm=OpenAILLMService(provider="openai", api_key="sk-key", model="gpt-4.1"),
            is_realtime=False,
            realtime=None,
        )
        result = resolve_effective_config(
            config,
            {
                "is_realtime": True,
                "realtime": {
                    "provider": "google_realtime",
                    "api_key": "goog-new",
                    "model": "gemini-3.1-flash-live-preview",
                    "voice": "Puck",
                    "language": "en",
                },
            },
        )
        assert result.is_realtime is True
        assert result.realtime.provider == "google_realtime"


# ---------------------------------------------------------------------------
# Multi-section overrides
# ---------------------------------------------------------------------------


class TestMultiSectionOverride:
    def test_override_llm_and_tts_not_stt(self, global_config):
        result = resolve_effective_config(
            global_config,
            {
                "llm": {"model": "gpt-4.1-mini"},
                "tts": {"voice": "shimmer"},
            },
        )
        assert result.llm.model == "gpt-4.1-mini"
        assert result.tts.voice == "shimmer"
        # STT untouched
        assert result.stt.model == "nova-3-general"
        assert result.stt.language == "multi"

    def test_override_all_sections(self, global_config):
        result = resolve_effective_config(
            global_config,
            {
                "llm": {"model": "gpt-4.1-mini"},
                "tts": {"voice": "shimmer"},
                "stt": {"language": "en"},
                "is_realtime": True,
                "realtime": {
                    "provider": "google_realtime",
                    "api_key": "goog-key",
                    "model": "gemini-3.1-flash-live-preview",
                    "voice": "Fenrir",
                    "language": "en",
                },
            },
        )
        assert result.llm.model == "gpt-4.1-mini"
        assert result.tts.voice == "shimmer"
        assert result.stt.language == "en"
        assert result.is_realtime is True
        assert result.realtime.voice == "Fenrir"


# ---------------------------------------------------------------------------
# Ignored / unknown keys
# ---------------------------------------------------------------------------


class TestUnknownKeys:
    def test_unknown_section_in_overrides_is_ignored(self, global_config):
        """Override with a key that doesn't map to any section should not crash."""
        result = resolve_effective_config(
            global_config, {"unknown_section": {"foo": "bar"}}
        )
        assert result.llm.model == "gpt-4.1"

    def test_embeddings_not_overridable(self, global_config):
        """Embeddings stay global — overrides for embeddings should be ignored."""
        result = resolve_effective_config(
            global_config,
            {"embeddings": {"provider": "openai", "model": "text-embedding-3-small"}},
        )
        assert result.embeddings is None  # was None in global, stays None


# ---------------------------------------------------------------------------
# enrich_overrides_with_api_keys
# ---------------------------------------------------------------------------


class TestEnrichOverridesWithApiKeys:
    def test_injects_api_key_when_same_provider(self, global_config):
        """Override matching the global provider gets the global API key stamped in."""
        overrides = {
            "tts": {
                "provider": "elevenlabs",
                "voice": "Bella",
                "model": "eleven_flash_v2_5",
            }
        }
        enriched = enrich_overrides_with_api_keys(overrides, global_config)
        assert enriched["tts"]["api_key"] == "el-global-tts"

    def test_injects_all_api_keys_when_global_has_multiple(self, global_config):
        """Override matching a multi-key global provider gets every global key."""
        global_config.llm.api_key = ["sk-global-1", "sk-global-2"]
        overrides = {"llm": {"provider": "openai", "model": "gpt-4.1-mini"}}

        enriched = enrich_overrides_with_api_keys(overrides, global_config)

        assert enriched["llm"]["api_key"] == ["sk-global-1", "sk-global-2"]

    def test_does_not_overwrite_existing_api_key(self, global_config):
        """Override that already has an api_key keeps its own key."""
        overrides = {
            "tts": {
                "provider": "elevenlabs",
                "api_key": "my-own-key",
                "voice": "Bella",
                "model": "eleven_flash_v2_5",
            }
        }
        enriched = enrich_overrides_with_api_keys(overrides, global_config)
        assert enriched["tts"]["api_key"] == "my-own-key"

    def test_skips_when_provider_differs(self, global_config):
        """Override for a different provider is not enriched with the global key."""
        overrides = {
            "tts": {"provider": "cartesia", "voice": "some-voice", "model": "sonic-3"}
        }
        enriched = enrich_overrides_with_api_keys(overrides, global_config)
        assert "api_key" not in enriched["tts"]

    def test_does_not_mutate_original(self, global_config):
        """The input overrides dict must not be modified."""
        overrides = {
            "tts": {
                "provider": "elevenlabs",
                "voice": "Bella",
                "model": "eleven_flash_v2_5",
            }
        }
        original_copy = {
            "tts": {
                "provider": "elevenlabs",
                "voice": "Bella",
                "model": "eleven_flash_v2_5",
            }
        }
        enrich_overrides_with_api_keys(overrides, global_config)
        assert overrides == original_copy

    def test_regression_override_survives_global_provider_change(self, global_config):
        """Core bug: override for provider A still works after global switches to B.

        Steps:
          1. Global TTS = ElevenLabs, Override TTS = ElevenLabs (different voice)
          2. enrich_overrides_with_api_keys stamps ElevenLabs API key into override
          3. Global TTS changes to Deepgram (simulate by building a new config)
          4. resolve_effective_config must still return a valid ElevenLabs config
        """
        override_at_save_time = {
            "tts": {
                "provider": "elevenlabs",
                "voice": "Bella",
                "model": "eleven_flash_v2_5",
            }
        }
        enriched = enrich_overrides_with_api_keys(override_at_save_time, global_config)
        assert enriched["tts"]["api_key"] == "el-global-tts"

        # Simulate global config switching to Deepgram
        from api.services.configuration.registry import DeepgramTTSConfiguration

        new_global = global_config.model_copy(
            update={
                "tts": DeepgramTTSConfiguration(
                    provider="deepgram", api_key="dg-new", voice="aura-2-helena-en"
                )
            }
        )

        # The enriched override should resolve correctly against the new global
        result = resolve_effective_config(new_global, enriched)
        assert result.tts.provider == "elevenlabs"
        assert result.tts.voice == "Bella"
        assert result.tts.api_key == "el-global-tts"


class TestWorkflowConfigurationSecrets:
    def test_masks_model_override_secrets(self):
        configs = {
            "model_overrides": {
                "llm": {
                    "provider": "openai",
                    "api_key": "sk-real-llm-key",
                    "model": "gpt-4.1-mini",
                },
                "tts": {
                    "provider": "elevenlabs",
                    "api_key": "el-real-tts-key",
                    "voice": "Bella",
                },
            },
            "ambient_noise_configuration": {"enabled": True},
        }

        masked = mask_workflow_configurations(configs)

        assert masked["model_overrides"]["llm"]["api_key"] != "sk-real-llm-key"
        assert contains_masked_key(masked["model_overrides"]["llm"]["api_key"])
        assert masked["model_overrides"]["llm"]["api_key"].endswith("-key")
        assert masked["model_overrides"]["tts"]["api_key"] != "el-real-tts-key"
        assert masked["ambient_noise_configuration"] == {"enabled": True}
        assert configs["model_overrides"]["llm"]["api_key"] == "sk-real-llm-key"

    def test_restores_masked_model_override_secrets_from_existing_config(self):
        existing = {
            "model_overrides": {
                "tts": {
                    "provider": "elevenlabs",
                    "api_key": "el-real-tts-key",
                    "voice": "Rachel",
                }
            }
        }
        incoming = mask_workflow_configurations(existing)
        incoming["model_overrides"]["tts"]["voice"] = "Bella"

        merged = merge_workflow_configuration_secrets(incoming, existing)

        assert merged["model_overrides"]["tts"]["api_key"] == "el-real-tts-key"
        assert merged["model_overrides"]["tts"]["voice"] == "Bella"
        assert incoming["model_overrides"]["tts"]["api_key"] != "el-real-tts-key"

    def test_single_masked_key_preserves_existing_multi_key_override(self):
        existing = {
            "model_overrides": {
                "llm": {
                    "provider": "openai",
                    "api_key": ["sk-workflow-1", "sk-workflow-2"],
                    "model": "gpt-4.1-mini",
                }
            }
        }
        incoming = mask_workflow_configurations(existing)
        incoming["model_overrides"]["llm"]["api_key"] = incoming["model_overrides"][
            "llm"
        ]["api_key"][0]

        merged = merge_workflow_configuration_secrets(incoming, existing)

        assert merged["model_overrides"]["llm"]["api_key"] == [
            "sk-workflow-1",
            "sk-workflow-2",
        ]

    def test_missing_secret_copies_current_global_key_instead_of_existing_workflow_key(
        self, global_config
    ):
        global_config.stt.api_key = ["dg-global-1", "dg-global-2"]
        existing = {
            "model_overrides": {
                "stt": {
                    "provider": "deepgram",
                    "api_key": "dg-workflow-key",
                    "model": "nova-3-general",
                    "language": "multi",
                }
            }
        }
        incoming = {
            "model_overrides": {
                "stt": {
                    "provider": "deepgram",
                    "model": "nova-3-general",
                    "language": "en",
                }
            }
        }

        merged = merge_workflow_configuration_secrets(incoming, existing)
        enriched = enrich_overrides_with_api_keys(
            merged["model_overrides"],
            global_config,
        )

        assert enriched["stt"]["api_key"] == ["dg-global-1", "dg-global-2"]
        assert enriched["stt"]["language"] == "en"
