"""Tests for ilum.core.safety — values diff, snapshot I/O, drift detection."""

from __future__ import annotations

from pathlib import Path
from typing import Any

from ilum.core.safety import (
    ValuesDiff,
    ValuesSnapshot,
    compute_diff,
    delete_snapshot,
    detect_drift,
    flatten_dict,
    format_diff_table,
    load_snapshot,
    make_snapshot,
    save_snapshot,
)

# ---------------------------------------------------------------------------
# flatten_dict
# ---------------------------------------------------------------------------


class TestFlattenDict:
    def test_flat_dict(self) -> None:
        assert flatten_dict({"a": 1, "b": 2}) == {"a": 1, "b": 2}

    def test_nested_dict(self) -> None:
        assert flatten_dict({"a": {"b": 1, "c": 2}}) == {"a.b": 1, "a.c": 2}

    def test_deeply_nested(self) -> None:
        result = flatten_dict({"a": {"b": {"c": 42}}})
        assert result == {"a.b.c": 42}

    def test_empty_dict(self) -> None:
        assert flatten_dict({}) == {}

    def test_mixed_values(self) -> None:
        result = flatten_dict({"a": 1, "b": {"c": True, "d": "str"}})
        assert result == {"a": 1, "b.c": True, "b.d": "str"}

    def test_empty_nested_dict(self) -> None:
        result = flatten_dict({"a": {}})
        assert result == {"a": {}}

    def test_list_value_not_flattened(self) -> None:
        result = flatten_dict({"a": [1, 2, 3]})
        assert result == {"a": [1, 2, 3]}


# ---------------------------------------------------------------------------
# compute_diff
# ---------------------------------------------------------------------------


class TestComputeDiff:
    def test_no_changes(self) -> None:
        old = {"a": 1, "b": {"c": True}}
        diff = compute_diff(old, old)
        assert diff.is_empty

    def test_added_keys(self) -> None:
        old: dict[str, Any] = {"a": 1}
        new = {"a": 1, "b": {"c": True}}
        diff = compute_diff(old, new)
        assert "b.c" in diff.added
        assert diff.added["b.c"] is True
        assert not diff.removed
        assert not diff.changed

    def test_removed_keys(self) -> None:
        old = {"a": 1, "b": {"c": True}}
        new: dict[str, Any] = {"a": 1}
        diff = compute_diff(old, new)
        assert "b.c" in diff.removed
        assert not diff.added
        assert not diff.changed

    def test_changed_values(self) -> None:
        old = {"a": {"enabled": False}}
        new = {"a": {"enabled": True}}
        diff = compute_diff(old, new)
        assert "a.enabled" in diff.changed
        assert diff.changed["a.enabled"] == (False, True)

    def test_mixed_changes(self) -> None:
        old = {"a": 1, "b": 2, "c": 3}
        new = {"a": 1, "b": 99, "d": 4}
        diff = compute_diff(old, new)
        assert diff.added == {"d": 4}
        assert diff.removed == {"c": 3}
        assert diff.changed == {"b": (2, 99)}

    def test_is_empty_property(self) -> None:
        diff = ValuesDiff()
        assert diff.is_empty is True

        diff_with_added = ValuesDiff(added={"x": 1})
        assert diff_with_added.is_empty is False


# ---------------------------------------------------------------------------
# Snapshot I/O
# ---------------------------------------------------------------------------


class TestSnapshotIO:
    def test_save_and_load_roundtrip(self, tmp_path: Path) -> None:
        snapshot = ValuesSnapshot(
            release="ilum",
            namespace="default",
            timestamp="2024-01-15T12:00:00+00:00",
            chart_version="6.7.0",
            values={"ilum-core": {"enabled": True}},
            operation="install",
        )
        save_snapshot(snapshot, tmp_path)
        loaded = load_snapshot("ilum", "default", tmp_path)

        assert loaded is not None
        assert loaded.release == "ilum"
        assert loaded.namespace == "default"
        assert loaded.chart_version == "6.7.0"
        assert loaded.values == {"ilum-core": {"enabled": True}}
        assert loaded.operation == "install"

    def test_load_nonexistent(self, tmp_path: Path) -> None:
        result = load_snapshot("nonexistent", "default", tmp_path)
        assert result is None

    def test_delete_snapshot(self, tmp_path: Path) -> None:
        snapshot = ValuesSnapshot(
            release="ilum",
            namespace="default",
            timestamp="2024-01-15T12:00:00+00:00",
            chart_version="6.7.0",
            values={},
            operation="install",
        )
        save_snapshot(snapshot, tmp_path)
        assert load_snapshot("ilum", "default", tmp_path) is not None

        delete_snapshot("ilum", "default", tmp_path)
        assert load_snapshot("ilum", "default", tmp_path) is None

    def test_delete_nonexistent_is_safe(self, tmp_path: Path) -> None:
        delete_snapshot("nope", "default", tmp_path)

    def test_snapshot_path_includes_namespace(self, tmp_path: Path) -> None:
        snap1 = ValuesSnapshot(
            release="ilum",
            namespace="ns1",
            timestamp="",
            chart_version="",
            values={"x": 1},
            operation="install",
        )
        snap2 = ValuesSnapshot(
            release="ilum",
            namespace="ns2",
            timestamp="",
            chart_version="",
            values={"x": 2},
            operation="install",
        )
        save_snapshot(snap1, tmp_path)
        save_snapshot(snap2, tmp_path)

        loaded1 = load_snapshot("ilum", "ns1", tmp_path)
        loaded2 = load_snapshot("ilum", "ns2", tmp_path)
        assert loaded1 is not None and loaded1.values == {"x": 1}
        assert loaded2 is not None and loaded2.values == {"x": 2}


# ---------------------------------------------------------------------------
# Drift detection
# ---------------------------------------------------------------------------


class TestDriftDetection:
    def test_no_snapshot_means_no_drift(self, tmp_path: Path) -> None:
        result = detect_drift("ilum", "default", {"a": 1}, tmp_path)
        assert result.has_drift is False
        assert result.snapshot_exists is False
        assert result.diff is None

    def test_no_drift_when_values_match(self, tmp_path: Path) -> None:
        values = {"ilum-core": {"enabled": True}}
        snap = ValuesSnapshot(
            release="ilum",
            namespace="default",
            timestamp="",
            chart_version="6.7.0",
            values=values,
            operation="install",
        )
        save_snapshot(snap, tmp_path)

        result = detect_drift("ilum", "default", values, tmp_path)
        assert result.has_drift is False
        assert result.snapshot_exists is True
        assert result.diff is not None
        assert result.diff.is_empty

    def test_drift_detected(self, tmp_path: Path) -> None:
        original = {"ilum-core": {"enabled": True}}
        snap = ValuesSnapshot(
            release="ilum",
            namespace="default",
            timestamp="",
            chart_version="6.7.0",
            values=original,
            operation="install",
        )
        save_snapshot(snap, tmp_path)

        live = {"ilum-core": {"enabled": True}, "custom": {"value": "foo"}}
        result = detect_drift("ilum", "default", live, tmp_path)
        assert result.has_drift is True
        assert result.diff is not None
        assert "custom.value" in result.diff.added

    def test_live_values_passed_through(self, tmp_path: Path) -> None:
        live = {"a": 1}
        result = detect_drift("ilum", "default", live, tmp_path)
        assert result.live_values is live


# ---------------------------------------------------------------------------
# make_snapshot
# ---------------------------------------------------------------------------


class TestMakeSnapshot:
    def test_creates_snapshot_with_timestamp(self) -> None:
        snap = make_snapshot("ilum", "default", "6.7.0", {"a": 1}, "install")
        assert snap.release == "ilum"
        assert snap.namespace == "default"
        assert snap.chart_version == "6.7.0"
        assert snap.values == {"a": 1}
        assert snap.operation == "install"
        assert snap.timestamp  # non-empty

    def test_values_are_deep_copied(self) -> None:
        original: dict[str, Any] = {"a": {"b": 1}}
        snap = make_snapshot("ilum", "default", "6.7.0", original, "install")
        original["a"]["b"] = 999
        assert snap.values["a"]["b"] == 1


# ---------------------------------------------------------------------------
# format_diff_table
# ---------------------------------------------------------------------------


class TestFormatDiffTable:
    def test_empty_diff(self) -> None:
        diff = ValuesDiff()
        assert format_diff_table(diff) == []

    def test_all_change_types(self) -> None:
        diff = ValuesDiff(
            added={"new.key": True},
            changed={"mod.key": (False, True)},
            removed={"old.key": "gone"},
        )
        rows = format_diff_table(diff)
        assert len(rows) == 3
        # Rows sorted within categories: added, changed, removed
        assert rows[0][0] == "+ added"
        assert rows[1][0] == "~ changed"
        assert rows[2][0] == "- removed"
