"""Tests for the Style system and theme loading."""

import pytest
from pathlib import Path
from neoplot.style import Style, ThemeLoader, load_theme
import neoplot  # triggers font registration


class TestStyle:
    """Test Style class."""
    
    def test_style_creation(self):
        """Test creating a Style object."""
        style = Style(name="test", dpi=300)
        assert style.name == "test"
        assert style.dpi == 300
        assert style.colors is not None
    
    def test_style_default_fonts(self):
        """Default body font is Montserrat, title font is Karma."""
        style = Style()
        assert style.font_family == "Montserrat"
        assert style.title_font_family == "Karma"

    def test_style_default_relative_text_sizes(self):
        """Axis labels are larger than ticks by default."""
        style = Style(font_size_base=10.0)
        assert style.axis_label_size == 11.0
        assert style.tick_label_size == 9.0

    def test_style_default_zero_line_enabled(self):
        """y=0 reference line is enabled by default."""
        style = Style()
        assert style.zero_line_visible is True
        assert style.zero_line_color == "black"
        assert style.zero_line_linestyle == "--"
        assert style.zero_line_alpha == 0.5

    def test_style_default_axis_baselines_enabled(self):
        """Custom left/bottom baselines are enabled by default."""
        style = Style()
        assert style.axis_baselines_visible is True
        assert style.axis_baselines_color == "black"
        assert style.axis_baselines_linestyle == "--"
        assert style.axis_baselines_alpha == 0.5

    def test_style_default_colors(self):
        """Test that default colors are present."""
        style = Style()
        assert "primary" in style.colors
        assert "secondary" in style.colors
        assert "background" in style.colors
    
    def test_style_chain_with_color(self):
        """Test chaining color overrides."""
        style = Style()
        result = style.with_color("primary", "#FF0000")
        assert result is style  # Returns self
        assert style.colors["primary"] == "#FF0000"
    
    def test_style_chain_with_font(self):
        """Test chaining font overrides."""
        style = Style()
        result = style.with_font("serif", 12.0)
        assert result is style
        assert style.font_family == "serif"
        assert style.font_size_base == 12.0
    
    def test_style_init_title_font_param(self):
        """title_font_family param sets the title font."""
        style = Style(title_font_family="Arial")
        assert style.title_font_family == "Arial"
        # Body font unaffected
        assert style.font_family == "Montserrat"

    def test_style_init_title_font_alias(self):
        """title_font= short alias sets the title font."""
        style = Style(title_font="Georgia")
        assert style.title_font_family == "Georgia"

    def test_style_chain_with_title_font(self):
        """with_title_font() chaining method works and returns self."""
        style = Style()
        result = style.with_title_font("serif", 14.0)
        assert result is style
        assert style.title_font_family == "serif"
        assert style.font_size_base == 14.0

    def test_style_init_color_overrides_merge_defaults(self):
        """color_overrides merges onto the default palette."""
        style = Style(color_overrides={"primary": "#FF0000"})
        assert style.colors["primary"] == "#FF0000"
        # Other defaults must still be present
        assert "secondary" in style.colors
        assert "background" in style.colors

    def test_style_init_full_colors_replaces_defaults(self):
        """Passing colors= replaces the default palette entirely."""
        custom = {"primary": "#AABBCC", "background": "#000000"}
        style = Style(colors=custom)
        assert style.colors["primary"] == "#AABBCC"
        # Keys not in custom should not exist (no merge with defaults)
        assert "secondary" not in style.colors

    def test_style_init_font_aliases(self):
        """font= and font_size= aliases work in constructor."""
        style = Style(font="serif", font_size=14.0)
        assert style.font_family == "serif"
        assert style.font_size_base == 14.0

    def test_style_init_long_form_font_params_still_work(self):
        """Existing font_family / font_size_base params are unchanged."""
        style = Style(font_family="monospace", font_size_base=9.0)
        assert style.font_family == "monospace"
        assert style.font_size_base == 9.0

    def test_style_init_axis_and_tick_sizes(self):
        """Constructor accepts explicit label/tick sizes."""
        style = Style(axis_label_size=13.0, tick_label_size=8.0)
        assert style.axis_label_size == 13.0
        assert style.tick_label_size == 8.0

    def test_style_init_zero_line_options(self):
        """Constructor accepts y=0 reference line options."""
        style = Style(
            zero_line_visible=False,
            zero_line_color="#333333",
            zero_line_linestyle=":",
            zero_line_linewidth=2.0,
            zero_line_alpha=0.2,
        )
        assert style.zero_line_visible is False
        assert style.zero_line_color == "#333333"
        assert style.zero_line_linestyle == ":"
        assert style.zero_line_linewidth == 2.0
        assert style.zero_line_alpha == 0.2

    def test_style_init_axis_baseline_options(self):
        """Constructor accepts axis baseline options."""
        style = Style(
            axis_baselines_visible=False,
            axis_baselines_color="#111111",
            axis_baselines_linestyle="-.",
            axis_baselines_linewidth=2.0,
            axis_baselines_alpha=0.3,
        )
        assert style.axis_baselines_visible is False
        assert style.axis_baselines_color == "#111111"
        assert style.axis_baselines_linestyle == "-."
        assert style.axis_baselines_linewidth == 2.0
        assert style.axis_baselines_alpha == 0.3

    def test_style_chain_label_and_tick_sizes(self):
        """Chaining methods override axis/tick sizes."""
        style = Style().with_axis_label_size(14.0).with_tick_label_size(7.0)
        assert style.axis_label_size == 14.0
        assert style.tick_label_size == 7.0

    def test_style_chain_with_zero_line(self):
        """with_zero_line() updates y=0 line config."""
        style = Style().with_zero_line(False, color="red", linestyle="-.", linewidth=3.0, alpha=0.2)
        assert style.zero_line_visible is False
        assert style.zero_line_color == "red"
        assert style.zero_line_linestyle == "-."
        assert style.zero_line_linewidth == 3.0
        assert style.zero_line_alpha == 0.2

    def test_style_chain_with_axis_baselines(self):
        """with_axis_baselines() updates left/bottom frame line config."""
        style = Style().with_axis_baselines(False, color="blue", linestyle=":", linewidth=2.5, alpha=0.25)
        assert style.axis_baselines_visible is False
        assert style.axis_baselines_color == "blue"
        assert style.axis_baselines_linestyle == ":"
        assert style.axis_baselines_linewidth == 2.5
        assert style.axis_baselines_alpha == 0.25

    def test_style_chain_with_dpi(self):
        """Test chaining DPI override."""
        style = Style(dpi=96)
        result = style.with_dpi(300)
        assert result is style
        assert style.dpi == 300
    
    def test_style_to_matplotlib_rc(self):
        """Test converting Style to matplotlib rcParams."""
        style = Style(
            font_family="sans-serif",
            font_size_base=10,
            axis_label_size=12,
            tick_label_size=8,
            dpi=300
        )
        rc = style.to_matplotlib_rc()
        assert rc["font.family"] == "sans-serif"
        assert rc["font.size"] == 10
        assert rc["axes.labelsize"] == 12
        assert rc["xtick.labelsize"] == 8
        assert rc["ytick.labelsize"] == 8
        assert "grid.linewidth" in rc
        assert rc["savefig.dpi"] == 300
    
    def test_style_to_dict_includes_title_font(self):
        """to_dict serialises title_font_family."""
        style = Style(title_font_family="Karma")
        d = style.to_dict()
        assert d["title_font_family"] == "Karma"
        assert "axis_label_size" in d
        assert "tick_label_size" in d
        assert "zero_line_visible" in d
        assert "zero_line_color" in d
        assert "zero_line_alpha" in d
        assert "axis_baselines_visible" in d
        assert "axis_baselines_alpha" in d
        assert "grid_linewidth" in d

    def test_style_to_dict_and_back(self):
        """Test serialization and deserialization."""
        style1 = Style(name="test", dpi=300, font_family="serif", title_font_family="Karma")
        data = style1.to_dict()

        style2 = Style.from_dict(data)
        assert style2.name == style1.name
        assert style2.dpi == style1.dpi
        assert style2.font_family == style1.font_family
        assert style2.title_font_family == style1.title_font_family


class TestThemeLoader:
    """Test ThemeLoader."""
    
    def test_theme_loader_list_themes(self):
        """Test listing available themes."""
        loader = ThemeLoader()
        themes = loader.list_themes()
        
        # Should have publication and presentation packs
        assert "publication" in themes or "presentation" in themes
    
    def test_theme_loader_load(self):
        """Test loading a theme."""
        loader = ThemeLoader()
        try:
            style = loader.load("publication/paper")
            assert style.name == "publication/paper"
            assert style.dpi == 300
            assert style.context == "publication"
        except FileNotFoundError:
            pytest.skip("Theme file not found (expected in packaged version)")
    
    def test_theme_loader_cache(self):
        """Test theme caching."""
        loader = ThemeLoader()
        try:
            style1 = loader.load("publication/paper")
            style2 = loader.load("publication/paper")
            # Should be different objects (deepcopy)
            assert style1 is not style2
            assert style1.name == style2.name
        except FileNotFoundError:
            pytest.skip("Theme file not found")
    
    def test_theme_loader_nonexistent(self):
        """Test loading nonexistent theme raises error."""
        loader = ThemeLoader()
        with pytest.raises(FileNotFoundError):
            loader.load("nonexistent/theme")


class TestGlobalThemeLoader:
    """Test the global theme loading function."""

    def test_load_theme_function(self):
        """Test the global load_theme function."""
        try:
            style = load_theme("publication/paper")
            assert style is not None
            assert style.dpi == 300
        except FileNotFoundError:
            pytest.skip("Theme file not found")


class TestFontRegistration:
    """Test bundled font registration via neoplot.fonts."""

    def test_montserrat_registered(self):
        """Montserrat should be available after package import."""
        from neoplot.fonts import is_available
        assert is_available("Montserrat"), (
            "Montserrat not found in matplotlib font list — "
            "check that the TTF files are present in neoplot/fonts/"
        )

    def test_karma_registered(self):
        """Karma should be available after package import."""
        from neoplot.fonts import is_available
        assert is_available("Karma"), (
            "Karma not found in matplotlib font list — "
            "check that the TTF files are present in neoplot/fonts/"
        )

    def test_available_families_returns_list(self):
        """available_families() returns a non-empty list of strings."""
        from neoplot.fonts import available_families
        families = available_families()
        assert isinstance(families, list)
        assert len(families) > 0
