import uuid

from app.compute_provider import ComputeResult, SnapshotResult, get_compute_provider
from app.errors import AppError
from app.main import app


def owner_payload():
    return {
        "email": "owner@example.com",
        "password": "correct horse battery staple",
        "display_name": "Owner",
        "workspace_name": "Personal",
        "timezone": "Asia/Dubai",
    }


def csrf_headers(client, *, idempotency_key: str | None = None):
    headers = {"x-csrf-token": client.cookies.get("hayva_csrf")}
    if idempotency_key:
        headers["Idempotency-Key"] = idempotency_key
    return headers


class FakeComputeProvider:
    name = "libvirt_gateway"
    configured = True

    def __init__(self):
        self.resources: dict[str, str] = {}
        self.snapshots: dict[str, str] = {}
        self.calls: list[tuple[str, str]] = []
        self.fail_action: str | None = None

    def result(self, resource_id: str) -> ComputeResult:
        return ComputeResult(
            provider_request_id=str(uuid.uuid4()),
            provider_resource_id=resource_id,
            status=self.resources[resource_id],
            ip_address="192.0.2.10",
            browser_status="ready" if self.resources[resource_id] == "running" else "stopped",
            metrics={"cpu_percent": 4, "memory_used_mb": 800, "disk_used_gb": 8},
        )

    async def create(self, spec, *, idempotency_key):
        resource_id = f"vm-{spec['computer_id']}"
        self.calls.append(("create", idempotency_key))
        self.resources[resource_id] = "stopped"
        return self.result(resource_id)

    async def _state(self, resource_id, state, action, idempotency_key):
        self.calls.append((action, idempotency_key))
        if self.fail_action == action:
            raise AppError(
                "COMPUTE_PROVIDER_UNAVAILABLE",
                "The virtual-machine provider could not be reached.",
                503,
            )
        self.resources[resource_id] = state
        return self.result(resource_id)

    async def start(self, resource_id, *, idempotency_key):
        return await self._state(resource_id, "running", "start", idempotency_key)

    async def stop(self, resource_id, *, idempotency_key):
        return await self._state(resource_id, "stopped", "stop", idempotency_key)

    async def restart(self, resource_id, *, idempotency_key):
        return await self._state(resource_id, "running", "restart", idempotency_key)

    async def pause(self, resource_id, *, idempotency_key):
        return await self._state(resource_id, "suspended", "pause", idempotency_key)

    async def resume(self, resource_id, *, idempotency_key):
        return await self._state(resource_id, "running", "resume", idempotency_key)

    async def delete(self, resource_id, *, idempotency_key):
        return await self._state(resource_id, "destroyed", "destroy", idempotency_key)

    async def snapshot(self, resource_id, spec, *, idempotency_key):
        self.calls.append(("snapshot", idempotency_key))
        snapshot_id = f"snap-{uuid.uuid4()}"
        self.snapshots[snapshot_id] = resource_id
        return SnapshotResult(
            provider_request_id=str(uuid.uuid4()),
            provider_snapshot_id=snapshot_id,
            status="ready",
        )

    async def restore(self, resource_id, snapshot_id, *, idempotency_key):
        assert self.snapshots[snapshot_id] == resource_id
        return await self._state(resource_id, "stopped", "restore", idempotency_key)

    async def resize(self, resource_id, spec, *, idempotency_key):
        return await self._state(resource_id, self.resources[resource_id], "resize", idempotency_key)

    async def get_status(self, resource_id):
        self.calls.append(("status", resource_id))
        return self.result(resource_id)

    async def get_metrics(self, resource_id):
        return self.result(resource_id)

    async def get_console(self, resource_id):  # pragma: no cover - not exposed yet
        raise AssertionError("Console gateway was not requested")


def configure_template(client, template_key: str, *, windows: bool = False):
    templates = client.get("/api/v1/computers/templates").json()["templates"]
    template = next(item for item in templates if item["template_key"] == template_key)
    return client.put(
        f"/api/v1/computers/templates/{template['id']}",
        json={
            "provider_template_ref": f"golden/{template_key}",
            "image_digest": "sha256:verified-template-digest",
            "windows_license_confirmed": windows,
        },
        headers=csrf_headers(client),
    )


def test_unconfigured_provider_is_honest_and_blocks_runtime_activation(auth_client):
    auth_client.post("/api/v1/setup/owner", json=owner_payload())
    listed = auth_client.get("/api/v1/computers")
    assert listed.status_code == 200
    assert listed.json()["provider_state"] == "unconfigured"
    computer = listed.json()["computers"][0]
    assert computer["status"] == "unconfigured"
    assert computer["provider_configured"] is False
    agent = auth_client.get("/api/v1/agents").json()["agents"][0]
    enabled = auth_client.post(
        f"/api/v1/agents/{agent['id']}/status",
        json={"status": "active"},
        headers=csrf_headers(auth_client),
    )
    assert enabled.status_code == 409
    assert enabled.json()["error"]["code"] == "AGENT_DEDICATED_COMPUTER_UNAVAILABLE"

    retired = auth_client.post(
        f"/api/v1/computers/{computer['id']}/actions/destroy",
        json={"confirm_computer_name": computer["name"]},
        headers=csrf_headers(auth_client, idempotency_key="destroy-unconfigured-01"),
    )
    assert retired.status_code == 200
    assert retired.json()["computer"]["status"] == "destroyed"
    assert retired.json()["operation"]["status"] == "succeeded"
    assert retired.json()["operation"]["verification"] == {
        "observed_status": "destroyed",
        "provider_resource_absent": True,
    }


def test_provision_lifecycle_snapshots_and_idempotency_are_verified(auth_client):
    auth_client.post("/api/v1/setup/owner", json=owner_payload())
    fake = FakeComputeProvider()
    app.dependency_overrides[get_compute_provider] = lambda: fake
    assert configure_template(auth_client, "ubuntu-desktop-24-04").status_code == 200
    computer = auth_client.get("/api/v1/computers").json()["computers"][0]

    missing_key = auth_client.post(
        f"/api/v1/computers/{computer['id']}/provision",
        headers=csrf_headers(auth_client),
    )
    assert missing_key.status_code == 400
    provisioned = auth_client.post(
        f"/api/v1/computers/{computer['id']}/provision",
        headers=csrf_headers(auth_client, idempotency_key="provision-default-01"),
    )
    assert provisioned.status_code == 200
    assert provisioned.json()["computer"]["status"] == "stopped"
    assert provisioned.json()["operation"]["status"] == "succeeded"
    replay = auth_client.post(
        f"/api/v1/computers/{computer['id']}/provision",
        headers=csrf_headers(auth_client, idempotency_key="provision-default-01"),
    )
    assert replay.status_code == 200
    assert replay.json()["replayed"] is True
    assert [call[0] for call in fake.calls].count("create") == 1

    started = auth_client.post(
        f"/api/v1/computers/{computer['id']}/actions/start",
        json={}, headers=csrf_headers(auth_client, idempotency_key="start-default-0001"),
    )
    assert started.status_code == 200
    assert started.json()["computer"]["status"] == "running"
    start_replay = auth_client.post(
        f"/api/v1/computers/{computer['id']}/actions/start",
        json={}, headers=csrf_headers(auth_client, idempotency_key="start-default-0001"),
    )
    assert start_replay.status_code == 200
    assert start_replay.json()["replayed"] is True
    assert [call[0] for call in fake.calls].count("start") == 1

    snapshot = auth_client.post(
        f"/api/v1/computers/{computer['id']}/snapshots",
        json={"name": "Before tools", "includes_memory": False},
        headers=csrf_headers(auth_client, idempotency_key="snapshot-default-01"),
    )
    assert snapshot.status_code == 201
    snapshot_id = snapshot.json()["snapshot_id"]
    not_confirmed = auth_client.post(
        f"/api/v1/computers/{computer['id']}/snapshots/{snapshot_id}/restore",
        json={"confirm": False},
        headers=csrf_headers(auth_client, idempotency_key="restore-default-001"),
    )
    assert not_confirmed.status_code == 409
    restored = auth_client.post(
        f"/api/v1/computers/{computer['id']}/snapshots/{snapshot_id}/restore",
        json={"confirm": True},
        headers=csrf_headers(auth_client, idempotency_key="restore-default-002"),
    )
    assert restored.status_code == 200
    assert restored.json()["operation"]["verification"]["observed_status"] == "stopped"


def test_agent_creation_provisions_exactly_one_os_computer_and_blocks_host_policy(auth_client):
    auth_client.post("/api/v1/setup/owner", json=owner_payload())
    fake = FakeComputeProvider()
    app.dependency_overrides[get_compute_provider] = lambda: fake
    unlicensed = configure_template(auth_client, "windows-11")
    assert unlicensed.status_code == 409
    assert configure_template(auth_client, "windows-11", windows=True).status_code == 200
    created = auth_client.post(
        "/api/v1/agents",
        json={
            "name": "Windows Sales Assistant",
            "purpose": "Operate an authorized Windows-only CRM.",
            "autonomy_level": 3,
            "tool_names": [],
            "permission_keys": [],
            "computer": {
                "create_dedicated": True,
                "os_family": "windows",
                "template_key": "windows-11",
                "cpu_cores": 4,
                "memory_mb": 8192,
                "disk_gb": 80,
                "browser": "edge",
                "persistent_disk": True,
                "system_privilege": "administrator",
                "computer_autonomy": 3,
                "start_policy": "manual",
                "network_policy": {
                    "internet_access": True,
                    "lan_access": False,
                    "platform_api_access": True,
                    "other_agent_networks": False,
                    "allowed_domains": ["crm.example.com"],
                    "blocked_domains": [],
                },
            },
        },
        headers=csrf_headers(auth_client),
    )
    assert created.status_code == 201
    agent = created.json()["agent"]
    assert agent["computer_required"] is True
    assert agent["computer"]["os_family"] == "windows"
    assert agent["computer"]["status"] == "stopped"
    computers = auth_client.get("/api/v1/computers").json()["computers"]
    assigned = [item for item in computers if item["agent"]["id"] == agent["id"]]
    assert len(assigned) == 1
    computer = assigned[0]
    host_access = auth_client.put(
        f"/api/v1/computers/{computer['id']}/network-policy",
        json={
            "internet_access": True,
            "lan_access": False,
            "platform_api_access": True,
            "other_agent_networks": False,
            "host_management_access": True,
            "allowed_domains": [],
            "blocked_domains": [],
        },
        headers=csrf_headers(auth_client),
    )
    assert host_access.status_code == 422


def test_ambiguous_provider_outage_remains_unknown_and_is_not_retried(auth_client):
    auth_client.post("/api/v1/setup/owner", json=owner_payload())
    fake = FakeComputeProvider()
    app.dependency_overrides[get_compute_provider] = lambda: fake
    assert configure_template(auth_client, "ubuntu-desktop-24-04").status_code == 200
    computer = auth_client.get("/api/v1/computers").json()["computers"][0]
    provisioned = auth_client.post(
        f"/api/v1/computers/{computer['id']}/provision",
        headers=csrf_headers(auth_client, idempotency_key="provision-outage-01"),
    )
    assert provisioned.json()["computer"]["status"] == "stopped"

    fake.fail_action = "start"
    first = auth_client.post(
        f"/api/v1/computers/{computer['id']}/actions/start",
        json={}, headers=csrf_headers(auth_client, idempotency_key="start-outage-0001"),
    )
    assert first.status_code == 200
    assert first.json()["computer"]["status"] == "recovering"
    assert first.json()["operation"]["status"] == "unknown"
    replay = auth_client.post(
        f"/api/v1/computers/{computer['id']}/actions/start",
        json={}, headers=csrf_headers(auth_client, idempotency_key="start-outage-0001"),
    )
    assert replay.status_code == 200
    assert replay.json()["replayed"] is True
    assert [call[0] for call in fake.calls].count("start") == 1
