"""Shelly Gen2 RPC klient — parser Switch.GetStatus a stavba RPC volání (mock httpx)."""

from __future__ import annotations

import asyncio
import json
import unittest

import httpx

from services.shelly_client import (
    ShellySwitchStatus,
    get_switch_status,
    parse_switch_status,
    set_switch,
    shelly_base_url,
)


class ParseSwitchStatusTests(unittest.TestCase):
    def test_full_gen2_payload(self) -> None:
        st = parse_switch_status(
            {
                "id": 0,
                "source": "HTTP_in",
                "output": True,
                "apower": 745.3,
                "voltage": 231.2,
                "current": 3.25,
                "aenergy": {"total": 12345.678, "by_minute": [123, 120, 118]},
                "temperature": {"tC": 41.2},
            }
        )
        self.assertEqual(
            st,
            ShellySwitchStatus(output=True, apower_w=745.3, aenergy_total_wh=12345.678),
        )

    def test_minimal_payload_without_metering(self) -> None:
        # Levnější relé bez měření: jen output.
        st = parse_switch_status({"id": 0, "output": False})
        self.assertFalse(st.output)
        self.assertIsNone(st.apower_w)
        self.assertIsNone(st.aenergy_total_wh)

    def test_missing_output_raises(self) -> None:
        # Gen1 /relay/0 odpověď ('ison') nesmí tiše projít — podporujeme jen Gen2.
        with self.assertRaises(ValueError):
            parse_switch_status({"ison": True, "has_timer": False})

    def test_zero_values_kept(self) -> None:
        st = parse_switch_status(
            {"id": 0, "output": False, "apower": 0.0, "aenergy": {"total": 0.0}}
        )
        self.assertEqual(st.apower_w, 0.0)
        self.assertEqual(st.aenergy_total_wh, 0.0)


class ShellyBaseUrlTests(unittest.TestCase):
    def test_defaults(self) -> None:
        self.assertEqual(shelly_base_url(None, "192.168.1.50", None), "http://192.168.1.50:80")

    def test_https_default_port(self) -> None:
        self.assertEqual(shelly_base_url("https", "shelly.local", None), "https://shelly.local:443")

    def test_unknown_protocol_falls_back_to_http(self) -> None:
        self.assertEqual(shelly_base_url("modbus_tcp", "1.2.3.4", 8080), "http://1.2.3.4:8080")


class ShellyRpcTests(unittest.TestCase):
    """RPC přes httpx.MockTransport — bez sítě."""

    def _client(self, handler) -> httpx.AsyncClient:
        return httpx.AsyncClient(transport=httpx.MockTransport(handler))

    def test_get_switch_status(self) -> None:
        seen: dict[str, str] = {}

        def handler(request: httpx.Request) -> httpx.Response:
            seen["path"] = request.url.path
            seen["id"] = request.url.params.get("id")
            return httpx.Response(
                200,
                json={"id": 0, "output": True, "apower": 740.0, "aenergy": {"total": 999.5}},
            )

        async def run() -> ShellySwitchStatus:
            async with self._client(handler) as client:
                return await get_switch_status("http://192.168.1.50:80", 0, client=client)

        st = asyncio.run(run())
        self.assertEqual(seen["path"], "/rpc/Switch.GetStatus")
        self.assertEqual(seen["id"], "0")
        self.assertTrue(st.output)
        self.assertEqual(st.apower_w, 740.0)
        self.assertEqual(st.aenergy_total_wh, 999.5)

    def test_set_switch_sends_json_bool_and_returns_was_on(self) -> None:
        seen: dict[str, str] = {}

        def handler(request: httpx.Request) -> httpx.Response:
            seen["path"] = request.url.path
            seen["id"] = request.url.params.get("id")
            seen["on"] = request.url.params.get("on")
            return httpx.Response(200, json={"was_on": False})

        async def run() -> bool | None:
            async with self._client(handler) as client:
                return await set_switch("http://192.168.1.50:80/", True, 0, client=client)

        was_on = asyncio.run(run())
        self.assertEqual(seen["path"], "/rpc/Switch.Set")
        self.assertEqual(seen["id"], "0")
        # Gen2 RPC parsuje query jako JSON — bool musí být doslova 'true'/'false'.
        self.assertEqual(seen["on"], "true")
        self.assertIs(was_on, False)

    def test_set_switch_off(self) -> None:
        seen: dict[str, str] = {}

        def handler(request: httpx.Request) -> httpx.Response:
            seen["on"] = request.url.params.get("on")
            return httpx.Response(200, json={"was_on": True})

        async def run() -> bool | None:
            async with self._client(handler) as client:
                return await set_switch("http://10.0.0.7", False, client=client)

        self.assertIs(asyncio.run(run()), True)
        self.assertEqual(seen["on"], "false")

    def test_http_error_raises(self) -> None:
        def handler(request: httpx.Request) -> httpx.Response:
            return httpx.Response(500, text="boom")

        async def run() -> None:
            async with self._client(handler) as client:
                await get_switch_status("http://10.0.0.7", client=client)

        with self.assertRaises(httpx.HTTPStatusError):
            asyncio.run(run())

    def test_non_gen2_body_raises_value_error(self) -> None:
        def handler(request: httpx.Request) -> httpx.Response:
            # Gen1 odpověď — klient ji odmítne (žádný Gen1 fallback).
            return httpx.Response(200, content=json.dumps({"ison": True}))

        async def run() -> None:
            async with self._client(handler) as client:
                await get_switch_status("http://10.0.0.7", client=client)

        with self.assertRaises(ValueError):
            asyncio.run(run())


if __name__ == "__main__":
    unittest.main()
