"""Tests for the values file merger."""

from __future__ import annotations

from pathlib import Path
from typing import Any

import pytest

from ilum.core.values import (
    apply_set_flags,
    deep_merge,
    load_values,
    merge_values_files,
    save_values,
)
from ilum.errors import ValuesError


class TestDeepMerge:
    def test_simple_dict_merge(self) -> None:
        base = {"a": 1, "b": 2}
        overlay = {"b": 3, "c": 4}
        result = deep_merge(base, overlay)
        assert result == {"a": 1, "b": 3, "c": 4}

    def test_nested_dict_merge(self) -> None:
        base = {"a": {"x": 1, "y": 2}, "b": 3}
        overlay = {"a": {"y": 99, "z": 100}}
        result = deep_merge(base, overlay)
        assert result == {"a": {"x": 1, "y": 99, "z": 100}, "b": 3}

    def test_lists_replaced_not_appended(self) -> None:
        base = {"items": [1, 2, 3]}
        overlay = {"items": [4, 5]}
        result = deep_merge(base, overlay)
        assert result == {"items": [4, 5]}

    def test_overlay_adds_new_keys(self) -> None:
        base = {"a": 1}
        overlay = {"b": {"nested": True}}
        result = deep_merge(base, overlay)
        assert result == {"a": 1, "b": {"nested": True}}

    def test_empty_overlay(self) -> None:
        base = {"a": 1}
        result = deep_merge(base, {})
        assert result == {"a": 1}

    def test_empty_base(self) -> None:
        overlay = {"a": 1}
        result = deep_merge({}, overlay)
        assert result == {"a": 1}

    def test_does_not_mutate_base(self) -> None:
        base: dict[str, Any] = {"a": {"x": 1}}
        overlay: dict[str, Any] = {"a": {"x": 2}}
        deep_merge(base, overlay)
        assert base["a"]["x"] == 1


class TestLoadSaveValues:
    def test_load_values(self, tmp_path: Path) -> None:
        f = tmp_path / "test.yaml"
        f.write_text("key: value\nnested:\n  inner: 42\n")
        data = load_values(f)
        assert data["key"] == "value"
        assert data["nested"]["inner"] == 42

    def test_load_nonexistent_raises(self, tmp_path: Path) -> None:
        with pytest.raises(ValuesError):
            load_values(tmp_path / "nope.yaml")

    def test_save_and_load_roundtrip(self, tmp_path: Path) -> None:
        data: dict[str, Any] = {"ilum-core": {"enabled": True, "sql": {"enabled": False}}}
        f = tmp_path / "out.yaml"
        save_values(data, f)
        loaded = load_values(f)
        assert loaded["ilum-core"]["enabled"] is True
        assert loaded["ilum-core"]["sql"]["enabled"] is False

    def test_save_creates_parent_dirs(self, tmp_path: Path) -> None:
        f = tmp_path / "sub" / "dir" / "out.yaml"
        save_values({"a": 1}, f)
        assert f.exists()


class TestMergeValuesFiles:
    def test_merge_two_files(self, tmp_path: Path) -> None:
        base = tmp_path / "base.yaml"
        base.write_text("a: 1\nb: 2\n")
        overlay = tmp_path / "overlay.yaml"
        overlay.write_text("b: 99\nc: 3\n")
        result = merge_values_files(base, overlay)
        assert result == {"a": 1, "b": 99, "c": 3}

    def test_merge_order_matters(self, tmp_path: Path) -> None:
        base = tmp_path / "base.yaml"
        base.write_text("val: 1\n")
        o1 = tmp_path / "o1.yaml"
        o1.write_text("val: 2\n")
        o2 = tmp_path / "o2.yaml"
        o2.write_text("val: 3\n")
        result = merge_values_files(base, o1, o2)
        assert result["val"] == 3


class TestApplySetFlags:
    def test_simple_boolean(self) -> None:
        data: dict[str, Any] = {}
        result = apply_set_flags(data, ["kafka.enabled=true"])
        assert result["kafka"]["enabled"] is True

    def test_boolean_false(self) -> None:
        data: dict[str, Any] = {"kafka": {"enabled": True}}
        result = apply_set_flags(data, ["kafka.enabled=false"])
        assert result["kafka"]["enabled"] is False

    def test_nested_creation(self) -> None:
        data: dict[str, Any] = {}
        result = apply_set_flags(data, ["ilum-core.sql.enabled=true"])
        assert result["ilum-core"]["sql"]["enabled"] is True

    def test_hyphenated_keys_preserved(self) -> None:
        data: dict[str, Any] = {}
        result = apply_set_flags(data, ["ilum-core.communication.type=kafka"])
        assert result["ilum-core"]["communication"]["type"] == "kafka"

    def test_numeric_coercion(self) -> None:
        data: dict[str, Any] = {}
        result = apply_set_flags(data, ["replicaCount=3"])
        assert result["replicaCount"] == 3

    def test_float_coercion(self) -> None:
        data: dict[str, Any] = {}
        result = apply_set_flags(data, ["ratio=0.5"])
        assert result["ratio"] == 0.5

    def test_string_value(self) -> None:
        data: dict[str, Any] = {}
        result = apply_set_flags(data, ["image.tag=latest"])
        assert result["image"]["tag"] == "latest"

    def test_malformed_flag_raises(self) -> None:
        with pytest.raises(ValuesError):
            apply_set_flags({}, ["no-equals-sign"])

    def test_multiple_flags(self) -> None:
        data: dict[str, Any] = {}
        result = apply_set_flags(
            data,
            [
                "ilum-sql.enabled=true",
                "ilum-core.sql.enabled=true",
                "postgresql.enabled=true",
            ],
        )
        assert result["ilum-sql"]["enabled"] is True
        assert result["ilum-core"]["sql"]["enabled"] is True
        assert result["postgresql"]["enabled"] is True

    def test_does_not_mutate_input(self) -> None:
        data: dict[str, Any] = {"a": {"b": 1}}
        apply_set_flags(data, ["a.b=2"])
        assert data["a"]["b"] == 1
