"""Tests for JSON config loading with env variable substitution."""

import json
from pathlib import Path

import pytest
from pydantic import ValidationError

from llm_rotator.config import RotatorConfig


def _minimal_config_dict():
    return {
        "providers": [
            {
                "name": "openai",
                "client_type": "openai",
                "priority": 1,
                "models": ["gpt-4o"],
                "keys": [{"token": "sk-test-123", "alias": "main"}],
            }
        ]
    }


def _full_config_dict():
    return {
        "providers": [
            {
                "name": "openai",
                "client_type": "openai",
                "priority": 1,
                "model_groups": [
                    {
                        "name": "flagship",
                        "tier": 1,
                        "models": ["gpt-4o", "gpt-4-turbo"],
                        "token_quota": {"limit": 250000, "reset": "daily_utc"},
                    },
                    {
                        "name": "mini",
                        "tier": 3,
                        "models": ["gpt-4o-mini"],
                    },
                ],
                "keys": [
                    {"token": "sk-key-1", "alias": "main_key"},
                    {"token": "sk-key-2", "alias": "backup_key"},
                ],
            },
            {
                "name": "gemini",
                "client_type": "gemini",
                "priority": 2,
                "models": ["gemini-2.0-flash"],
                "keys": [{"token": "AIza-test", "alias": "google_key"}],
            },
        ]
    }


class TestFromJsonFile:
    def test_from_json_file_valid(self, tmp_path: Path):
        config_file = tmp_path / "config.json"
        config_file.write_text(json.dumps(_minimal_config_dict()))

        cfg = RotatorConfig.from_json(config_file)

        assert len(cfg.providers) == 1
        assert cfg.providers[0].name == "openai"
        assert cfg.providers[0].keys[0].token == "sk-test-123"

    def test_from_json_file_str_path(self, tmp_path: Path):
        config_file = tmp_path / "config.json"
        config_file.write_text(json.dumps(_minimal_config_dict()))

        cfg = RotatorConfig.from_json(str(config_file))

        assert cfg.providers[0].name == "openai"

    def test_from_json_file_full(self, tmp_path: Path):
        config_file = tmp_path / "config.json"
        config_file.write_text(json.dumps(_full_config_dict()))

        cfg = RotatorConfig.from_json(config_file)

        assert len(cfg.providers) == 2
        assert cfg.providers[0].name == "openai"
        assert len(cfg.providers[0].model_groups) == 2
        assert cfg.providers[0].model_groups[0].token_quota.limit == 250000
        assert cfg.providers[1].name == "gemini"

    def test_from_json_file_not_found(self):
        with pytest.raises(FileNotFoundError):
            RotatorConfig.from_json("/nonexistent/path/config.json")

    def test_from_json_file_invalid_json(self, tmp_path: Path):
        config_file = tmp_path / "bad.json"
        config_file.write_text("{invalid json content!!!")

        with pytest.raises(ValueError, match=r"[Ii]nvalid JSON"):
            RotatorConfig.from_json(config_file)

    def test_from_json_file_validation_error(self, tmp_path: Path):
        config_file = tmp_path / "config.json"
        config_file.write_text(json.dumps({"providers": []}))

        with pytest.raises(ValidationError):
            RotatorConfig.from_json(config_file)


class TestEnvSubstitution:
    def test_env_dollar_var(self, tmp_path: Path, monkeypatch):
        monkeypatch.setenv("TEST_OPENAI_KEY", "sk-from-env-123")

        config = _minimal_config_dict()
        config["providers"][0]["keys"][0]["token"] = "$TEST_OPENAI_KEY"

        config_file = tmp_path / "config.json"
        config_file.write_text(json.dumps(config))

        cfg = RotatorConfig.from_json(config_file)

        assert cfg.providers[0].keys[0].token == "sk-from-env-123"

    def test_env_braced_var(self, tmp_path: Path, monkeypatch):
        monkeypatch.setenv("TEST_OPENAI_KEY", "sk-braced-456")

        config = _minimal_config_dict()
        config["providers"][0]["keys"][0]["token"] = "${TEST_OPENAI_KEY}"

        config_file = tmp_path / "config.json"
        config_file.write_text(json.dumps(config))

        cfg = RotatorConfig.from_json(config_file)

        assert cfg.providers[0].keys[0].token == "sk-braced-456"

    def test_env_missing_raises(self, tmp_path: Path, monkeypatch):
        monkeypatch.delenv("NONEXISTENT_VAR_12345", raising=False)

        config = _minimal_config_dict()
        config["providers"][0]["keys"][0]["token"] = "$NONEXISTENT_VAR_12345"

        config_file = tmp_path / "config.json"
        config_file.write_text(json.dumps(config))

        with pytest.raises(ValueError, match="NONEXISTENT_VAR_12345"):
            RotatorConfig.from_json(config_file)

    def test_env_default_value(self, tmp_path: Path, monkeypatch):
        monkeypatch.delenv("MISSING_KEY_WITH_DEFAULT", raising=False)

        config = _minimal_config_dict()
        config["providers"][0]["keys"][0]["token"] = "${MISSING_KEY_WITH_DEFAULT:-sk-default-val}"

        config_file = tmp_path / "config.json"
        config_file.write_text(json.dumps(config))

        cfg = RotatorConfig.from_json(config_file)

        assert cfg.providers[0].keys[0].token == "sk-default-val"

    def test_env_default_value_not_used_when_set(self, tmp_path: Path, monkeypatch):
        monkeypatch.setenv("EXISTING_KEY", "sk-real-value")

        config = _minimal_config_dict()
        config["providers"][0]["keys"][0]["token"] = "${EXISTING_KEY:-sk-default}"

        config_file = tmp_path / "config.json"
        config_file.write_text(json.dumps(config))

        cfg = RotatorConfig.from_json(config_file)

        assert cfg.providers[0].keys[0].token == "sk-real-value"

    def test_env_empty_default(self, tmp_path: Path, monkeypatch):
        monkeypatch.delenv("MISSING_KEY_EMPTY_DEFAULT", raising=False)

        config = _minimal_config_dict()
        config["providers"][0]["keys"][0]["token"] = "${MISSING_KEY_EMPTY_DEFAULT:-}"

        config_file = tmp_path / "config.json"
        config_file.write_text(json.dumps(config))

        cfg = RotatorConfig.from_json(config_file)

        assert cfg.providers[0].keys[0].token == ""

    def test_env_substitution_in_nested_values(self, tmp_path: Path, monkeypatch):
        monkeypatch.setenv("TEST_KEY_A", "sk-key-a")
        monkeypatch.setenv("TEST_KEY_B", "sk-key-b")

        config = _minimal_config_dict()
        config["providers"][0]["keys"] = [
            {"token": "$TEST_KEY_A", "alias": "key_a"},
            {"token": "${TEST_KEY_B}", "alias": "key_b"},
        ]

        config_file = tmp_path / "config.json"
        config_file.write_text(json.dumps(config))

        cfg = RotatorConfig.from_json(config_file)

        assert cfg.providers[0].keys[0].token == "sk-key-a"
        assert cfg.providers[0].keys[1].token == "sk-key-b"

    def test_env_no_substitution_for_plain_strings(self, tmp_path: Path):
        """Plain strings without $ are left unchanged."""
        config_file = tmp_path / "config.json"
        config_file.write_text(json.dumps(_minimal_config_dict()))

        cfg = RotatorConfig.from_json(config_file)

        assert cfg.providers[0].keys[0].token == "sk-test-123"
        assert cfg.providers[0].name == "openai"

    def test_env_substitution_in_alias(self, tmp_path: Path, monkeypatch):
        """Env substitution works in any string field, not just tokens."""
        monkeypatch.setenv("MY_ALIAS", "dynamic-alias")

        config = _minimal_config_dict()
        config["providers"][0]["keys"][0]["alias"] = "$MY_ALIAS"

        config_file = tmp_path / "config.json"
        config_file.write_text(json.dumps(config))

        cfg = RotatorConfig.from_json(config_file)

        assert cfg.providers[0].keys[0].alias == "dynamic-alias"


class TestLLMRotatorFromJson:
    def test_from_json_creates_rotator(self, tmp_path: Path):
        from llm_rotator.rotator import LLMRotator

        config_file = tmp_path / "config.json"
        config_file.write_text(json.dumps(_minimal_config_dict()))

        rotator = LLMRotator.from_json(config_file)

        assert isinstance(rotator, LLMRotator)
        assert rotator._config.providers[0].name == "openai"

    def test_from_json_with_kwargs(self, tmp_path: Path):
        from llm_rotator.backends import InMemoryBackend
        from llm_rotator.rotator import LLMRotator

        config_file = tmp_path / "config.json"
        config_file.write_text(json.dumps(_minimal_config_dict()))

        backend = InMemoryBackend()
        rotator = LLMRotator.from_json(config_file, backend=backend)

        assert rotator._backend is backend
