"""Tests for the type-casting module (cast.py)."""

from __future__ import annotations

from datetime import timedelta

import pytest

from routeros.cast import cast, cast_optional, typed_map
from routeros.sentence import Sentence

# ---------------------------------------------------------------------------
# cast()
# ---------------------------------------------------------------------------


class TestCastStr:
    def test_identity(self):
        assert cast("hello", str) == "hello"

    def test_empty(self):
        assert cast("", str) == ""


class TestCastInt:
    def test_positive(self):
        assert cast("42", int) == 42

    def test_zero(self):
        assert cast("0", int) == 0

    def test_negative(self):
        assert cast("-5", int) == -5

    def test_invalid_raises(self):
        with pytest.raises(ValueError):
            cast("abc", int)


class TestCastFloat:
    def test_basic(self):
        assert cast("3.14", float) == pytest.approx(3.14)

    def test_integer_string(self):
        assert cast("10", float) == 10.0

    def test_invalid_raises(self):
        with pytest.raises(ValueError):
            cast("x", float)


class TestCastBool:
    @pytest.mark.parametrize("v", ["true", "yes", "1"])
    def test_truthy(self, v):
        assert cast(v, bool) is True

    @pytest.mark.parametrize("v", ["false", "no", "0", ""])
    def test_falsy(self, v):
        assert cast(v, bool) is False

    def test_case_insensitive(self):
        assert cast("True", bool) is True
        assert cast("FALSE", bool) is False

    def test_invalid_raises(self):
        with pytest.raises(ValueError):
            cast("maybe", bool)


class TestCastTimedelta:
    def test_seconds_only(self):
        assert cast("30s", timedelta) == timedelta(seconds=30)

    def test_minutes_seconds(self):
        assert cast("5m30s", timedelta) == timedelta(minutes=5, seconds=30)

    def test_hours(self):
        assert cast("2h", timedelta) == timedelta(hours=2)

    def test_days(self):
        assert cast("3d", timedelta) == timedelta(days=3)

    def test_weeks(self):
        assert cast("1w", timedelta) == timedelta(weeks=1)

    def test_full_format(self):
        assert cast("1w2d3h4m5s", timedelta) == timedelta(
            weeks=1, days=2, hours=3, minutes=4, seconds=5
        )

    def test_plain_integer_is_seconds(self):
        assert cast("60", timedelta) == timedelta(seconds=60)

    def test_empty_is_zero(self):
        assert cast("", timedelta) == timedelta(0)

    def test_invalid_raises(self):
        with pytest.raises(ValueError):
            cast("abc", timedelta)


class TestCastList:
    def test_csv(self):
        assert cast("a,b,c", list) == ["a", "b", "c"]

    def test_single(self):
        assert cast("only", list) == ["only"]

    def test_empty_string(self):
        assert cast("", list) == []

    def test_spaces_stripped(self):
        assert cast("a, b, c", list) == ["a", "b", "c"]


class TestCastBytes:
    def test_mac_with_colons(self):
        result = cast("AA:BB:CC:DD:EE:FF", bytes)
        assert result == bytes.fromhex("AABBCCDDEEFF")

    def test_mac_with_dashes(self):
        result = cast("AA-BB-CC-DD-EE-FF", bytes)
        assert result == bytes.fromhex("AABBCCDDEEFF")

    def test_plain_hex(self):
        assert cast("deadbeef", bytes) == bytes.fromhex("deadbeef")


class TestCastUnsupportedType:
    def test_raises_for_unknown_type(self):
        with pytest.raises(ValueError, match="unsupported cast target type"):
            cast("x", dict)


# ---------------------------------------------------------------------------
# cast_optional()
# ---------------------------------------------------------------------------


class TestCastOptional:
    def test_empty_returns_none(self):
        assert cast_optional("", int) is None

    def test_non_empty_casts(self):
        assert cast_optional("42", int) == 42

    def test_bool_none(self):
        assert cast_optional("", bool) is None

    def test_bool_value(self):
        assert cast_optional("true", bool) is True


# ---------------------------------------------------------------------------
# typed_map()
# ---------------------------------------------------------------------------


def _sentence(*kv_pairs) -> Sentence:
    words = ["!re"] + [f"={k}={v}" for k, v in kv_pairs]
    return Sentence.from_words(words)


class TestTypedMap:
    def test_basic_schema(self):
        sen = _sentence(("address", "192.168.1.1/24"), ("mtu", "1500"), ("running", "true"))
        result = typed_map(sen, {"address": str, "mtu": int, "running": bool})
        assert result == {"address": "192.168.1.1/24", "mtu": 1500, "running": True}

    def test_missing_key_omitted(self):
        sen = _sentence(("address", "10.0.0.1/8"))
        result = typed_map(sen, {"address": str, "mtu": int})
        assert "mtu" not in result

    def test_empty_schema(self):
        sen = _sentence(("a", "1"))
        assert typed_map(sen, {}) == {}

    def test_timedelta_in_schema(self):
        sen = _sentence(("uptime", "2d3h"))
        result = typed_map(sen, {"uptime": timedelta})
        assert result["uptime"] == timedelta(days=2, hours=3)
