from unittest.mock import AsyncMock, patch

import pytest
from click.testing import CliRunner

from dobby.cli.main import cli


@pytest.fixture
def runner():
    return CliRunner()


def test_add_task(runner):
    with patch("dobby.cli.client.DobbyClient.post", new_callable=AsyncMock) as mock_post:
        mock_post.return_value = {
            "id": "t-001", "status": "pending", "project": "spare", "description": "Test",
        }
        result = runner.invoke(cli, ["add", "Test task", "--project", "spare"])
    assert result.exit_code == 0
    assert "t-001" in result.output


def test_add_task_with_cron(runner):
    with patch("dobby.cli.client.DobbyClient.post", new_callable=AsyncMock) as mock_post:
        mock_post.return_value = {
            "id": "t-002", "status": "active", "project": "spare", "description": "Nightly eval",
        }
        result = runner.invoke(
            cli, ["add", "Nightly eval", "--project", "spare", "--cron", "0 3 * * *"]
        )
    assert result.exit_code == 0
    mock_post.assert_called_once()
    assert mock_post.call_args.kwargs["json"]["schedule"] == {"type": "cron", "expr": "0 3 * * *"}


def test_add_task_with_backend(runner):
    with patch("dobby.cli.client.DobbyClient.post", new_callable=AsyncMock) as mock_post:
        mock_post.return_value = {
            "id": "t-004", "status": "pending", "project": "spare", "backend": "codex",
        }
        result = runner.invoke(
            cli, ["add", "Use codex", "--project", "spare", "--backend", "codex"]
        )
    assert result.exit_code == 0
    mock_post.assert_called_once()
    assert mock_post.call_args.kwargs["json"]["backend"] == "codex"


def test_plan(runner):
    with patch("dobby.cli.client.DobbyClient.post", new_callable=AsyncMock) as mock_post:
        mock_post.return_value = {"id": "t-003", "status": "drafting", "mode": "planned"}
        result = runner.invoke(cli, ["plan", "Refactor orchestrator", "--project", "memagent"])
    assert result.exit_code == 0
    assert "t-003" in result.output


def test_status(runner):
    with patch("dobby.cli.client.DobbyClient.get", new_callable=AsyncMock) as mock_get:
        mock_get.return_value = [
            {"id": "t-001", "project": "spare", "description": "Task A",
             "status": "running", "priority": "normal"},
            {"id": "t-002", "project": "memagent", "description": "Task B",
             "status": "pending", "priority": "high"},
        ]
        result = runner.invoke(cli, ["status"])
    assert result.exit_code == 0
    assert "spare" in result.output


def test_status_project_filter(runner):
    with patch("dobby.cli.client.DobbyClient.get", new_callable=AsyncMock) as mock_get:
        mock_get.return_value = [
            {"id": "t-001", "project": "spare", "description": "Task A",
             "status": "running", "priority": "normal"},
        ]
        result = runner.invoke(cli, ["status", "spare"])
    assert result.exit_code == 0


def test_results(runner):
    with patch("dobby.cli.client.DobbyClient.get", new_callable=AsyncMock) as mock_get:
        mock_get.return_value = {
            "task_id": "t-001", "status": "completed", "summary": "Done",
            "artifacts": [], "metrics": {}, "cost": {"cost_usd": 0.10},
        }
        result = runner.invoke(cli, ["results", "t-001"])
    assert result.exit_code == 0
    assert "Done" in result.output


def test_logs(runner):
    with patch("dobby.cli.client.DobbyClient.get", new_callable=AsyncMock) as mock_get:
        mock_get.return_value = {"logs": "line 1\nline 2", "lines": 2}
        result = runner.invoke(cli, ["logs", "t-001"])
    assert result.exit_code == 0
    assert "line 1" in result.output


def test_projects(runner):
    with patch("dobby.cli.client.DobbyClient.get", new_callable=AsyncMock) as mock_get:
        mock_get.return_value = [
            {"name": "spare", "repo": "/path/spare", "context": "RL"},
            {"name": "memagent", "repo": "/path/memagent", "context": "Memory"},
        ]
        result = runner.invoke(cli, ["projects"])
    assert result.exit_code == 0
    assert "spare" in result.output
    assert "memagent" in result.output


def test_cancel(runner):
    with patch("dobby.cli.client.DobbyClient.patch", new_callable=AsyncMock) as mock_patch:
        mock_patch.return_value = {"id": "t-001", "status": "cancelled"}
        result = runner.invoke(cli, ["cancel", "t-001"])
    assert result.exit_code == 0
    assert "cancelled" in result.output.lower()


def test_retry(runner):
    with patch("dobby.cli.client.DobbyClient.post", new_callable=AsyncMock) as mock_post:
        mock_post.return_value = {"id": "t-001-retry-1", "status": "pending"}
        result = runner.invoke(cli, ["retry", "t-001"])
    assert result.exit_code == 0


def test_retry_cascade(runner):
    with patch("dobby.cli.client.DobbyClient.post", new_callable=AsyncMock) as mock_post:
        mock_post.return_value = {
            "retried": [
                {"id": "t-001", "status": "pending"},
                {"id": "t-002", "status": "active"},
            ]
        }
        result = runner.invoke(cli, ["retry", "t-001", "--cascade"])
    assert result.exit_code == 0
    assert "2 task(s)" in result.output
    assert "t-001" in result.output
    assert "t-002" in result.output


def test_stats(runner):
    with patch("dobby.cli.client.DobbyClient.get", new_callable=AsyncMock) as mock_get:
        mock_get.return_value = {
            "pending": 3, "running": 1, "completed": 10, "failed": 2,
            "total_cost_usd": 4.20, "total_tokens": 500000,
        }
        result = runner.invoke(cli, ["stats"])
    assert result.exit_code == 0
    assert "pending" in result.output.lower()


def test_register(runner):
    with patch("dobby.cli.client.DobbyClient.post", new_callable=AsyncMock) as mock_post:
        mock_post.return_value = {"name": "myproject", "repo": "/path/to/repo"}
        result = runner.invoke(cli, ["register", "/path/to/repo", "--name", "myproject"])
    assert result.exit_code == 0
