"""
Tests for the configuration management module.

This module contains unit tests for the Config class,
validating configuration loading, saving, and management functionality.
"""

import tempfile
import json
from pathlib import Path
from unittest.mock import patch, mock_open

from hosts.core.config import Config


class TestConfig:
    """Test cases for the Config class."""

    def test_config_initialization(self):
        """Test basic config initialization with defaults."""
        with patch.object(Config, "load"):
            config = Config()

            # Check default settings
            assert config.get("show_default_entries") is False
            assert len(config.get("default_entries", [])) == 3
            assert config.get("window_settings", {}).get("last_sort_column") == ""
            assert config.get("window_settings", {}).get("last_sort_ascending") is True

    def test_default_settings_structure(self):
        """Test that default settings have the expected structure."""
        with patch.object(Config, "load"):
            config = Config()

            default_entries = config.get("default_entries", [])
            assert len(default_entries) == 3

            # Check localhost entries
            localhost_entries = [
                e for e in default_entries if e["hostname"] == "localhost"
            ]
            assert len(localhost_entries) == 2  # IPv4 and IPv6

            # Check broadcasthost entry
            broadcast_entries = [
                e for e in default_entries if e["hostname"] == "broadcasthost"
            ]
            assert len(broadcast_entries) == 1
            assert broadcast_entries[0]["ip"] == "255.255.255.255"

    def test_config_paths(self):
        """Test that config paths are set correctly."""
        with patch.object(Config, "load"):
            config = Config()

            expected_dir = Path.home() / ".config" / "hosts-manager"
            expected_file = expected_dir / "config.json"

            assert config.config_dir == expected_dir
            assert config.config_file == expected_file

    def test_get_existing_key(self):
        """Test getting an existing configuration key."""
        with patch.object(Config, "load"):
            config = Config()

            result = config.get("show_default_entries")
            assert result is False

    def test_get_nonexistent_key_with_default(self):
        """Test getting a nonexistent key with default value."""
        with patch.object(Config, "load"):
            config = Config()

            result = config.get("nonexistent_key", "default_value")
            assert result == "default_value"

    def test_get_nonexistent_key_without_default(self):
        """Test getting a nonexistent key without default value."""
        with patch.object(Config, "load"):
            config = Config()

            result = config.get("nonexistent_key")
            assert result is None

    def test_set_configuration_value(self):
        """Test setting a configuration value."""
        with patch.object(Config, "load"):
            config = Config()

            config.set("test_key", "test_value")
            assert config.get("test_key") == "test_value"

    def test_set_overwrites_existing_value(self):
        """Test that setting overwrites existing values."""
        with patch.object(Config, "load"):
            config = Config()

            # Set initial value
            config.set("show_default_entries", True)
            assert config.get("show_default_entries") is True

            # Overwrite with new value
            config.set("show_default_entries", False)
            assert config.get("show_default_entries") is False

    def test_is_default_entry_true(self):
        """Test identifying default entries correctly."""
        with patch.object(Config, "load"):
            config = Config()

            # Test localhost IPv4
            assert config.is_default_entry("127.0.0.1", "localhost") is True

            # Test localhost IPv6
            assert config.is_default_entry("::1", "localhost") is True

            # Test broadcasthost
            assert config.is_default_entry("255.255.255.255", "broadcasthost") is True

    def test_is_default_entry_false(self):
        """Test that non-default entries are not identified as default."""
        with patch.object(Config, "load"):
            config = Config()

            # Test custom entries
            assert config.is_default_entry("192.168.1.1", "router") is False
            assert config.is_default_entry("10.0.0.1", "test.local") is False
            assert config.is_default_entry("127.0.0.1", "custom") is False

    def test_should_show_default_entries_default(self):
        """Test default value for show_default_entries."""
        with patch.object(Config, "load"):
            config = Config()

            assert config.should_show_default_entries() is False

    def test_should_show_default_entries_configured(self):
        """Test configured value for show_default_entries."""
        with patch.object(Config, "load"):
            config = Config()
            config.set("show_default_entries", True)

            assert config.should_show_default_entries() is True

    def test_toggle_show_default_entries(self):
        """Test toggling the show_default_entries setting."""
        with patch.object(Config, "load"), patch.object(Config, "save") as mock_save:
            config = Config()

            # Initial state should be False
            assert config.should_show_default_entries() is False

            # Toggle to True
            config.toggle_show_default_entries()
            assert config.should_show_default_entries() is True
            mock_save.assert_called_once()

            # Toggle back to False
            mock_save.reset_mock()
            config.toggle_show_default_entries()
            assert config.should_show_default_entries() is False
            mock_save.assert_called_once()

    def test_load_nonexistent_file(self):
        """Test loading config when file doesn't exist."""
        with patch("pathlib.Path.exists", return_value=False):
            config = Config()

            # Should use defaults when file doesn't exist
            assert config.get("show_default_entries") is False

    def test_load_existing_file(self):
        """Test loading config from existing file."""
        test_config = {"show_default_entries": True, "custom_setting": "custom_value"}

        with (
            patch("pathlib.Path.exists", return_value=True),
            patch("builtins.open", mock_open(read_data=json.dumps(test_config))),
        ):
            config = Config()

            # Should load values from file
            assert config.get("show_default_entries") is True
            assert config.get("custom_setting") == "custom_value"

            # Should still have defaults for missing keys
            assert len(config.get("default_entries", [])) == 3

    def test_load_invalid_json(self):
        """Test loading config with invalid JSON falls back to defaults."""
        with (
            patch("pathlib.Path.exists", return_value=True),
            patch("builtins.open", mock_open(read_data="invalid json")),
        ):
            config = Config()

            # Should use defaults when JSON is invalid
            assert config.get("show_default_entries") is False

    def test_load_file_io_error(self):
        """Test loading config with file I/O error falls back to defaults."""
        with (
            patch("pathlib.Path.exists", return_value=True),
            patch("builtins.open", side_effect=IOError("File error")),
        ):
            config = Config()

            # Should use defaults when file can't be read
            assert config.get("show_default_entries") is False

    def test_save_creates_directory(self):
        """Test that save creates config directory if it doesn't exist."""
        with (
            patch.object(Config, "load"),
            patch("pathlib.Path.mkdir") as mock_mkdir,
            patch("builtins.open", mock_open()) as mock_file,
        ):
            config = Config()
            config.save()

            # Should create directory with parents=True, exist_ok=True
            mock_mkdir.assert_called_once_with(parents=True, exist_ok=True)
            mock_file.assert_called_once()

    def test_save_writes_json(self):
        """Test that save writes configuration as JSON."""
        with (
            patch.object(Config, "load"),
            patch("pathlib.Path.mkdir"),
            patch("builtins.open", mock_open()) as mock_file,
        ):
            config = Config()
            config.set("test_key", "test_value")
            config.save()

            # Check that file was opened for writing
            mock_file.assert_called_once_with(config.config_file, "w")

            # Check that JSON was written
            handle = mock_file()
            written_data = "".join(call.args[0] for call in handle.write.call_args_list)

            # Should be valid JSON containing our test data
            parsed_data = json.loads(written_data)
            assert parsed_data["test_key"] == "test_value"

    def test_save_io_error_silent_fail(self):
        """Test that save silently fails on I/O error."""
        with (
            patch.object(Config, "load"),
            patch("pathlib.Path.mkdir"),
            patch("builtins.open", side_effect=IOError("Write error")),
        ):
            config = Config()

            # Should not raise exception
            config.save()

    def test_save_directory_creation_error_silent_fail(self):
        """Test that save silently fails on directory creation error."""
        with (
            patch.object(Config, "load"),
            patch("pathlib.Path.mkdir", side_effect=OSError("Permission denied")),
        ):
            config = Config()

            # Should not raise exception
            config.save()

    def test_integration_load_save_roundtrip(self):
        """Test complete load/save cycle with temporary file."""
        with tempfile.TemporaryDirectory() as temp_dir:
            config_dir = Path(temp_dir) / "hosts-manager"
            config_file = config_dir / "config.json"

            with patch.object(Config, "__init__", lambda self: None):
                config = Config()
                config.config_dir = config_dir
                config.config_file = config_file
                config._settings = config._load_default_settings()

                # Modify some settings
                config.set("show_default_entries", True)
                config.set("custom_setting", "test_value")

                # Save configuration
                config.save()

                # Verify file was created
                assert config_file.exists()

                # Create new config instance and load
                config2 = Config()
                config2.config_dir = config_dir
                config2.config_file = config_file
                config2._settings = config2._load_default_settings()
                config2.load()

                # Verify settings were loaded correctly
                assert config2.get("show_default_entries") is True
                assert config2.get("custom_setting") == "test_value"

                # Verify defaults are still present
                assert len(config2.get("default_entries", [])) == 3
