"""Tests for tacoshop.config."""

from __future__ import annotations

from pathlib import Path

import pytest

from tacoshop.config import (
    Config,
    ConfigError,
    FlagVar,
    StaticVar,
    _parse_table_entry,
    _stringify,
    merge_configs,
    parse_toml,
    validate_config,
)


class TestStringify:
    def test_int(self):
        assert _stringify(1) == "1"

    def test_float(self):
        assert _stringify(3.14) == "3.14"

    def test_str(self):
        assert _stringify("hello") == "hello"

    def test_bool_true(self):
        assert _stringify(True) == "1"

    def test_bool_false(self):
        assert _stringify(False) == "0"

    def test_unsupported_type(self):
        with pytest.raises(ConfigError, match="unsupported value type"):
            _stringify([1, 2, 3])


class TestParseTableEntry:
    def test_flag_with_value(self):
        result = _parse_table_entry(
            "TRITON", {"flag": "--triton", "value": 1}, "test.toml"
        )
        assert isinstance(result, FlagVar)
        assert result.name == "TRITON"
        assert result.flag == "--triton"
        assert result.value == "1"

    def test_flag_without_value(self):
        result = _parse_table_entry(
            "TORCH_LOGS", {"flag": "--logs"}, "test.toml"
        )
        assert isinstance(result, FlagVar)
        assert result.value is None

    def test_value_without_flag(self):
        result = _parse_table_entry(
            "MY_VAR",
            {"value": "hello", "description": "A var"},
            "test.toml",
        )
        assert isinstance(result, StaticVar)
        assert result.value == "hello"
        assert result.description == "A var"

    def test_no_flag_no_value_errors(self):
        with pytest.raises(ConfigError, match="must have at least"):
            _parse_table_entry("BAD", {"description": "oops"}, "test.toml")

    def test_flag_not_starting_with_dash(self):
        with pytest.raises(ConfigError, match="starting with '-'"):
            _parse_table_entry("X", {"flag": "nope", "value": 1}, "test.toml")

    def test_append_and_separator(self):
        result = _parse_table_entry(
            "LOGS",
            {"flag": "--logs", "append": True, "separator": ","},
            "test.toml",
        )
        assert isinstance(result, FlagVar)
        assert result.append is True
        assert result.separator == ","

    def test_append_static_var(self):
        result = _parse_table_entry(
            "PATH_EXT",
            {"value": "/extra/bin", "append": True, "separator": ":"},
            "test.toml",
        )
        assert isinstance(result, StaticVar)
        assert result.append is True
        assert result.separator == ":"

    def test_prepend_flag_var(self):
        result = _parse_table_entry(
            "PATH",
            {"flag": "--path", "prepend": True, "separator": ":"},
            "test.toml",
        )
        assert isinstance(result, FlagVar)
        assert result.prepend is True
        assert result.append is False
        assert result.separator == ":"

    def test_prepend_static_var(self):
        result = _parse_table_entry(
            "PATH",
            {"value": "/my/bin", "prepend": True, "separator": ":"},
            "test.toml",
        )
        assert isinstance(result, StaticVar)
        assert result.prepend is True

    def test_append_and_prepend_errors(self):
        with pytest.raises(ConfigError, match="cannot set both"):
            _parse_table_entry(
                "X",
                {"value": "1", "append": True, "prepend": True},
                "test.toml",
            )

    def test_bool_value_in_table(self):
        result = _parse_table_entry(
            "ENABLE", {"flag": "--enable", "value": True}, "test.toml"
        )
        assert isinstance(result, FlagVar)
        assert result.value == "1"


class TestParseToml:
    def test_static_scalar(self, tmp_toml):
        path = tmp_toml('MYVAR = 1\nSTRVAR = "hello"\n')
        config = parse_toml(path)
        assert "MYVAR" in config.static_vars
        assert config.static_vars["MYVAR"].value == "1"
        assert config.static_vars["STRVAR"].value == "hello"

    def test_flag_toggle(self, tmp_toml):
        path = tmp_toml("""\
            [TRITON_ALWAYS_COMPILE]
            flag = "--triton"
            value = 1
        """)
        config = parse_toml(path)
        assert "TRITON_ALWAYS_COMPILE" in config.flag_vars
        fv = config.flag_vars["TRITON_ALWAYS_COMPILE"]
        assert fv.flag == "--triton"
        assert fv.value == "1"
        assert "--triton" in config.flag_lookup

    def test_flag_value_required(self, tmp_toml):
        path = tmp_toml("""\
            [TORCH_LOGS]
            flag = "--logs"
        """)
        config = parse_toml(path)
        fv = config.flag_vars["TORCH_LOGS"]
        assert fv.value is None

    def test_verbose_static_var(self, tmp_toml):
        path = tmp_toml("""\
            [MY_SETTING]
            value = "on"
            description = "Enable my setting"
        """)
        config = parse_toml(path)
        assert "MY_SETTING" in config.static_vars
        sv = config.static_vars["MY_SETTING"]
        assert sv.value == "on"
        assert sv.description == "Enable my setting"

    def test_mixed_config(self, tmp_toml):
        path = tmp_toml("""\
            STATIC_VAR = 1

            [FLAG_PRESET]
            flag = "--preset"
            value = "yes"

            [FLAG_INPUT]
            flag = "--input"

            [VERBOSE_STATIC]
            value = 42
            description = "A verbose static var"
        """)
        config = parse_toml(path)
        assert len(config.static_vars) == 2  # STATIC_VAR + VERBOSE_STATIC
        assert len(config.flag_vars) == 2  # FLAG_PRESET + FLAG_INPUT

    def test_source_is_set(self, tmp_toml):
        path = tmp_toml("X = 1\n")
        config = parse_toml(path)
        assert config.static_vars["X"].source == str(path)
        assert config.loaded_files == [str(path)]


class TestMergeConfigs:
    def test_later_overrides_earlier(self, tmp_toml):
        c1 = Config(
            static_vars={"X": StaticVar(name="X", value="1", source="a")},
            flag_vars={
                "Y": FlagVar(name="Y", flag="--y", value="old", source="a")
            },
            flag_lookup={"--y": FlagVar(name="Y", flag="--y", value="old", source="a")},
            loaded_files=["a"],
        )
        c2 = Config(
            static_vars={"X": StaticVar(name="X", value="2", source="b")},
            flag_vars={
                "Y": FlagVar(name="Y", flag="--y", value="new", source="b")
            },
            flag_lookup={"--y": FlagVar(name="Y", flag="--y", value="new", source="b")},
            loaded_files=["b"],
        )
        merged = merge_configs([c1, c2])
        assert merged.static_vars["X"].value == "2"
        assert merged.flag_vars["Y"].value == "new"
        assert merged.loaded_files == ["a", "b"]

    def test_disjoint_merge(self):
        c1 = Config(
            static_vars={"A": StaticVar(name="A", value="1")},
            loaded_files=["a"],
        )
        c2 = Config(
            static_vars={"B": StaticVar(name="B", value="2")},
            loaded_files=["b"],
        )
        merged = merge_configs([c1, c2])
        assert "A" in merged.static_vars
        assert "B" in merged.static_vars


class TestValidateConfig:
    def test_flag_collision(self):
        config = Config(
            flag_vars={
                "A": FlagVar(name="A", flag="--same"),
                "B": FlagVar(name="B", flag="--same"),
            }
        )
        with pytest.raises(ConfigError, match="mapped to multiple env vars"):
            validate_config(config)

    def test_reserved_flag(self):
        config = Config(
            flag_vars={
                "X": FlagVar(name="X", flag="--help"),
            }
        )
        with pytest.raises(ConfigError, match="reserved by tacoshop"):
            validate_config(config)

    def test_valid_config_passes(self):
        config = Config(
            static_vars={"A": StaticVar(name="A", value="1")},
            flag_vars={
                "B": FlagVar(name="B", flag="--bee"),
                "C": FlagVar(name="C", flag="--see"),
            },
            flag_lookup={
                "--bee": FlagVar(name="B", flag="--bee"),
                "--see": FlagVar(name="C", flag="--see"),
            },
        )
        validate_config(config)  # should not raise

    def test_reserved_short_flag(self):
        config = Config(
            flag_vars={"X": FlagVar(name="X", flag="-v")}
        )
        with pytest.raises(ConfigError, match="reserved"):
            validate_config(config)
