from __future__ import annotations

import pytest
from pathlib import Path
from zshell.utils import schemepalettes as sp


@pytest.fixture
def tmp_schemes(tmp_path: Path) -> Path:
    schemes = tmp_path / "schemes"
    schemes.mkdir()

    gmedium = schemes / "gruvbox" / "medium"
    gmedium.mkdir(parents=True)
    (gmedium / "dark.txt").write_text("background 101415\nonBackground e0e3e4\nprimary 81d3e0\nsurface 1c2021\n")
    (gmedium / "light.txt").write_text("background fbf1c7\nonBackground 3c3836\nprimary 6b5f10\nsurface fbf1c7\n")

    ghard = schemes / "gruvbox" / "hard"
    ghard.mkdir(parents=True)
    (ghard / "dark.txt").write_text("background 0b0d0e\nprimary 81d3e0\n")

    cmocha = schemes / "catppuccin" / "mocha"
    cmocha.mkdir(parents=True)
    (cmocha / "dark.txt").write_text("background 1e1e2e\nprimary cba6f7\nsecondary 756294\nsurface 313244\n")
    (cmocha / "mauve-dark.txt").write_text("background 1e1e2e\nprimary cba6f7\nsecondary 756294\nsurface 313244\n")
    (cmocha / "green-dark.txt").write_text("background 1e1e2e\nprimary a6e3a1\nsecondary 5b8964\nsurface 313244\n")

    clatte = schemes / "catppuccin" / "latte"
    clatte.mkdir(parents=True)
    (clatte / "light.txt").write_text("background eff1f5\nprimary 8839ef\nsecondary c2b8d0\nsurface ccd0da\n")
    (clatte / "mauve-light.txt").write_text("background eff1f5\nprimary 8839ef\nsecondary c2b8d0\nsurface ccd0da\n")

    cextra = schemes / "extra" / "default"
    cextra.mkdir(parents=True)
    (cextra / "dark.txt").write_text(
        "# this is a comment\n\nbackground 000000\nprimary ffffff\n\n  # indented comment  \n  secondary cccccc\n"
    )

    return schemes


class TestParseTxt:
    def test_basic(self, tmp_schemes):
        path = tmp_schemes / "gruvbox" / "medium" / "dark.txt"
        colors = sp._parse_txt(path)
        assert colors["background"] == "#101415"
        assert colors["primary"] == "#81d3e0"
        assert colors["surface"] == "#1c2021"

    def test_adds_hash_prefix(self, tmp_schemes):
        path = tmp_schemes / "gruvbox" / "medium" / "dark.txt"
        colors = sp._parse_txt(path)
        for v in colors.values():
            assert v.startswith("#"), f"value {v!r} missing # prefix"

    def test_skips_comments_and_empty_lines(self, tmp_schemes):
        path = tmp_schemes / "extra" / "default" / "dark.txt"
        colors = sp._parse_txt(path)
        assert colors["background"] == "#000000"
        assert colors["primary"] == "#ffffff"
        assert colors["secondary"] == "#cccccc"
        assert len(colors) == 3


class TestDiscoverSchemes:
    def test_discovers_all_schemes(self):
        schemes = sp._discover_schemes()
        assert "gruvbox" in schemes
        assert "catppuccin" in schemes
        assert "everforest" in schemes
        assert "nord" in schemes
        assert len(schemes) >= 10

    def test_scheme_has_variants(self):
        schemes = sp._discover_schemes()
        gruvbox = schemes["gruvbox"]
        var_ids = {v.id for v in gruvbox.variants}
        assert "medium" in var_ids
        assert "hard" in var_ids
        assert "soft" in var_ids

    def test_variant_has_modes(self):
        schemes = sp._discover_schemes()
        gmedium = next(v for v in schemes["gruvbox"].variants if v.id == "medium")
        assert "dark" in gmedium.modes
        assert "light" in gmedium.modes

    def test_catppuccin_has_accents(self):
        schemes = sp._discover_schemes()
        mocha = next(v for v in schemes["catppuccin"].variants if v.id == "mocha")
        assert "mauve" in mocha.accents
        assert "green" in mocha.accents
        assert "rosewater" in mocha.accents
        assert len(mocha.accents) >= 14

    def test_non_accent_scheme_has_no_accents(self):
        schemes = sp._discover_schemes()
        gmedium = next(v for v in schemes["gruvbox"].variants if v.id == "medium")
        assert gmedium.accents == ()


class TestGetPalette:
    def test_loads_basic_palette(self):
        pal = sp.get_palette("gruvbox", "medium", "dark")
        assert pal.scheme == "gruvbox"
        assert pal.variant == "medium"
        assert pal.mode == "dark"
        assert pal.colors["background"].startswith("#")
        assert pal.colors["primary"].startswith("#")

    def test_loads_accent_palette(self):
        pal = sp.get_palette("catppuccin", "mocha", "dark", accent="mauve")
        assert pal.accent == "mauve"
        assert pal.colors["primary"] == "#cba6f7"

    def test_different_accent_changes_colors(self):
        mauve = sp.get_palette("catppuccin", "mocha", "dark", accent="mauve")
        green = sp.get_palette("catppuccin", "mocha", "dark", accent="green")
        assert mauve.colors["primary"] != green.colors["primary"]
        assert mauve.colors["secondary"] != green.colors["secondary"]

    def test_unknown_scheme_raises(self):
        with pytest.raises(KeyError, match="Unknown scheme 'nope'"):
            sp.get_palette("nope", "medium", "dark")

    def test_unknown_variant_raises(self):
        with pytest.raises(KeyError, match="Unknown variant 'bogus' for scheme 'gruvbox'"):
            sp.get_palette("gruvbox", "bogus", "dark")

    def test_unknown_accent_falls_back(self):
        pal = sp.get_palette("catppuccin", "mocha", "dark", accent="nonexistent")
        assert pal.accent == "nonexistent"
        assert pal.colors["primary"] is not None

    def test_accent_on_non_accent_scheme(self):
        pal = sp.get_palette("gruvbox", "medium", "dark", accent="mauve")
        assert pal.colors is not None

    def test_non_existent_mode_raises(self):
        with pytest.raises(FileNotFoundError):
            sp.get_palette("catppuccin", "mocha", "light")


class TestListSchemes:
    def test_returns_dict(self):
        schemes = sp.list_schemes()
        assert isinstance(schemes, dict)

    def test_includes_known_schemes(self):
        schemes = sp.list_schemes()
        assert "catppuccin" in schemes
        assert "gruvbox" in schemes


class TestResolvePreset:
    def test_two_parts(self):
        assert sp.resolve_preset("gruvbox:medium") == ("gruvbox", "medium")

    def test_three_parts(self):
        with pytest.raises(ValueError, match="Invalid preset spec"):
            sp.resolve_preset("catppuccin:mocha:mauve")

    def test_one_part(self):
        assert sp.resolve_preset("default") == ("default", "default")

    def test_edge_spaces(self):
        assert sp.resolve_preset(" catppuccin : mocha ") == (" catppuccin ", " mocha ")
