"""
Unit tests for the theme module components.

These tests cover:
1. TOML frontmatter parsing (loader.py)
2. ThemeMetadata dataclass (schema.py)
3. ThemeRegistry functionality (__init__.py)
4. Invalid theme file handling
"""

import pytest

from local_deep_research.web.themes import (
    ThemeMetadata,
    ThemeRegistry,
    get_theme_metadata,
    get_themes,
    get_themes_json,
    theme_registry,
)
from local_deep_research.web.themes.loader import ThemeLoader
from local_deep_research.web.themes.schema import (
    REQUIRED_RGB_VARIANTS,
    REQUIRED_VARIABLES,
)


class TestFrontmatterParsing:
    """Test TOML frontmatter parsing from CSS files."""

    @pytest.fixture
    def loader(self, tmp_path):
        """Create a ThemeLoader with temporary directory."""
        return ThemeLoader(tmp_path)

    def test_parse_valid_frontmatter(self, loader):
        """Valid TOML frontmatter should be parsed correctly."""
        css = """/*---
name = "Test Theme"
label = "Test Theme"
icon = "fa-star"
group = "core"
type = "dark"
description = "A test theme"
---*/

[data-theme="test"] {
    --bg-primary: #121212;
}
"""
        result = loader.parse_frontmatter(css)
        assert result["name"] == "Test Theme"
        assert result["label"] == "Test Theme"
        assert result["icon"] == "fa-star"
        assert result["group"] == "core"
        assert result["type"] == "dark"
        assert result["description"] == "A test theme"

    def test_parse_missing_frontmatter(self, loader):
        """CSS without frontmatter should return empty dict."""
        css = """[data-theme="test"] {
    --bg-primary: #121212;
}
"""
        result = loader.parse_frontmatter(css)
        assert result == {}

    def test_parse_malformed_frontmatter(self, loader):
        """Malformed TOML should return empty dict without crashing."""
        css = """/*---
name = "Missing closing quote
icon = fa-star  # missing quotes
---*/

[data-theme="test"] {
    --bg-primary: #121212;
}
"""
        result = loader.parse_frontmatter(css)
        assert result == {}

    def test_parse_empty_frontmatter(self, loader):
        """Empty frontmatter should return empty dict."""
        css = """/*---
---*/

[data-theme="test"] {
    --bg-primary: #121212;
}
"""
        result = loader.parse_frontmatter(css)
        assert result == {}


class TestThemeIdExtraction:
    """Test theme ID extraction from CSS selectors."""

    @pytest.fixture
    def loader(self, tmp_path):
        """Create a ThemeLoader with temporary directory."""
        return ThemeLoader(tmp_path)

    def test_extract_theme_id(self, loader):
        """Theme ID should be extracted from data-theme selector."""
        css = """[data-theme="nord"] {
    --bg-primary: #2e3440;
}
"""
        result = loader.extract_theme_id(css)
        assert result == "nord"

    def test_extract_theme_id_with_root(self, loader):
        """Theme ID should be extracted when combined with :root."""
        css = """:root,
[data-theme="hashed"] {
    --bg-primary: #121212;
}
"""
        result = loader.extract_theme_id(css)
        assert result == "hashed"

    def test_extract_theme_id_missing(self, loader):
        """Should return None when no data-theme selector present."""
        css = """:root {
    --bg-primary: #121212;
}
"""
        result = loader.extract_theme_id(css)
        assert result is None


class TestCSSVariableValidation:
    """Test validation of required CSS variables."""

    @pytest.fixture
    def loader(self, tmp_path):
        """Create a ThemeLoader with temporary directory."""
        return ThemeLoader(tmp_path)

    def test_validate_complete_theme(self, loader):
        """Theme with all required variables should pass validation."""
        # Build CSS with all required variables
        vars_css = "\n".join(
            [f"    {var}: #123456;" for var in REQUIRED_VARIABLES]
        )
        rgb_css = "\n".join(
            [f"    {var}: 18, 52, 86;" for var in REQUIRED_RGB_VARIANTS]
        )
        css = f"""[data-theme="complete"] {{
{vars_css}
{rgb_css}
}}
"""
        missing_base, missing_rgb = loader.validate_css_variables(
            css, "complete"
        )
        assert missing_base == []
        assert missing_rgb == []

    def test_validate_missing_variables(self, loader):
        """Theme missing required variables should be flagged."""
        css = """[data-theme="incomplete"] {
    --bg-primary: #121212;
    --text-primary: #ffffff;
}
"""
        missing_base, missing_rgb = loader.validate_css_variables(
            css, "incomplete"
        )
        assert "--bg-secondary" in missing_base
        assert "--accent-primary" in missing_base
        assert "--bg-primary-rgb" in missing_rgb


class TestThemeLoading:
    """Test loading themes from files."""

    @pytest.fixture
    def themes_dir(self, tmp_path):
        """Create a temporary themes directory with test themes."""
        core_dir = tmp_path / "core"
        core_dir.mkdir()

        # Create a valid theme file
        valid_theme = core_dir / "test-valid.css"
        valid_theme.write_text("""/*---
name = "Test Valid"
label = "Test Valid"
icon = "fa-check"
group = "core"
type = "dark"
---*/

[data-theme="test-valid"] {
    --bg-primary: #121212;
    --bg-secondary: #1e1e1e;
    --bg-tertiary: #2d2d2d;
    --accent-primary: #bb86fc;
    --accent-secondary: #03dac6;
    --accent-tertiary: #cf6679;
    --text-primary: #ffffff;
    --text-secondary: #b3b3b3;
    --text-muted: #666666;
    --border-color: #333333;
    --success-color: #4caf50;
    --warning-color: #ff9800;
    --error-color: #f44336;
    --bg-primary-rgb: 18, 18, 18;
    --bg-secondary-rgb: 30, 30, 30;
    --bg-tertiary-rgb: 45, 45, 45;
    --accent-primary-rgb: 187, 134, 252;
    --accent-secondary-rgb: 3, 218, 198;
    --accent-tertiary-rgb: 207, 102, 121;
    --text-primary-rgb: 255, 255, 255;
    --text-secondary-rgb: 179, 179, 179;
    --text-muted-rgb: 102, 102, 102;
    --border-color-rgb: 51, 51, 51;
    --success-color-rgb: 76, 175, 80;
    --warning-color-rgb: 255, 152, 0;
    --error-color-rgb: 244, 67, 54;
}
""")

        return tmp_path

    def test_load_valid_theme(self, themes_dir):
        """Valid theme file should be loaded correctly."""
        loader = ThemeLoader(themes_dir)
        theme = loader.load_theme(themes_dir / "core" / "test-valid.css")

        assert theme is not None
        assert theme.id == "test-valid"
        assert theme.label == "Test Valid"
        assert theme.icon == "fa-check"
        assert theme.group == "core"
        assert theme.type == "dark"

    def test_load_nonexistent_file(self, themes_dir):
        """Loading nonexistent file should return None."""
        loader = ThemeLoader(themes_dir)
        theme = loader.load_theme(themes_dir / "nonexistent.css")

        assert theme is None

    def test_load_all_themes_includes_system(self, themes_dir):
        """load_all_themes should include virtual 'system' theme."""
        loader = ThemeLoader(themes_dir)
        themes = loader.load_all_themes()

        assert "system" in themes
        assert themes["system"].css_path is None
        assert themes["system"].group == "system"


class TestThemeRegistry:
    """Test ThemeRegistry singleton and methods."""

    def test_singleton_pattern(self):
        """ThemeRegistry should be a singleton."""
        registry1 = ThemeRegistry()
        registry2 = ThemeRegistry()
        assert registry1 is registry2

    def test_registry_loads_themes(self):
        """Registry should load themes from themes directory."""
        assert len(theme_registry.themes) > 0
        assert "system" in theme_registry.themes

    def test_get_theme_ids(self):
        """get_theme_ids should return sorted list of theme IDs."""
        ids = theme_registry.get_theme_ids()
        assert isinstance(ids, list)
        assert len(ids) > 0
        assert ids == sorted(ids)

    def test_get_theme_valid(self):
        """get_theme should return ThemeMetadata for valid ID."""
        theme = theme_registry.get_theme("system")
        assert theme is not None
        assert theme.id == "system"

    def test_get_theme_invalid(self):
        """get_theme should return None for invalid ID."""
        theme = theme_registry.get_theme("nonexistent-theme-xyz")
        assert theme is None

    def test_is_valid_theme(self):
        """is_valid_theme should correctly validate theme IDs."""
        assert theme_registry.is_valid_theme("system") is True
        assert theme_registry.is_valid_theme("nonexistent") is False

    def test_get_combined_css(self):
        """get_combined_css should return valid CSS string."""
        css = theme_registry.get_combined_css()
        assert isinstance(css, str)
        assert "Auto-generated theme CSS" in css
        assert "[data-theme=" in css

    def test_get_themes_by_group(self):
        """get_themes_by_group should organize themes correctly."""
        grouped = theme_registry.get_themes_by_group()
        assert isinstance(grouped, dict)
        assert "system" in grouped
        assert len(grouped["system"]) == 1
        assert grouped["system"][0].id == "system"


class TestThemeMetadata:
    """Test ThemeMetadata dataclass."""

    def test_to_dict(self):
        """to_dict should return serializable dictionary."""
        theme = ThemeMetadata(
            id="test",
            label="Test Theme",
            icon="fa-star",
            group="core",
            type="dark",
            description="A test theme",
        )
        result = theme.to_dict()

        assert result["label"] == "Test Theme"
        assert result["icon"] == "fa-star"
        assert result["group"] == "core"
        assert result["type"] == "dark"
        # id, description, author, css_path should NOT be in to_dict
        assert "id" not in result
        assert "description" not in result

    def test_default_values(self):
        """ThemeMetadata should have sensible defaults."""
        theme = ThemeMetadata(
            id="minimal",
            label="Minimal",
            icon="fa-circle",
            group="core",
        )
        assert theme.type == "dark"
        assert theme.description == ""
        assert theme.author == ""
        assert theme.css_path is None


class TestFlaskIntegrationFunctions:
    """Test Flask integration helper functions."""

    def test_get_themes(self):
        """get_themes should return list of theme IDs."""
        themes = get_themes()
        assert isinstance(themes, list)
        assert "system" in themes

    def test_get_themes_json(self):
        """get_themes_json should return Markup-safe JSON."""
        json_str = get_themes_json()
        # Should be a valid JSON array
        assert json_str.startswith("[")
        assert json_str.endswith("]")
        assert '"system"' in json_str

    def test_get_theme_metadata(self):
        """get_theme_metadata should return Markup-safe JSON object."""
        json_str = get_theme_metadata()
        # Should be a valid JSON object
        assert json_str.startswith("{")
        assert json_str.endswith("}")
        assert '"system"' in json_str


class TestSettingsOptions:
    """Test settings options generation."""

    def test_get_settings_options_format(self):
        """get_settings_options should return correct format."""
        options = theme_registry.get_settings_options()

        assert isinstance(options, list)
        assert len(options) > 0

        # Each option should have label and value
        for opt in options:
            assert "label" in opt
            assert "value" in opt
            assert isinstance(opt["label"], str)
            assert isinstance(opt["value"], str)

    def test_get_settings_options_sorted(self):
        """get_settings_options should be sorted by label."""
        options = theme_registry.get_settings_options()
        labels = [opt["label"] for opt in options]
        assert labels == sorted(labels)

    def test_get_grouped_settings_options(self):
        """get_grouped_settings_options should include group info."""
        options = theme_registry.get_grouped_settings_options()

        assert isinstance(options, list)
        for opt in options:
            assert "label" in opt
            assert "value" in opt
            assert "group" in opt


class TestEdgeCases:
    """Test edge cases and error handling."""

    def test_theme_with_special_characters_in_description(self, tmp_path):
        """Themes with special characters should be handled."""
        core_dir = tmp_path / "core"
        core_dir.mkdir()

        theme_file = core_dir / "special.css"
        theme_file.write_text("""/*---
name = "Special \\"Theme\\""
label = "Special Theme"
icon = "fa-star"
group = "core"
description = "Theme with \\"quotes\\" and 'apostrophes'"
---*/

[data-theme="special"] {
    --bg-primary: #121212;
}
""")
        loader = ThemeLoader(tmp_path)
        theme = loader.load_theme(theme_file)

        assert theme is not None
        assert "quotes" in theme.description

    def test_auto_generated_metadata(self, tmp_path):
        """Themes without frontmatter should get auto-generated metadata."""
        core_dir = tmp_path / "core"
        core_dir.mkdir()

        theme_file = core_dir / "auto-theme.css"
        theme_file.write_text("""[data-theme="auto-theme"] {
    --bg-primary: #121212;
}
""")
        loader = ThemeLoader(tmp_path)
        theme = loader.load_theme(theme_file)

        assert theme is not None
        assert theme.id == "auto-theme"
        assert theme.label == "Auto Theme"  # Auto-generated from filename
        assert theme.icon == "fa-palette"  # Default icon
        assert theme.group == "core"  # From parent directory

    def test_theme_type_validation(self, tmp_path):
        """Invalid theme type should default to 'dark'."""
        core_dir = tmp_path / "core"
        core_dir.mkdir()

        theme_file = core_dir / "bad-type.css"
        theme_file.write_text("""/*---
name = "Bad Type"
label = "Bad Type"
icon = "fa-star"
group = "core"
type = "invalid"
---*/

[data-theme="bad-type"] {
    --bg-primary: #121212;
}
""")
        loader = ThemeLoader(tmp_path)
        theme = loader.load_theme(theme_file)

        assert theme is not None
        assert theme.type == "dark"  # Should default to dark


class TestCSSContentGeneration:
    """Test CSS content generation and stripping."""

    def test_get_css_content_strips_frontmatter(self, tmp_path):
        """get_css_content should strip TOML frontmatter."""
        core_dir = tmp_path / "core"
        core_dir.mkdir()

        theme_file = core_dir / "strip-test.css"
        theme_file.write_text("""/*---
name = "Strip Test"
label = "Strip Test"
icon = "fa-star"
group = "core"
---*/

[data-theme="strip-test"] {
    --bg-primary: #121212;
}
""")
        loader = ThemeLoader(tmp_path)
        theme = loader.load_theme(theme_file)
        css = loader.get_css_content(theme)

        assert "/*---" not in css
        assert "---*/" not in css
        assert "name = " not in css
        assert "[data-theme=" in css
        assert "--bg-primary" in css


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
