"""Tests for dashboard/tools/ REST endpoints via TestClient."""

from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from fastapi.testclient import TestClient
from fastapi import FastAPI

from proxym.dashboard._tickets_api import _tickets
from proxym.dashboard.tickets import Ticket
from proxym.tools import ToolRegistry, ToolCategory
from proxym.tools.adapters import ToolResult
from proxym.tools.executor import ToolExecutor, JobStatus, ToolJob


@pytest.fixture(autouse=True)
def reset_singletons():
    ToolRegistry._instance = None
    ToolExecutor._instance = None
    yield
    ToolRegistry._instance = None
    ToolExecutor._instance = None


@pytest.fixture
def client():
    app = FastAPI()
    from proxym.dashboard.tools import router
    app.include_router(router, prefix="/dashboard")
    return TestClient(app)


# ── List tools ────────────────────────────────────────────

class TestListTools:
    def test_returns_all_tools(self, client):
        r = client.get("/dashboard/tools/")
        assert r.status_code == 200
        data = r.json()
        assert "tools" in data
        assert data["count"] == 9  # builtins
        names = {t["name"] for t in data["tools"]}
        assert "aider" in names
        assert "claude-code" in names
        assert "human" in names

    def test_filter_by_category(self, client):
        r = client.get("/dashboard/tools/?category=agent_cli")
        assert r.status_code == 200
        data = r.json()
        for t in data["tools"]:
            assert t["category"] == "agent_cli"

    def test_summary(self, client):
        r = client.get("/dashboard/tools/summary")
        assert r.status_code == 200
        data = r.json()
        assert data["total"] == 9
        assert "available" in data
        assert "tools" in data


# ── Get specific tool ─────────────────────────────────────

class TestGetTool:
    def test_get_aider(self, client):
        r = client.get("/dashboard/tools/aider")
        assert r.status_code == 200
        data = r.json()
        assert data["name"] == "aider"
        assert data["category"] == "agent_cli"

    def test_get_missing_tool(self, client):
        r = client.get("/dashboard/tools/nonexistent-xyz")
        assert r.status_code == 404


# ── Queue endpoints ───────────────────────────────────────

class TestQueueEndpoints:
    def test_queue_status_empty(self, client):
        r = client.get("/dashboard/tools/queue")
        assert r.status_code == 200
        data = r.json()
        assert data["total_jobs"] == 0
        assert data["running"] is False

    def test_queue_status_includes_ticket_and_logs(self, client):
        mgr = _tickets()
        ticket = mgr.create_ticket(Ticket(title="Export logs ticket"))

        executor = ToolExecutor.get()
        job = ToolJob(
            ticket_id=ticket.id,
            tool_name="roo-code",
            mode="code",
            model="claude-sonnet-4-6",
            project_dir="/projects/export-logs",
            prompt="Update the export logs summary and ensure test coverage.",
        )
        job.status = JobStatus.DONE
        job.started_at = 1.0
        job.finished_at = 2.5
        job.result = ToolResult(
            tool="roo-code",
            ticket_id=ticket.id,
            exit_code=0,
            stdout="hello\nworld",
            stderr="warning line",
            duration_s=1.5,
            execution_metadata={"adapter": "shell", "binary": "roo-code", "args": ["roo-code", "prompt"]},
        )
        executor._jobs[job.id] = job

        r = client.get("/dashboard/tools/queue")
        assert r.status_code == 200
        data = r.json()
        recent = data["recent_jobs"]
        assert len(recent) == 1
        snapshot = recent[0]
        assert snapshot["ticket"]["title"] == "Export logs ticket"
        assert snapshot["project_dir"] == "/projects/export-logs"
        assert snapshot["prompt_excerpt"].startswith("Update the export logs")
        assert snapshot["model"] == "claude-sonnet-4-6"
        assert snapshot["result"]["stdout"] == "hello\nworld"
        assert snapshot["result"]["stderr"] == "warning line"
        assert snapshot["result"]["success"] is True
        assert snapshot["result"]["execution"]["adapter"] == "shell"

    def test_list_jobs_empty(self, client):
        r = client.get("/dashboard/tools/queue/jobs")
        assert r.status_code == 200
        data = r.json()
        assert data["jobs"] == []
        assert data["count"] == 0

    def test_get_job_missing(self, client):
        r = client.get("/dashboard/tools/queue/jobs/nonexistent")
        assert r.status_code == 404

    def test_cancel_job_missing(self, client):
        r = client.post("/dashboard/tools/queue/jobs/nonexistent/cancel")
        assert r.status_code == 400


# ── Dispatch (requires ticket) ────────────────────────────

class TestDispatch:
    def test_dispatch_unknown_tool(self, client):
        r = client.post("/dashboard/tools/dispatch", json={
            "ticket_id": "TKT-FAKE",
            "tool": "nonexistent-xyz",
        })
        assert r.status_code == 404

    def test_dispatch_missing_ticket(self, client):
        r = client.post("/dashboard/tools/dispatch", json={
            "ticket_id": "TKT-DOESNOTEXIST",
            "tool": "aider",
        })
        # The ticket lookup should fail with 404
        assert r.status_code == 404

    @patch("proxym.tools.executor.ToolExecutor.get")
    def test_dispatch_human_creates_manual_assignment(self, mock_executor_get, client):
        ticket = _tickets().create_ticket(Ticket(title="Manual handoff"))

        r = client.post("/dashboard/tools/dispatch", json={
            "ticket_id": ticket.id,
            "tool": "human",
        })

        assert r.status_code == 200
        data = r.json()
        assert data["job"] is None
        assert "Human task assigned" in data["message"]
        assert data["ticket"]["assigned_tool"]["tool"] == "human"
        assert data["ticket"]["status"] == "in_progress"
        mock_executor_get.assert_not_called()

    @patch("proxym.dashboard.tools._dispatch._default_project_dir", return_value="/projects/proj-123")
    @patch("proxym.dashboard.tools._dispatch._get_ticket")
    @patch("proxym.dashboard.tools._dispatch.ToolRegistry.get")
    @patch("proxym.tools.executor.ToolExecutor.get")
    def test_dispatch_uses_ticket_project_when_project_dir_missing(
        self,
        mock_executor_get,
        mock_registry_get,
        mock_get_ticket,
        mock_default_project_dir,
        client,
    ):
        ticket = SimpleNamespace(id="TKT-123", project_id="proj-123")
        tool = SimpleNamespace(name="aider")
        mock_get_ticket.return_value = ticket

        registry = MagicMock()
        registry.get_tool.return_value = tool
        mock_registry_get.return_value = registry

        executor = MagicMock()
        executor.start = AsyncMock()
        executor.enqueue.return_value = MagicMock()
        executor.enqueue.return_value.summary.return_value = {"id": "job-1"}
        mock_executor_get.return_value = executor

        r = client.post("/dashboard/tools/dispatch", json={
            "ticket_id": "TKT-123",
            "tool": "aider",
        })

        assert r.status_code == 200
        mock_default_project_dir.assert_called_once_with("proj-123")
        executor.enqueue.assert_called_once_with(ticket, tool, "/projects/proj-123", "", "", environment=None)



# ── Prompt preview (requires ticket) ─────────────────────

class TestPromptPreview:
    def test_preview_unknown_tool(self, client):
        r = client.post("/dashboard/tools/prompt/preview", json={
            "ticket_id": "TKT-1",
            "tool": "nonexistent-xyz",
        })
        assert r.status_code == 404
