"""
Unit tests for configuration loading and overrides.

Tests cover:
- Valid YAML config loading
- Valid JSON config loading
- Invalid YAML/JSON error behavior
- Missing config file behavior
- Template completion
"""

from __future__ import annotations

import pytest
import json

from langbot.pkg.config.impls.yaml import YAMLConfigFile
from langbot.pkg.config.impls.json import JSONConfigFile
from langbot.pkg.config.manager import ConfigManager


class TestYAMLConfigFile:
    """Tests for YAML config file handling."""

    @pytest.mark.asyncio
    async def test_valid_yaml_loads(self, tmp_path):
        """Valid YAML config should load correctly."""
        config_file = tmp_path / 'test_config.yaml'

        # Write valid YAML
        config_file.write_text("""
name: test_app
version: 1.0
settings:
  debug: true
  port: 8080
""")

        yaml_file = YAMLConfigFile(
            str(config_file),
            template_data={'name': 'default', 'version': '0.1'},
        )

        result = await yaml_file.load(completion=False)

        assert result['name'] == 'test_app'
        assert result['version'] == 1.0
        assert result['settings']['debug'] is True
        assert result['settings']['port'] == 8080

    @pytest.mark.asyncio
    async def test_invalid_yaml_raises_error(self, tmp_path):
        """Invalid YAML should raise clear error."""
        config_file = tmp_path / 'invalid.yaml'

        # Write invalid YAML (unclosed bracket)
        config_file.write_text("""
name: test
settings:
  - item1
  - item2
  - [unclosed
""")

        yaml_file = YAMLConfigFile(
            str(config_file),
            template_data={'name': 'default'},
        )

        with pytest.raises(Exception, match='Syntax error'):
            await yaml_file.load(completion=False)

    @pytest.mark.asyncio
    async def test_missing_config_creates_from_template(self, tmp_path):
        """Missing config file should be created from template."""
        config_file = tmp_path / 'new_config.yaml'

        # File doesn't exist yet
        assert not config_file.exists()

        yaml_file = YAMLConfigFile(
            str(config_file),
            template_data={'name': 'new_app', 'version': '1.0'},
        )

        result = await yaml_file.load()

        assert config_file.exists()
        assert result['name'] == 'new_app'
        assert result['version'] == '1.0'

    @pytest.mark.asyncio
    async def test_template_completion(self, tmp_path):
        """Config should be completed with template defaults."""
        config_file = tmp_path / 'partial.yaml'

        # Write partial config missing some template keys
        config_file.write_text("""
name: custom_name
""")

        yaml_file = YAMLConfigFile(
            str(config_file),
            template_data={'name': 'default_name', 'version': '2.0', 'debug': False},
        )

        result = await yaml_file.load(completion=True)

        # Existing key preserved
        assert result['name'] == 'custom_name'
        # Missing keys filled from template
        assert result['version'] == '2.0'
        assert result['debug'] is False

    @pytest.mark.asyncio
    async def test_yaml_save(self, tmp_path):
        """YAML config can be saved."""
        config_file = tmp_path / 'save_test.yaml'

        yaml_file = YAMLConfigFile(
            str(config_file),
            template_data={'name': 'test'},
        )

        await yaml_file.save({'name': 'saved_app', 'new_key': 'new_value'})

        assert config_file.exists()
        content = config_file.read_text()
        assert 'saved_app' in content
        assert 'new_key' in content

    def test_yaml_save_sync(self, tmp_path):
        """YAML config can be saved synchronously."""
        config_file = tmp_path / 'sync_save.yaml'

        yaml_file = YAMLConfigFile(
            str(config_file),
            template_data={'name': 'test'},
        )

        yaml_file.save_sync({'name': 'sync_saved'})

        assert config_file.exists()
        content = config_file.read_text()
        assert 'sync_saved' in content


class TestJSONConfigFile:
    """Tests for JSON config file handling."""

    @pytest.mark.asyncio
    async def test_valid_json_loads(self, tmp_path):
        """Valid JSON config should load correctly."""
        config_file = tmp_path / 'test_config.json'

        # Write valid JSON
        config_file.write_text(
            json.dumps(
                {
                    'name': 'json_app',
                    'version': '1.0',
                    'settings': {'debug': True, 'port': 8080},
                }
            )
        )

        json_file = JSONConfigFile(
            str(config_file),
            template_data={'name': 'default', 'version': '0.1'},
        )

        result = await json_file.load(completion=False)

        assert result['name'] == 'json_app'
        assert result['version'] == '1.0'
        assert result['settings']['debug'] is True

    @pytest.mark.asyncio
    async def test_invalid_json_raises_error(self, tmp_path):
        """Invalid JSON should raise clear error."""
        config_file = tmp_path / 'invalid.json'

        # Write invalid JSON (missing closing brace)
        config_file.write_text('{"name": "test", "unclosed": ')

        json_file = JSONConfigFile(
            str(config_file),
            template_data={'name': 'default'},
        )

        with pytest.raises(Exception, match='Syntax error'):
            await json_file.load(completion=False)

    @pytest.mark.asyncio
    async def test_missing_json_creates_from_template(self, tmp_path):
        """Missing JSON file should be created from template."""
        config_file = tmp_path / 'new_config.json'

        json_file = JSONConfigFile(
            str(config_file),
            template_data={'name': 'new_json_app', 'version': '1.0'},
        )

        result = await json_file.load()

        assert config_file.exists()
        assert result['name'] == 'new_json_app'

    @pytest.mark.asyncio
    async def test_json_save(self, tmp_path):
        """JSON config can be saved."""
        config_file = tmp_path / 'save_test.json'

        json_file = JSONConfigFile(
            str(config_file),
            template_data={'name': 'test'},
        )

        await json_file.save({'name': 'saved_json', 'new_key': 'value'})

        assert config_file.exists()
        content = config_file.read_text()
        data = json.loads(content)
        assert data['name'] == 'saved_json'


class TestConfigManager:
    """Tests for ConfigManager."""

    @pytest.mark.asyncio
    async def test_config_manager_load(self, tmp_path):
        """ConfigManager loads config correctly."""
        config_file = tmp_path / 'manager_test.yaml'
        config_file.write_text('name: managed_app\nversion: "1.0"\n')

        yaml_file = YAMLConfigFile(
            str(config_file),
            template_data={'name': 'default', 'version': '0.1'},
        )

        manager = ConfigManager(yaml_file)
        await manager.load_config()

        assert manager.data['name'] == 'managed_app'
        assert manager.data['version'] == '1.0'

    @pytest.mark.asyncio
    async def test_config_manager_dump(self, tmp_path):
        """ConfigManager can dump config."""
        config_file = tmp_path / 'dump_test.yaml'

        yaml_file = YAMLConfigFile(
            str(config_file),
            template_data={'name': 'default'},
        )

        manager = ConfigManager(yaml_file)
        manager.data = {'name': 'dumped', 'new_field': 'value'}

        await manager.dump_config()

        content = config_file.read_text()
        assert 'dumped' in content

    def test_config_manager_dump_sync(self, tmp_path):
        """ConfigManager can dump config synchronously."""
        config_file = tmp_path / 'sync_dump.yaml'

        yaml_file = YAMLConfigFile(
            str(config_file),
            template_data={'name': 'default'},
        )

        manager = ConfigManager(yaml_file)
        manager.data = {'name': 'sync_dumped'}

        manager.dump_config_sync()

        assert config_file.exists()


class TestConfigExists:
    """Tests for config file existence check."""

    def test_yaml_exists_true(self, tmp_path):
        """exists() returns True for existing file."""
        config_file = tmp_path / 'exists.yaml'
        config_file.write_text('name: test')

        yaml_file = YAMLConfigFile(str(config_file), template_data={})
        assert yaml_file.exists() is True

    def test_yaml_exists_false(self, tmp_path):
        """exists() returns False for missing file."""
        config_file = tmp_path / 'missing.yaml'

        yaml_file = YAMLConfigFile(str(config_file), template_data={})
        assert yaml_file.exists() is False

    def test_json_exists_true(self, tmp_path):
        """exists() returns True for existing JSON file."""
        config_file = tmp_path / 'exists.json'
        config_file.write_text('{}')

        json_file = JSONConfigFile(str(config_file), template_data={})
        assert json_file.exists() is True

    def test_json_exists_false(self, tmp_path):
        """exists() returns False for missing JSON file."""
        config_file = tmp_path / 'missing.json'

        json_file = JSONConfigFile(str(config_file), template_data={})
        assert json_file.exists() is False
