"""Tests for the configuration manager."""

import time
from pathlib import Path

import pytest

from ghostty_tui.config_manager import (
    ConfigManager,
    ConfigReadError,
    ConfigWriteError,
    ConfigError,
)


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

    def test_load_config(self, tmp_config_file):
        """Test loading configuration file."""
        manager = ConfigManager(tmp_config_file)
        values = manager.load()
        assert "font-size" in values
        assert values["font-size"].value == "14"

    def test_load_returns_dict(self, tmp_config_file):
        """Test load returns a dictionary."""
        manager = ConfigManager(tmp_config_file)
        values = manager.load()
        assert isinstance(values, dict)

    def test_load_nonexistent_returns_empty(self, tmp_config_dir):
        """Test loading non-existent file returns empty dict."""
        manager = ConfigManager(tmp_config_dir / "nonexistent.config")
        values = manager.load()
        assert len(values) == 0

    def test_load_parses_all_options(self, tmp_config_file):
        """Test all options in file are loaded."""
        manager = ConfigManager(tmp_config_file)
        values = manager.load()
        # Based on fixture: font-size, cursor-style, window-title
        assert "font-size" in values
        assert "cursor-style" in values
        assert "window-title" in values

    def test_load_preserves_line_numbers(self, tmp_config_file):
        """Test line numbers are preserved."""
        manager = ConfigManager(tmp_config_file)
        values = manager.load()
        assert values["font-size"].line_number is not None
        assert values["font-size"].line_number > 0

    def test_load_commented_option(self, tmp_config_file):
        """Test commented options are loaded."""
        manager = ConfigManager(tmp_config_file)
        values = manager.load()
        # Based on fixture: # background = #ffffff
        assert "background" in values
        assert values["background"].is_commented_out

    def test_get_value(self, tmp_config_file):
        """Test getting a specific value."""
        manager = ConfigManager(tmp_config_file)
        manager.load()
        value = manager.get("font-size")
        assert value is not None
        assert value.value == "14"

    def test_get_nonexistent_returns_none(self, tmp_config_file):
        """Test getting non-existent value returns None."""
        manager = ConfigManager(tmp_config_file)
        manager.load()
        value = manager.get("nonexistent-option")
        assert value is None

    def test_set_value(self, tmp_config_file):
        """Test setting a value."""
        manager = ConfigManager(tmp_config_file)
        manager.load()
        manager.set("font-size", "16")
        assert manager.get("font-size").value == "16"
        assert manager.get("font-size").is_modified

    def test_set_value_marks_modified(self, tmp_config_file):
        """Test setting a value marks it as modified."""
        manager = ConfigManager(tmp_config_file)
        manager.load()
        manager.set("font-size", "20")
        value = manager.get("font-size")
        assert value.is_modified

    def test_set_same_value_not_modified(self, tmp_config_file):
        """Test setting the same value doesn't mark as modified."""
        manager = ConfigManager(tmp_config_file)
        manager.load()
        original = manager.get("font-size").value
        manager.set("font-size", original)
        assert not manager.get("font-size").is_modified

    def test_set_new_value(self, tmp_config_file):
        """Test setting a new value not in file."""
        manager = ConfigManager(tmp_config_file)
        manager.load()
        manager.set("new-option", "new-value")
        value = manager.get("new-option")
        assert value is not None
        assert value.value == "new-value"
        assert value.is_modified

    def test_set_uncomments_option(self, tmp_config_file):
        """Test setting a commented option uncomments it."""
        manager = ConfigManager(tmp_config_file)
        manager.load()
        # background is commented in fixture
        manager.set("background", "#000000")
        value = manager.get("background")
        assert not value.is_commented_out

    def test_save_creates_file(self, tmp_config_dir):
        """Test save creates new file if it doesn't exist."""
        config_path = tmp_config_dir / "new_config"
        manager = ConfigManager(config_path)
        manager.set("test", "value")
        manager.save()
        assert config_path.exists()

    def test_save_creates_backup(self, tmp_config_file):
        """Test save creates backup file."""
        manager = ConfigManager(tmp_config_file)
        manager.load()
        manager.set("font-size", "20")
        manager.save(backup=True)

        backup_path = tmp_config_file.with_suffix(".config.bak")
        assert backup_path.exists()

    def test_save_no_backup(self, tmp_config_file):
        """Test save without backup."""
        manager = ConfigManager(tmp_config_file)
        manager.load()
        manager.set("font-size", "20")

        backup_path = tmp_config_file.with_suffix(".config.bak")
        if backup_path.exists():
            backup_path.unlink()

        manager.save(backup=False)
        assert not backup_path.exists()

    def test_save_preserves_comments(self, tmp_config_file):
        """Test save preserves comments."""
        manager = ConfigManager(tmp_config_file)
        manager.load()
        manager.set("font-size", "20")
        manager.save()

        content = tmp_config_file.read_text()
        assert "# Test configuration" in content

    def test_save_preserves_structure(self, tmp_config_file):
        """Test save preserves file structure."""
        manager = ConfigManager(tmp_config_file)
        manager.load()
        original_content = tmp_config_file.read_text()

        # Save without changes
        manager.save()
        new_content = tmp_config_file.read_text()

        # Structure should be similar (comments preserved)
        assert "# Test configuration" in new_content
        assert "# Commented option" in new_content

    def test_save_updates_value(self, tmp_config_file):
        """Test save updates values in file."""
        manager = ConfigManager(tmp_config_file)
        manager.load()
        manager.set("font-size", "20")
        manager.save()

        content = tmp_config_file.read_text()
        assert "font-size = 20" in content

    def test_save_adds_new_option(self, tmp_config_file):
        """Test save adds new options to file."""
        manager = ConfigManager(tmp_config_file)
        manager.load()
        manager.set("new-option", "new-value")
        manager.save()

        content = tmp_config_file.read_text()
        assert "new-option = new-value" in content

    def test_save_creates_parent_dir(self, tmp_config_dir):
        """Test save creates parent directories."""
        config_path = tmp_config_dir / "subdir" / "config"
        manager = ConfigManager(config_path)
        manager.set("test", "value")
        manager.save()
        assert config_path.exists()
        assert config_path.parent.exists()

    def test_save_resets_modified_flags(self, tmp_config_file):
        """Test save resets modification flags."""
        manager = ConfigManager(tmp_config_file)
        manager.load()
        manager.set("font-size", "20")
        assert manager.get("font-size").is_modified

        manager.save()
        assert not manager.get("font-size").is_modified

    def test_has_unsaved_changes(self, tmp_config_file):
        """Test has_unsaved_changes property."""
        manager = ConfigManager(tmp_config_file)
        manager.load()
        assert not manager.has_unsaved_changes

        manager.set("font-size", "20")
        assert manager.has_unsaved_changes

    def test_has_unsaved_changes_after_save(self, tmp_config_file):
        """Test has_unsaved_changes is false after save."""
        manager = ConfigManager(tmp_config_file)
        manager.load()
        manager.set("font-size", "20")
        manager.save()
        assert not manager.has_unsaved_changes

    def test_config_exists_property(self, tmp_config_file, tmp_config_dir):
        """Test config_exists property."""
        existing = ConfigManager(tmp_config_file)
        assert existing.config_exists

        nonexistent = ConfigManager(tmp_config_dir / "nope")
        assert not nonexistent.config_exists

    def test_config_path_property(self, tmp_config_file):
        """Test config_path property."""
        manager = ConfigManager(tmp_config_file)
        assert manager.config_path == tmp_config_file

    def test_external_changes_detection(self, tmp_config_file):
        """Test external change detection."""
        manager = ConfigManager(tmp_config_file)
        manager.load()

        # Initially no external changes
        assert not manager.has_external_changes()

        # Simulate external change
        time.sleep(0.1)
        tmp_config_file.write_text("modified = true\n")

        assert manager.has_external_changes()

    def test_get_mtime_nonexistent(self, tmp_config_dir):
        """Test get_mtime for non-existent file."""
        manager = ConfigManager(tmp_config_dir / "nonexistent")
        assert manager.get_mtime() == 0.0

    def test_get_mtime_existing(self, tmp_config_file):
        """Test get_mtime for existing file."""
        manager = ConfigManager(tmp_config_file)
        mtime = manager.get_mtime()
        assert mtime > 0

    def test_load_updates_mtime(self, tmp_config_file):
        """Test that load updates the internal mtime."""
        manager = ConfigManager(tmp_config_file)
        manager.load()

        # Should not detect external changes immediately after load
        assert not manager.has_external_changes()

    def test_save_updates_mtime(self, tmp_config_file):
        """Test that save updates the internal mtime."""
        manager = ConfigManager(tmp_config_file)
        manager.load()
        manager.set("test", "value")
        manager.save()

        # Should not detect external changes immediately after save
        assert not manager.has_external_changes()

    def test_xdg_config_home_respected(self, tmp_config_dir, monkeypatch):
        """Test that XDG_CONFIG_HOME is respected."""
        xdg_path = tmp_config_dir / "xdg_config"
        xdg_path.mkdir()
        monkeypatch.setenv("XDG_CONFIG_HOME", str(xdg_path))

        manager = ConfigManager()
        expected = xdg_path / "ghostty" / "config"
        assert manager.config_path == expected

    def test_default_config_path(self, monkeypatch):
        """Test default config path when XDG_CONFIG_HOME not set."""
        monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)

        manager = ConfigManager()
        expected = Path.home() / ".config" / "ghostty" / "config"
        assert manager.config_path == expected

    def test_custom_path_overrides_xdg(self, tmp_config_file, tmp_config_dir, monkeypatch):
        """Test that custom path overrides XDG_CONFIG_HOME."""
        monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_config_dir))

        manager = ConfigManager(tmp_config_file)
        assert manager.config_path == tmp_config_file

    def test_expanduser_in_path(self, tmp_config_dir):
        """Test that ~ is expanded in paths."""
        # Create a path with ~ (will be expanded)
        manager = ConfigManager("~/test_config")
        assert "~" not in str(manager.config_path)

    def test_inline_comment_preserved(self, tmp_config_dir):
        """Test that inline comments are preserved."""
        config_path = tmp_config_dir / "test_config"
        config_path.write_text("font-size = 12  # my comment\n")

        manager = ConfigManager(config_path)
        manager.load()
        value = manager.get("font-size")
        assert value.comment == "my comment"

    def test_inline_comment_not_in_value(self, tmp_config_dir):
        """Test that inline comments are not included in value."""
        config_path = tmp_config_dir / "test_config"
        config_path.write_text("font-size = 12  # comment\n")

        manager = ConfigManager(config_path)
        manager.load()
        value = manager.get("font-size")
        assert value.value == "12"
        assert "#" not in value.value

    def test_set_none_removes_option(self, tmp_config_file):
        """Test that setting value to None removes it from output."""
        manager = ConfigManager(tmp_config_file)
        manager.load()
        manager.set("font-size", None)
        manager.save()

        content = tmp_config_file.read_text()
        # font-size line should be removed
        assert "font-size = 14" not in content

    def test_multiple_loads(self, tmp_config_file):
        """Test that multiple loads work correctly."""
        manager = ConfigManager(tmp_config_file)

        values1 = manager.load()
        values2 = manager.load()

        assert values1.keys() == values2.keys()

    def test_load_clears_previous_values(self, tmp_config_file):
        """Test that load clears previously loaded values."""
        manager = ConfigManager(tmp_config_file)
        manager.load()
        manager.set("new-option", "value")

        # Reload should clear the new option
        manager.load()
        assert manager.get("new-option") is None
