"""External smart-plug power control (Issue #103).

The printer itself has no MQTT command to power off or enter standby - only
heaters/motors/etc. can be controlled remotely. For users running the
printer through a Tasmota-style smart plug, the bridge exposes plain
HTTP GET on/off/status URLs (configured per printer) as its own dashboard
button, instead of routing through Moonraker's device_power API.
"""
import argparse
import tempfile
from unittest.mock import AsyncMock, MagicMock, patch

import pytest
import pytest_asyncio
from aiohttp.test_utils import TestClient, TestServer

from kobrax_moonraker_bridge import KobraXBridge, build_app


def _make_bridge(pid="1", **arg_overrides):
    c = MagicMock()
    c.callbacks = {}
    c.connected = False
    args = argparse.Namespace(
        printer_ip="192.168.1.50", mqtt_port=9883, username="", password="",
        mode_id="20030", device_id="", host="127.0.0.1", port=7125,
        data_dir=tempfile.mkdtemp(prefix="kxpower-"),
        power_on_url="", power_off_url="", power_status_url="",
    )
    for k, v in arg_overrides.items():
        setattr(args, k, v)
    all_bridges = {}
    bridge = KobraXBridge(c, args=args, printer_id=pid, all_bridges=all_bridges)
    all_bridges[pid] = bridge
    return bridge


@pytest_asyncio.fixture
async def power_client():
    bridge = _make_bridge(
        power_on_url="http://192.168.1.99/cm?cmnd=Power%20on",
        power_off_url="http://192.168.1.99/cm?cmnd=Power%20off",
        power_status_url="http://192.168.1.99/cm?cmnd=Power",
    )
    app = build_app(bridge)
    async with TestClient(TestServer(app)) as c:
        yield c, bridge


def _fake_get_response(status=200, text=""):
    resp = MagicMock()
    resp.status = status
    resp.text = AsyncMock(return_value=text)
    ctx = MagicMock()
    ctx.__aenter__ = AsyncMock(return_value=resp)
    ctx.__aexit__ = AsyncMock(return_value=False)
    return ctx


@pytest.mark.asyncio
async def test_printers_list_reports_has_power_control(power_client):
    c, bridge = power_client
    resp = await c.get("/kx/printers")
    data = await resp.json()
    assert data["result"][0]["has_power_control"] is True


@pytest.mark.asyncio
async def test_printers_list_no_power_control_when_unconfigured():
    bridge = _make_bridge()
    app = build_app(bridge)
    async with TestClient(TestServer(app)) as c:
        resp = await c.get("/kx/printers")
        data = await resp.json()
        assert data["result"][0]["has_power_control"] is False


@pytest.mark.asyncio
async def test_power_on_hits_configured_url(power_client):
    c, bridge = power_client
    with patch("aiohttp.ClientSession.get", return_value=_fake_get_response(200)) as mock_get:
        resp = await c.post("/kx/printers/1/power", json={"action": "on"})
        data = await resp.json()
    assert resp.status == 200
    assert data["result"] == "ok"
    mock_get.assert_called_once()
    assert mock_get.call_args[0][0] == "http://192.168.1.99/cm?cmnd=Power%20on"


@pytest.mark.asyncio
async def test_power_off_hits_configured_url(power_client):
    c, bridge = power_client
    with patch("aiohttp.ClientSession.get", return_value=_fake_get_response(200)) as mock_get:
        resp = await c.post("/kx/printers/1/power", json={"action": "off"})
        data = await resp.json()
    assert resp.status == 200
    assert data["result"] == "ok"
    assert mock_get.call_args[0][0] == "http://192.168.1.99/cm?cmnd=Power%20off"


@pytest.mark.asyncio
async def test_power_invalid_action_rejected(power_client):
    c, bridge = power_client
    resp = await c.post("/kx/printers/1/power", json={"action": "toggle"})
    assert resp.status == 400


@pytest.mark.asyncio
async def test_power_unknown_printer_id_404(power_client):
    c, bridge = power_client
    resp = await c.post("/kx/printers/99/power", json={"action": "on"})
    assert resp.status == 404


@pytest.mark.asyncio
async def test_power_missing_url_configured_error():
    bridge = _make_bridge()  # no power_on_url set
    app = build_app(bridge)
    async with TestClient(TestServer(app)) as c:
        resp = await c.post("/kx/printers/1/power", json={"action": "on"})
        assert resp.status == 400


@pytest.mark.asyncio
async def test_power_switch_unreachable_returns_502(power_client):
    c, bridge = power_client
    with patch("aiohttp.ClientSession.get", side_effect=OSError("connection refused")):
        resp = await c.post("/kx/printers/1/power", json={"action": "on"})
    assert resp.status == 502


@pytest.mark.asyncio
async def test_power_status_parses_tasmota_json(power_client):
    c, bridge = power_client
    with patch("aiohttp.ClientSession.get", return_value=_fake_get_response(200, '{"POWER":"ON"}')):
        resp = await c.get("/kx/printers/1/power-status")
        data = await resp.json()
    assert data["state"] == "on"


@pytest.mark.asyncio
async def test_power_status_parses_tasmota_json_off(power_client):
    c, bridge = power_client
    with patch("aiohttp.ClientSession.get", return_value=_fake_get_response(200, '{"POWER":"OFF"}')):
        resp = await c.get("/kx/printers/1/power-status")
        data = await resp.json()
    assert data["state"] == "off"


@pytest.mark.asyncio
async def test_power_status_falls_back_to_plain_text(power_client):
    c, bridge = power_client
    with patch("aiohttp.ClientSession.get", return_value=_fake_get_response(200, "STATE: ON")):
        resp = await c.get("/kx/printers/1/power-status")
        data = await resp.json()
    assert data["state"] == "on"


@pytest.mark.asyncio
async def test_power_status_missing_url_configured_error():
    bridge = _make_bridge()  # no power_status_url set
    app = build_app(bridge)
    async with TestClient(TestServer(app)) as c:
        resp = await c.get("/kx/printers/1/power-status")
        assert resp.status == 400


@pytest.mark.asyncio
async def test_settings_roundtrip_persists_power_urls(power_client):
    c, bridge = power_client
    resp = await c.get("/api/settings")
    data = await resp.json()
    assert data["power_on_url"] == "http://192.168.1.99/cm?cmnd=Power%20on"
    assert data["power_off_url"] == "http://192.168.1.99/cm?cmnd=Power%20off"
    assert data["power_status_url"] == "http://192.168.1.99/cm?cmnd=Power"
