"""Tests for rbsp.runtime.config -- configuration loading, saving, defaults."""

import json
import os
import sys
import unittest

sys.path.insert(0, os.path.dirname(__file__))
from conftest import RBSPTestCase, FIXED_TIMESTAMP, SAMPLE_TEXTS

from rbsp.runtime.config import RBSPConfig, _DEFAULTS


class TestConfigDefaults(RBSPTestCase):
    """Tests for default configuration values."""

    def test_load_returns_config(self):
        config = RBSPConfig.load(self.tmp_dir)
        self.assertIsInstance(config, RBSPConfig)

    def test_default_max_file_size(self):
        config = RBSPConfig.load(self.tmp_dir)
        self.assertEqual(config.max_file_size, _DEFAULTS["max_file_size"])

    def test_default_max_results(self):
        config = RBSPConfig.load(self.tmp_dir)
        self.assertEqual(config.max_results, _DEFAULTS["max_results"])

    def test_default_resonance_threshold(self):
        config = RBSPConfig.load(self.tmp_dir)
        self.assertAlmostEqual(config.resonance_threshold, _DEFAULTS["resonance_threshold"])

    def test_default_watch_interval(self):
        config = RBSPConfig.load(self.tmp_dir)
        self.assertAlmostEqual(config.watch_interval, _DEFAULTS["watch_interval"])

    def test_default_file_extensions(self):
        config = RBSPConfig.load(self.tmp_dir)
        self.assertIsInstance(config.file_extensions, list)
        self.assertIn(".py", config.file_extensions)

    def test_default_ignored_dirs(self):
        config = RBSPConfig.load(self.tmp_dir)
        self.assertIsInstance(config.ignored_dirs, list)
        self.assertIn("node_modules", config.ignored_dirs)
        self.assertIn("__pycache__", config.ignored_dirs)

    def test_default_index_path(self):
        config = RBSPConfig.load(self.tmp_dir)
        self.assertEqual(config.index_path, _DEFAULTS["index_path"])

    def test_root_set_to_resolved_path(self):
        config = RBSPConfig.load(self.tmp_dir)
        self.assertTrue(os.path.isabs(config.root))


class TestConfigOverrides(RBSPTestCase):
    """Tests for configuration override mechanism."""

    def test_override_max_results(self):
        config = RBSPConfig.load(self.tmp_dir, overrides={"max_results": 10})
        self.assertEqual(config.max_results, 10)

    def test_override_resonance_threshold(self):
        config = RBSPConfig.load(self.tmp_dir, overrides={"resonance_threshold": 0.5})
        self.assertAlmostEqual(config.resonance_threshold, 0.5)

    def test_override_max_file_size(self):
        config = RBSPConfig.load(self.tmp_dir, overrides={"max_file_size": 500})
        self.assertEqual(config.max_file_size, 500)

    def test_override_watch_interval(self):
        config = RBSPConfig.load(self.tmp_dir, overrides={"watch_interval": 5.0})
        self.assertAlmostEqual(config.watch_interval, 5.0)

    def test_override_file_extensions(self):
        config = RBSPConfig.load(self.tmp_dir, overrides={"file_extensions": [".py", ".js"]})
        self.assertEqual(config.file_extensions, [".py", ".js"])

    def test_override_ignored_dirs(self):
        config = RBSPConfig.load(self.tmp_dir, overrides={"ignored_dirs": [".git"]})
        self.assertEqual(config.ignored_dirs, [".git"])

    def test_multiple_overrides(self):
        config = RBSPConfig.load(
            self.tmp_dir,
            overrides={"max_results": 5, "verbose": True},
        )
        self.assertEqual(config.max_results, 5)
        self.assertTrue(config.verbose)


class TestConfigJsonPersistence(RBSPTestCase):
    """Tests for loading/saving config.json."""

    def test_save_json_creates_file(self):
        config = RBSPConfig.load(self.tmp_dir)
        path = config.save_json()
        self.assertTrue(path.is_file())

    def test_save_json_valid_json(self):
        config = RBSPConfig.load(self.tmp_dir)
        path = config.save_json()
        with open(path, "r", encoding="utf-8") as f:
            data = json.load(f)
        self.assertIsInstance(data, dict)

    def test_save_json_excludes_root(self):
        config = RBSPConfig.load(self.tmp_dir)
        path = config.save_json()
        with open(path, "r", encoding="utf-8") as f:
            data = json.load(f)
        self.assertNotIn("root", data)

    def test_save_json_custom_path(self):
        config = RBSPConfig.load(self.tmp_dir)
        custom_path = os.path.join(self.tmp_dir, "custom", "config.json")
        path = config.save_json(custom_path)
        self.assertTrue(path.is_file())

    def test_load_from_saved_json(self):
        original = RBSPConfig.load(self.tmp_dir, overrides={"max_results": 7})
        original.save_json()
        # Load from the same root, which should pick up .rbsp/config.json
        loaded = RBSPConfig.load(self.tmp_dir)
        self.assertEqual(loaded.max_results, 7)


class TestConfigResolvePath(RBSPTestCase):
    """Tests for RBSPConfig.resolve_path()."""

    def test_resolve_relative_path(self):
        config = RBSPConfig.load(self.tmp_dir)
        resolved = config.resolve_path("index_path")
        self.assertTrue(resolved.is_absolute())
        self.assertIn(".rbsp", str(resolved))

    def test_resolve_absolute_path(self):
        abs_path = os.path.join(self.tmp_dir, "custom_index")
        config = RBSPConfig.load(
            self.tmp_dir,
            overrides={"index_path": abs_path},
        )
        resolved = config.resolve_path("index_path")
        self.assertEqual(str(resolved), abs_path)

    def test_resolve_plugins_path(self):
        config = RBSPConfig.load(self.tmp_dir)
        resolved = config.resolve_path("plugins_path")
        self.assertTrue(resolved.is_absolute())


class TestConfigToDict(RBSPTestCase):
    """Tests for RBSPConfig.to_dict() and get()."""

    def test_to_dict_returns_dict(self):
        config = RBSPConfig.load(self.tmp_dir)
        d = config.to_dict()
        self.assertIsInstance(d, dict)

    def test_to_dict_contains_all_fields(self):
        config = RBSPConfig.load(self.tmp_dir)
        d = config.to_dict()
        for key in ("index_path", "max_results", "resonance_threshold", "root"):
            self.assertIn(key, d)

    def test_get_existing_key(self):
        config = RBSPConfig.load(self.tmp_dir)
        val = config.get("max_results")
        self.assertEqual(val, config.max_results)

    def test_get_missing_key_returns_default(self):
        config = RBSPConfig.load(self.tmp_dir)
        val = config.get("nonexistent_key", "fallback")
        self.assertEqual(val, "fallback")


if __name__ == "__main__":
    unittest.main()
