"""E2E tests for new dashboard features: auto-fix, human tool, job history, KVM/VM support.

These tests verify end-to-end API flows for:
- Tool health check with KVM/VM support
- Auto-fix execution (install, start_container)
- Human tool dispatch (bypasses executor)
- Job history in ticket details
- Bulk auto-fix
"""
from __future__ import annotations

import pytest


@pytest.fixture
async def api_client():
    """Return client and auto-skip if server not available."""
    import httpx

    client = httpx.AsyncClient(base_url="http://localhost:4001", timeout=30.0)

    # Check if server is available
    try:
        await client.get("/health")
    except Exception as exc:
        await client.aclose()
        pytest.skip(f"Dashboard API not available on localhost:4001: {exc}")

    yield client
    await client.aclose()


class TestToolHealthCheck:
    """E2E tests for tool health check endpoint."""

    @pytest.mark.asyncio
    async def test_healthcheck_returns_all_tools(self, api_client):
        """Health check returns all registered tools with diagnostics."""
        resp = await api_client.get("/dashboard/tools/healthcheck")
        assert resp.status_code == 200
        data = resp.json()

        assert "tools" in data
        assert "summary" in data
        assert "providers_configured" in data

        # Check all tools have expected fields
        for tool in data["tools"]:
            assert "name" in tool
            assert "available" in tool
            assert "binary_found" in tool
            assert "docker_instance" in tool
            assert "kvm_instance" in tool
            assert "vm_instance" in tool
            assert "status" in tool  # ok, degraded, unavailable
            assert tool["status"] in ("ok", "degraded", "unavailable")

    @pytest.mark.asyncio
    async def test_healthcheck_summary_counts(self, api_client):
        """Summary shows counts of ok/degraded/unavailable tools."""
        resp = await api_client.get("/dashboard/tools/healthcheck")
        data = resp.json()

        summary = data["summary"]
        assert "total" in summary
        assert "ok" in summary
        assert "degraded" in summary
        assert "unavailable" in summary

        # Verify counts add up
        total = summary["total"]
        ok = summary["ok"]
        degraded = summary["degraded"]
        unavailable = summary["unavailable"]
        assert ok + degraded + unavailable == total


class TestAutoFix:
    """E2E tests for auto-fix endpoints."""

    @pytest.mark.asyncio
    async def test_autofix_endpoint_exists(self, api_client):
        """Auto-fix endpoint is accessible and returns expected format."""
        payload = {
            "tool_name": "test-tool",
            "fix_action": "info",
            "command": "echo test"
        }
        resp = await api_client.post("/dashboard/tools/autofix", json=payload)
        # May fail due to auth or tool not found, but endpoint should exist
        assert resp.status_code in (200, 400, 404, 422)

    @pytest.mark.asyncio
    async def test_autofix_creates_ticket_for_human_action(self, api_client):
        """Auto-fix with 'info' or 'config' action creates human ticket."""
        payload = {
            "tool_name": "windsurf",
            "fix_action": "info",
            "command": "# Install windsurf from website"
        }
        resp = await api_client.post("/dashboard/tools/autofix", json=payload)
        assert resp.status_code == 200
        data = resp.json()

        # Should indicate human action required
        assert "ticket_created" in data or "error" in data

    @pytest.mark.asyncio
    async def test_bulk_autofix_endpoint(self, api_client):
        """Bulk auto-fix endpoint runs fixes for all degraded tools."""
        resp = await api_client.post("/dashboard/tools/autofix/bulk")
        assert resp.status_code == 200
        data = resp.json()

        assert "executed" in data
        assert "failed" in data
        assert "human_required" in data
        assert "results" in data

        # Counts should be non-negative
        assert data["executed"] >= 0
        assert data["failed"] >= 0
        assert data["human_required"] >= 0


class TestHumanTool:
    """E2E tests for human tool dispatch."""

    @pytest.mark.asyncio
    async def test_human_tool_dispatches_without_executor(self, api_client):
        """Human tool dispatch assigns ticket without creating a job."""
        # First create a ticket
        ticket_payload = {
            "task": "Human approval needed for API key",
            "description": "Please approve the API key request",
            "priority": "high",
            "type": "task"
        }
        resp = await api_client.post("/dashboard/tickets", json=ticket_payload)
        assert resp.status_code in (200, 201)
        ticket = resp.json()
        ticket_id = ticket["id"]

        # Dispatch human tool
        dispatch_payload = {
            "ticket_id": ticket_id,
            "tool": "human",
            "mode": "",
            "model": ""
        }
        resp = await api_client.post("/dashboard/tools/dispatch", json=dispatch_payload)
        assert resp.status_code == 200
        data = resp.json()

        # Human tool should not create a job
        assert data.get("job") is None
        assert "ticket" in data
        assert "Human task assigned" in data.get("message", "")

        # Verify ticket was updated with human assignment
        ticket_data = data["ticket"]
        assert ticket_data["assigned_tool"]["tool"] == "human"

    @pytest.mark.asyncio
    async def test_human_tool_in_healthcheck(self, api_client):
        """Human tool appears in health check as always available."""
        resp = await api_client.get("/dashboard/tools/healthcheck")
        data = resp.json()

        human_tools = [t for t in data["tools"] if t["name"] == "human"]
        if human_tools:
            human = human_tools[0]
            assert human["available"] is True
            assert human["status"] in ("ok", "degraded")


class TestJobHistory:
    """E2E tests for job history in ticket details."""

    @pytest.mark.asyncio
    async def test_ticket_detail_includes_job_history(self, api_client):
        """GET /dashboard/tickets/{id} returns job history."""
        # First list tickets to find one
        resp = await api_client.get("/dashboard/tickets")
        assert resp.status_code == 200
        data = resp.json()
        tickets = data.get("tickets", []) if isinstance(data, dict) else data

        if not tickets or len(tickets) == 0:
            pytest.skip("No tickets available for testing")

        ticket_id = tickets[0]["id"]

        # Get ticket detail
        resp = await api_client.get(f"/dashboard/tickets/{ticket_id}")
        assert resp.status_code == 200
        data = resp.json()

        # Should have job_history field
        assert "job_history" in data
        job_history = data["job_history"]

        # If there are jobs, verify their structure
        if job_history:
            for job in job_history:
                assert "job_id" in job
                assert "tool_name" in job
                assert "status" in job
                assert "created_at" in job

    @pytest.mark.asyncio
    async def test_job_execution_details_in_ticket(self, api_client):
        """Job history includes execution metadata like exit_code, stdout, stderr."""
        resp = await api_client.get("/dashboard/tickets")
        assert resp.status_code == 200
        data = resp.json()
        tickets = data.get("tickets", []) if isinstance(data, dict) else data

        if not tickets or len(tickets) == 0:
            pytest.skip("No tickets available for testing")

        ticket_id = tickets[0]["id"]

        resp = await api_client.get(f"/dashboard/tickets/{ticket_id}")
        data = resp.json()
        job_history = data.get("job_history", [])

        # Check completed jobs have execution details
        for job in job_history:
            if job.get("status") in ("done", "failed"):
                # Should have result details
                if "result" in job:
                    result = job["result"]
                    assert "success" in result
                    assert "exit_code" in result
                    assert "stdout_lines" in result or "stdout" in result
                    assert "stderr_lines" in result or "stderr" in result


class TestKVMVMSupport:
    """E2E tests for KVM/VM instance support in tools."""

    @pytest.mark.asyncio
    async def test_tool_instances_show_vm_details(self, api_client):
        """Tool summary includes KVM/VM instance details if configured."""
        resp = await api_client.get("/dashboard/tools/")
        assert resp.status_code == 200
        data = resp.json()

        for tool in data.get("tools", []):
            instances = tool.get("instances", [])
            for inst in instances:
                # All instances should have kind
                assert "kind" in inst
                assert inst["kind"] in ("host", "docker", "kvm", "vm")

                # KVM/VM instances should have relevant fields
                if inst["kind"] in ("kvm", "vm"):
                    assert "vm_name" in inst
                    assert "vm_host" in inst
                    assert "vm_user" in inst

    @pytest.mark.asyncio
    async def test_healthcheck_detects_vm_availability(self, api_client):
        """Health check reports KVM/VM instance availability."""
        resp = await api_client.get("/dashboard/tools/healthcheck")
        data = resp.json()

        for tool in data.get("tools", []):
            # KVM instance flag should exist
            assert "kvm_instance" in tool
            assert "vm_instance" in tool

            # If tool has VM instances, check for related issues
            if tool.get("kvm_instance") or tool.get("vm_instance"):
                # VM-based tool should have appropriate status
                assert tool["status"] in ("ok", "degraded", "unavailable")


class TestToolQueueOperations:
    """E2E tests for job queue operations."""

    @pytest.mark.asyncio
    async def test_queue_status_endpoint(self, api_client):
        """Queue status returns summary and recent jobs."""
        resp = await api_client.get("/dashboard/tools/queue")
        assert resp.status_code == 200
        data = resp.json()

        assert "queue_size" in data
        assert "max_concurrent" in data
        assert "recent_jobs" in data

    @pytest.mark.asyncio
    async def test_list_jobs_endpoint(self, api_client):
        """List jobs endpoint returns jobs with filtering."""
        resp = await api_client.get("/dashboard/tools/queue/jobs?limit=10")
        assert resp.status_code == 200
        data = resp.json()

        assert "jobs" in data
        assert "count" in data
        assert data["count"] <= 10

    @pytest.mark.asyncio
    async def test_job_detail_includes_ticket_metadata(self, api_client):
        """Job details include associated ticket metadata."""
        # Get list of jobs
        resp = await api_client.get("/dashboard/tools/queue/jobs?limit=1")
        data = resp.json()

        if not data.get("jobs"):
            pytest.skip("No jobs available for testing")

        job_id = data["jobs"][0]["id"]

        # Get job detail
        resp = await api_client.get(f"/dashboard/tools/queue/jobs/{job_id}")
        assert resp.status_code == 200
        job_data = resp.json()

        # Should have ticket reference
        assert "ticket_id" in job_data
        if job_data.get("ticket"):
            assert "id" in job_data["ticket"]


class TestToolProviderAssignment:
    """E2E tests for tool provider assignment."""

    @pytest.mark.asyncio
    async def test_get_tool_providers(self, api_client):
        """Get current tool provider mappings."""
        resp = await api_client.get("/dashboard/tools/providers")
        assert resp.status_code == 200
        data = resp.json()

        assert "tool_providers" in data
        assert "available_providers" in data

    @pytest.mark.asyncio
    async def test_set_tool_provider_validation(self, api_client):
        """Setting invalid provider returns error."""
        payload = {
            "tool": "aider",
            "provider": "invalid-provider-name-12345"
        }
        resp = await api_client.post("/dashboard/tools/provider", json=payload)
        # Should fail validation or return 400
        assert resp.status_code in (400, 422, 200)


class TestEndToEndToolFlow:
    """Full end-to-end tool dispatch and monitoring flow."""

    @pytest.mark.asyncio
    async def test_full_dispatch_to_completion_flow(self, api_client):
        """Test complete flow: dispatch tool, monitor job, verify ticket update."""
        # 1. Create a ticket
        ticket_payload = {
            "task": "E2E test: add README file",
            "description": "Create a README.md file with project description",
            "priority": "low",
            "type": "task"
        }
        resp = await api_client.post("/dashboard/tickets", json=ticket_payload)
        assert resp.status_code in (200, 201)
        ticket = resp.json()
        ticket_id = ticket["id"]

        # 2. Dispatch a tool (use human to avoid actual execution)
        dispatch_payload = {
            "ticket_id": ticket_id,
            "tool": "human",
            "mode": "",
            "model": ""
        }
        resp = await api_client.post("/dashboard/tools/dispatch", json=dispatch_payload)
        assert resp.status_code == 200

        # 3. Verify ticket updated
        resp = await api_client.get(f"/dashboard/tickets/{ticket_id}")
        assert resp.status_code == 200
        updated_ticket = resp.json()

        assert updated_ticket["assigned_tool"]["tool"] == "human"
