"""Comprehensive tests for src/config.py."""

import json
import pytest
from pathlib import Path
from src.config import Config, load_config


@pytest.fixture
def tmp_config(tmp_path):
    """Create a temporary config file and return its path."""
    data = {
        "bot": {
            "name": "TestBot",
            "default_model": "sonnet",
            "owner": None,
            "admins": []
        },
        "channels": {},
        "heartbeat": {
            "enabled": True,
            "interval_minutes": 30
        },
        "ollama": {
            "url": "http://localhost:11434"
        },
        "paths": {
            "personality": "personality/",
            "tools": "tools/",
            "memory": "memory/"
        }
    }
    config_file = tmp_path / "config.json"
    config_file.write_text(json.dumps(data, indent=2))
    return config_file


class TestConfigLoad:
    def test_load_from_path(self, tmp_config):
        cfg = Config(tmp_config)
        assert cfg.get("bot.name") == "TestBot"

    def test_load_config_convenience(self, tmp_config):
        cfg = load_config(tmp_config)
        assert cfg.get("bot.name") == "TestBot"

    def test_load_missing_file_raises(self, tmp_path):
        with pytest.raises(FileNotFoundError):
            Config(tmp_path / "nonexistent.json")

    def test_load_invalid_json_raises(self, tmp_path):
        bad = tmp_path / "bad.json"
        bad.write_text("not json {{{")
        with pytest.raises(json.JSONDecodeError):
            Config(bad)


class TestConfigGet:
    def test_top_level_key(self, tmp_config):
        cfg = Config(tmp_config)
        assert cfg.get("channels") == {}

    def test_nested_key(self, tmp_config):
        cfg = Config(tmp_config)
        assert cfg.get("bot.default_model") == "sonnet"

    def test_deeply_nested(self, tmp_config):
        cfg = Config(tmp_config)
        assert cfg.get("heartbeat.interval_minutes") == 30

    def test_missing_key_returns_none(self, tmp_config):
        cfg = Config(tmp_config)
        assert cfg.get("nonexistent") is None

    def test_missing_key_returns_default(self, tmp_config):
        cfg = Config(tmp_config)
        assert cfg.get("nonexistent", "fallback") == "fallback"

    def test_missing_nested_key_returns_default(self, tmp_config):
        cfg = Config(tmp_config)
        assert cfg.get("bot.nonexistent.deep", 42) == 42

    def test_null_value_returned_as_none(self, tmp_config):
        cfg = Config(tmp_config)
        assert cfg.get("bot.owner") is None

    def test_list_value(self, tmp_config):
        cfg = Config(tmp_config)
        assert cfg.get("bot.admins") == []

    def test_bool_value(self, tmp_config):
        cfg = Config(tmp_config)
        assert cfg.get("heartbeat.enabled") is True


class TestConfigSet:
    def test_set_existing_key(self, tmp_config):
        cfg = Config(tmp_config)
        cfg.set("bot.name", "NewName")
        assert cfg.get("bot.name") == "NewName"

    def test_set_new_nested_key(self, tmp_config):
        cfg = Config(tmp_config)
        cfg.set("new.nested.key", 42)
        assert cfg.get("new.nested.key") == 42

    def test_set_top_level_key(self, tmp_config):
        cfg = Config(tmp_config)
        cfg.set("debug", True)
        assert cfg.get("debug") is True

    def test_set_overwrites_non_dict(self, tmp_config):
        cfg = Config(tmp_config)
        cfg.set("bot.name.sub", "value")
        assert cfg.get("bot.name.sub") == "value"


class TestConfigSave:
    def test_save_persists_changes(self, tmp_config):
        cfg = Config(tmp_config)
        cfg.set("bot.name", "Saved")
        cfg.save()

        cfg2 = Config(tmp_config)
        assert cfg2.get("bot.name") == "Saved"

    def test_save_preserves_structure(self, tmp_config):
        cfg = Config(tmp_config)
        cfg.set("new_key", "new_value")
        cfg.save()

        raw = json.loads(tmp_config.read_text())
        assert raw["bot"]["name"] == "TestBot"
        assert raw["new_key"] == "new_value"


class TestConfigReload:
    def test_reload_picks_up_external_changes(self, tmp_config):
        cfg = Config(tmp_config)
        assert cfg.get("bot.name") == "TestBot"

        data = json.loads(tmp_config.read_text())
        data["bot"]["name"] = "ExternalChange"
        tmp_config.write_text(json.dumps(data))

        cfg.reload()
        assert cfg.get("bot.name") == "ExternalChange"


class TestConfigRaw:
    def test_raw_returns_dict(self, tmp_config):
        cfg = Config(tmp_config)
        raw = cfg.raw()
        assert isinstance(raw, dict)
        assert raw["bot"]["name"] == "TestBot"

    def test_raw_is_reference(self, tmp_config):
        cfg = Config(tmp_config)
        raw = cfg.raw()
        raw["bot"]["name"] = "Mutated"
        assert cfg.get("bot.name") == "Mutated"
