from __future__ import annotations

from urllib.parse import parse_qs

import httpx
import pytest

from pve_vm_setup.errors import ProxmoxConnectError
from pve_vm_setup.models.workflow import VmConfig
from pve_vm_setup.services.proxmox import ProxmoxApiClient
from pve_vm_setup.settings import AppSettings


def build_settings() -> AppSettings:
    return AppSettings.from_env(
        {
            "PROXMOX_URL": "https://proxmox.example.invalid:8006",
            "PROXMOX_USER": "root",
            "PROXMOX_PASSWORD": "secret",
            "PROXMOX_REALM": "pam",
        },
        load_dotenv_file=False,
    )


def test_client_uses_api_base_when_loading_realms() -> None:
    recorded_urls: list[str] = []

    def handler(request: httpx.Request) -> httpx.Response:
        recorded_urls.append(str(request.url))
        return httpx.Response(200, json={"data": [{"realm": "pam", "comment": "Linux PAM"}]})

    client = ProxmoxApiClient(build_settings(), transport=httpx.MockTransport(handler))
    try:
        realms = client.load_realms()
    finally:
        client.close()

    assert realms[0].name == "pam"
    assert recorded_urls == ["https://proxmox.example.invalid:8006/api2/json/access/domains"]


def test_client_maps_connect_errors() -> None:
    def handler(request: httpx.Request) -> httpx.Response:
        raise httpx.ConnectError("boom", request=request)

    client = ProxmoxApiClient(build_settings(), transport=httpx.MockTransport(handler))
    try:
        with pytest.raises(ProxmoxConnectError):
            client.load_realms()
    finally:
        client.close()


def test_client_attaches_serial_device_without_switching_display_to_serial() -> None:
    requests: list[tuple[str, str, bytes]] = []

    def handler(request: httpx.Request) -> httpx.Response:
        requests.append((request.method, request.url.path, request.content))
        path = request.url.path
        if path.endswith("/nodes/fake-node-01/qemu") and request.method == "POST":
            return httpx.Response(200, json={"data": "UPID:create"})
        if path.endswith("/nodes/fake-node-01/tasks/UPID:create/status"):
            return httpx.Response(200, json={"data": {"status": "stopped", "exitstatus": "OK"}})
        if path.endswith("/nodes/fake-node-01/qemu/123/config") and request.method == "PUT":
            return httpx.Response(200, json={"data": "UPID:serial"})
        if path.endswith("/nodes/fake-node-01/tasks/UPID:serial/status"):
            return httpx.Response(200, json={"data": {"status": "stopped", "exitstatus": "OK"}})
        raise AssertionError(f"Unexpected request: {request.method} {request.url}")

    client = ProxmoxApiClient(build_settings(), transport=httpx.MockTransport(handler))
    client._ticket = "ticket"
    client._csrf_token = "csrf"
    client._client.cookies.set("PVEAuthCookie", "ticket")

    config = VmConfig()
    config.general.node = "fake-node-01"
    config.general.vmid = 123
    config.general.name = "demo"
    config.general.ha_enabled = False
    config.os.storage = "cephfs"
    config.os.iso = "cephfs:iso/nixos.iso"

    try:
        client.create_vm(config)
    finally:
        client.close()

    serial_request = next(
        content
        for method, path, content in requests
        if method == "PUT" and path.endswith("/nodes/fake-node-01/qemu/123/config")
    )
    payload = parse_qs(serial_request.decode())

    assert payload["serial0"] == ["socket"]
    assert "vga" not in payload


def test_client_starts_vm_after_create_when_requested() -> None:
    requests: list[tuple[str, str, bytes]] = []

    def handler(request: httpx.Request) -> httpx.Response:
        requests.append((request.method, request.url.path, request.content))
        path = request.url.path
        if path.endswith("/nodes/fake-node-01/qemu") and request.method == "POST":
            return httpx.Response(200, json={"data": "UPID:create"})
        if path.endswith("/nodes/fake-node-01/tasks/UPID:create/status"):
            return httpx.Response(200, json={"data": {"status": "stopped", "exitstatus": "OK"}})
        if path.endswith("/nodes/fake-node-01/qemu/123/config") and request.method == "PUT":
            return httpx.Response(200, json={"data": "UPID:serial"})
        if path.endswith("/nodes/fake-node-01/tasks/UPID:serial/status"):
            return httpx.Response(200, json={"data": {"status": "stopped", "exitstatus": "OK"}})
        if path.endswith("/nodes/fake-node-01/qemu/123/status/start") and request.method == "POST":
            return httpx.Response(200, json={"data": "UPID:start"})
        if path.endswith("/nodes/fake-node-01/tasks/UPID:start/status"):
            return httpx.Response(200, json={"data": {"status": "stopped", "exitstatus": "OK"}})
        raise AssertionError(f"Unexpected request: {request.method} {request.url}")

    client = ProxmoxApiClient(build_settings(), transport=httpx.MockTransport(handler))
    client._ticket = "ticket"
    client._csrf_token = "csrf"
    client._client.cookies.set("PVEAuthCookie", "ticket")

    config = VmConfig()
    config.general.node = "fake-node-01"
    config.general.vmid = 123
    config.general.name = "demo"
    config.general.ha_enabled = False
    config.os.storage = "cephfs"
    config.os.iso = "cephfs:iso/nixos.iso"

    try:
        client.create_vm(config, start_after_create=True)
    finally:
        client.close()

    assert any(
        method == "POST" and path.endswith("/nodes/fake-node-01/qemu/123/status/start")
        for method, path, _ in requests
    )


def test_client_registers_ha_without_start_when_auto_start_disabled() -> None:
    requests: list[tuple[str, str, bytes]] = []

    def handler(request: httpx.Request) -> httpx.Response:
        requests.append((request.method, request.url.path, request.content))
        path = request.url.path
        if path.endswith("/nodes/fake-node-01/qemu") and request.method == "POST":
            return httpx.Response(200, json={"data": "UPID:create"})
        if path.endswith("/nodes/fake-node-01/tasks/UPID:create/status"):
            return httpx.Response(200, json={"data": {"status": "stopped", "exitstatus": "OK"}})
        if path.endswith("/nodes/fake-node-01/qemu/123/config") and request.method == "PUT":
            return httpx.Response(200, json={"data": "UPID:serial"})
        if path.endswith("/nodes/fake-node-01/tasks/UPID:serial/status"):
            return httpx.Response(200, json={"data": {"status": "stopped", "exitstatus": "OK"}})
        if path.endswith("/cluster/ha/resources") and request.method == "POST":
            return httpx.Response(200, json={"data": "UPID:ha"})
        if path.endswith("/nodes/fake-node-01/tasks/UPID:ha/status"):
            return httpx.Response(200, json={"data": {"status": "stopped", "exitstatus": "OK"}})
        raise AssertionError(f"Unexpected request: {request.method} {request.url}")

    client = ProxmoxApiClient(build_settings(), transport=httpx.MockTransport(handler))
    client._ticket = "ticket"
    client._csrf_token = "csrf"
    client._client.cookies.set("PVEAuthCookie", "ticket")

    config = VmConfig()
    config.general.node = "fake-node-01"
    config.general.vmid = 123
    config.general.name = "demo"
    config.general.ha_enabled = True
    config.os.storage = "cephfs"
    config.os.iso = "cephfs:iso/nixos.iso"

    try:
        client.create_vm(config, start_after_create=False)
    finally:
        client.close()

    ha_request = next(
        content
        for method, path, content in requests
        if method == "POST" and path.endswith("/cluster/ha/resources")
    )
    payload = parse_qs(ha_request.decode())

    assert payload["state"] == ["stopped"]
    assert not any(
        method == "POST" and path.endswith("/nodes/fake-node-01/qemu/123/status/start")
        for method, path, _ in requests
    )
