"""Tests for routers/tailscale.py — the proxy in front of the host-agent's
Tailscale status endpoint.

Mocked surfaces:
  * urllib.request.urlopen — stand-in for the host-agent HTTP call.

The actual container/native `tailscale status --json` behavior lives at the
host-agent layer and is not reproducible here.
"""

import json
from unittest.mock import patch, MagicMock

import urllib.error


# ---------------------------------------------------------------------------
# Auth enforcement
# ---------------------------------------------------------------------------


def test_tailscale_status_requires_auth(test_client):
    resp = test_client.get("/api/tailscale/status")
    assert resp.status_code == 401


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _mock_agent_response(body, status=200):
    mock_resp = MagicMock()
    mock_resp.status = status
    mock_resp.read = MagicMock(return_value=json.dumps(body).encode("utf-8"))
    mock_resp.__enter__ = MagicMock(return_value=mock_resp)
    mock_resp.__exit__ = MagicMock(return_value=False)
    return mock_resp


def _mock_agent_http_error(status, body):
    err = urllib.error.HTTPError(
        url="http://agent/v1/tailscale/status",
        code=status,
        msg="error",
        hdrs=None,
        fp=None,
    )
    err.read = lambda: json.dumps(body).encode("utf-8")
    return err


# ---------------------------------------------------------------------------
# Three normal status shapes (all 200)
# ---------------------------------------------------------------------------


def test_status_when_extension_not_running(test_client):
    """Container not started → running=false. Not an error — the user just
    hasn't enabled the extension yet."""
    upstream = {"running": False}
    with patch("routers.tailscale.urllib.request.urlopen", return_value=_mock_agent_response(upstream)):
        resp = test_client.get("/api/tailscale/status", headers=test_client.auth_headers)
    assert resp.status_code == 200
    body = resp.json()
    assert body["running"] is False


def test_status_when_running_but_not_authenticated(test_client):
    """Container up but TS_AUTHKEY is empty / rejected → user-facing reason."""
    upstream = {
        "running": True,
        "authenticated": False,
        "reason": "Tailscale is running but not yet authenticated. Set TS_AUTHKEY and restart.",
    }
    with patch("routers.tailscale.urllib.request.urlopen", return_value=_mock_agent_response(upstream)):
        resp = test_client.get("/api/tailscale/status", headers=test_client.auth_headers)
    assert resp.status_code == 200
    body = resp.json()
    assert body["running"] is True
    assert body["authenticated"] is False
    assert "TS_AUTHKEY" in body["reason"]


def test_status_when_fully_joined(test_client):
    """Happy path — device is on the tailnet."""
    upstream = {
        "running": True,
        "authenticated": True,
        "backend_state": "Running",
        "self": {
            "hostname": "ods",
            "dns_name": "ods.tail-abcde.ts.net",
            "ips": ["100.64.0.42", "fd7a:115c:a1e0::42"],
            "online": True,
        },
        "magic_dns_suffix": "tail-abcde.ts.net",
        "tailnet_name": "example.com",
    }
    with patch("routers.tailscale.urllib.request.urlopen", return_value=_mock_agent_response(upstream)):
        resp = test_client.get("/api/tailscale/status", headers=test_client.auth_headers)
    assert resp.status_code == 200
    body = resp.json()
    assert body["authenticated"] is True
    assert body["self"]["dns_name"].endswith(".ts.net")
    assert "100.64.0.42" in body["self"]["ips"]
    assert body["magic_dns_suffix"] == "tail-abcde.ts.net"


# ---------------------------------------------------------------------------
# Error translation
# ---------------------------------------------------------------------------


def test_status_returns_503_when_agent_unreachable(test_client):
    with patch(
        "routers.tailscale.urllib.request.urlopen",
        side_effect=urllib.error.URLError("connection refused"),
    ):
        resp = test_client.get("/api/tailscale/status", headers=test_client.auth_headers)
    assert resp.status_code == 503


def test_status_returns_504_when_agent_request_times_out(test_client):
    with patch(
        "routers.tailscale.urllib.request.urlopen",
        side_effect=TimeoutError("timed out"),
    ):
        resp = test_client.get("/api/tailscale/status", headers=test_client.auth_headers)
    assert resp.status_code == 504


def test_status_passes_through_504_timeout(test_client):
    err = _mock_agent_http_error(504, {"error": "docker exec timed out"})
    with patch("routers.tailscale.urllib.request.urlopen", side_effect=err):
        resp = test_client.get("/api/tailscale/status", headers=test_client.auth_headers)
    assert resp.status_code == 504
    assert "timed out" in resp.json()["detail"]


def test_status_passes_through_500_when_agent_errors(test_client):
    err = _mock_agent_http_error(500, {"error": "docker exec failed: ENOENT"})
    with patch("routers.tailscale.urllib.request.urlopen", side_effect=err):
        resp = test_client.get("/api/tailscale/status", headers=test_client.auth_headers)
    assert resp.status_code == 500
