"""MCP service create_task_tool unit tests."""

import os
from collections.abc import AsyncGenerator
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any

import pytest
from mcp import Client as MCPClient
from pygit2 import Oid, Repository, init_repository
from pygittools.tasks import EMPTY_TREE_OID_HEX, Board, CreateTaskResult, Task, create_task, get_board
from pygittools.tasks_query import TaskView, board_ref

from pygittools_mcp.exceptions import (
    BoardNotFoundError,
    InvalidTaskDueDateError,
    InvalidTaskPriorityError,
    InvalidTaskStatusError,
)
from pygittools_mcp.mcp_server import mcp
from pygittools_mcp.models import CreateTaskToolResult

TAGGER: str = "alice <alice@example.com>"


@pytest.fixture
def repo(tmp_path: Path) -> Repository:
    """Create a temp repo with a Tasks board so create_task_tool can find it."""
    repo_path = tmp_path / "repo"
    repo_path.mkdir()

    repo = init_repository(str(repo_path), bare=False)

    # Set up initial commit on main
    import subprocess

    os_repo_path = str(repo_path)
    subprocess.run(
        ["git", "config", "user.email", "test@test.com"],
        cwd=os_repo_path,
        check=True,
    )
    subprocess.run(
        ["git", "config", "user.name", "Test"],
        cwd=os_repo_path,
        check=True,
    )
    with open(os.path.join(os_repo_path, "README.md"), "w") as f:
        f.write("# initial\n")
    subprocess.run(["git", "add", "README.md"], cwd=os_repo_path, check=True)
    subprocess.run(
        ["git", "commit", "-m", "initial"],
        cwd=os_repo_path,
        check=True,
        capture_output=True,
        text=True,
    )

    # Create the Tasks board ref so create_task_tool.get_board() can find it
    eoi = Oid(hex=EMPTY_TREE_OID_HEX)
    tasks_board = Board(eoi, "refs/tags/boards/Tasks", TAGGER, description="Tasks")
    tasks_board.write(repo)
    return repo


@pytest.fixture
def test_task(repo: Repository) -> CreateTaskResult:
    board = get_board(repo, board_ref("Tasks"))
    assert board is not None
    return create_task(repo, board, title="Test Task")


@pytest.fixture
async def mcp_client(repo: Repository, monkeypatch: pytest.MonkeyPatch) -> AsyncGenerator[MCPClient, None]:
    monkeypatch.setenv("PYGITTOOLS_REPO", repo.path)
    async with MCPClient(mcp) as client:
        yield client


def assert_default_board(board: dict[str, Any]):
    assert board["name"] == "Tasks"
    assert board["description"] == "Tasks"
    assert board["task_count"] == 0


def assert_is_test_task(view: TaskView, task: CreateTaskResult):
    assert view.get("ref") == task["ref"]
    assert view.get("title") == task["task"].title
    assert view.get("description") == task["task"].description
    view_status = view.get("status")
    task_status = task["task"].status
    assert view_status == task_status.value if task_status else None
    view_priority = view.get("priority")
    task_priority = task["task"].priority
    assert view_priority == task_priority.value if task_priority else None
    view_due_date = view.get("due_date")
    task_due_date = task["task"].due_date
    if view_due_date:
        assert view_due_date == task_due_date.isoformat() if task_due_date else None
    if t := view.get("created_at"):
        assert t == task["task"].created_at.isoformat()
    if t := view.get("updated_at"):
        assert t == task["task"].updated_at.isoformat()


@pytest.mark.anyio
async def test_list_boards_tool(mcp_client: MCPClient) -> None:
    result = await mcp_client.call_tool("list_boards_tool", {"project": None})
    assert not result.is_error, result.content
    content = result.structured_content["result"]
    assert isinstance(content, list)
    assert len(content) == 1
    assert_default_board(content[0])


@pytest.mark.anyio
async def test_list_tasks_tool(mcp_client: MCPClient, test_task: CreateTaskResult) -> None:
    result = await mcp_client.call_tool("list_tasks_tool", {})
    assert not result.is_error, result.content
    content = result.structured_content["result"]
    assert isinstance(content, list)
    assert len(content) == 1
    assert_is_test_task(content[0], test_task)


@pytest.mark.anyio
async def test_get_task_tool(mcp_client: MCPClient, test_task: CreateTaskResult) -> None:
    result = await mcp_client.call_tool("get_task_tool", {"task_ref": test_task["ref"]})
    assert not result.is_error, result.content
    assert_is_test_task(result.structured_content, test_task)


@pytest.mark.anyio
async def test_get_task_tool_not_found(mcp_client: MCPClient) -> None:
    result = await mcp_client.call_tool("get_task_tool", {"task_ref": "fake-task-ref"})
    assert result.is_error


@pytest.mark.anyio
async def test_create_task_tool_success(mcp_client: MCPClient) -> None:
    title = "Test Task"
    description = "A test task"
    create_result = await mcp_client.call_tool("create_task_tool", {"title": title, "description": description})
    assert not create_result.is_error
    task: CreateTaskToolResult = create_result.structured_content
    task_ref = task["ref"]

    get_result = await mcp_client.call_tool("get_task_tool", {"task_ref": task_ref})
    assert not get_result.is_error
    view: TaskView = get_result.structured_content
    assert view.get("ref") == task["ref"]
    assert view.get("title") == title
    assert view.get("description") == description


@pytest.mark.anyio
async def test_create_task_invalid_board(mcp_client: MCPClient) -> None:
    title = "Test Task"
    description = "A test task"
    board = "Fake-Board"
    create_result = await mcp_client.call_tool(
        "create_task_tool", {"title": title, "description": description, "board": board}
    )
    assert create_result.is_error, create_result.content
    assert str(BoardNotFoundError(board)) in create_result.content[0].text


@pytest.mark.anyio
async def test_create_task_invalid_status(mcp_client: MCPClient) -> None:
    title = "Test Task"
    description = "A test task"
    status = "invalid"
    create_result = await mcp_client.call_tool(
        "create_task_tool", {"title": title, "description": description, "status": status}
    )
    assert create_result.is_error
    assert str(InvalidTaskStatusError(status)) in create_result.content[0].text


@pytest.mark.anyio
async def test_create_task_invalid_priority(mcp_client: MCPClient) -> None:
    title = "Test Task"
    description = "A test task"
    priority = "invalid"
    create_result = await mcp_client.call_tool(
        "create_task_tool", {"title": title, "description": description, "priority": priority}
    )
    assert create_result.is_error
    assert str(InvalidTaskPriorityError(priority)) in create_result.content[0].text


@pytest.mark.anyio
async def test_create_task_invalid_due_date(mcp_client: MCPClient):
    title = "Test Task"
    description = "A test task"
    due_date = "soon"
    create_result = await mcp_client.call_tool(
        "create_task_tool", {"title": title, "description": description, "due_date": due_date}
    )
    assert create_result.is_error
    assert str(InvalidTaskDueDateError(due_date)) in create_result.content[0].text


@pytest.mark.anyio
async def test_create_full_task(mcp_client: MCPClient):
    call_args = {
        "title": "Test Task",
        "description": "Test task description",
        "board": "Tasks",
        "status": Task.Status.IN_PROGRESS.value,
        "priority": Task.Priority.HIGH.value,
        "assignee": "ahoyt41",
        "due_duate": (datetime.now() + timedelta(days=5)).isoformat(),
        "tags": '("foo"="bar")',
        "points": 3,
    }
    create_result = await mcp_client.call_tool("create_task_tool", call_args)
    assert not create_result.is_error, create_result.content
    assert create_result.structured_content
    task: CreateTaskToolResult = create_result.structured_content

    get_result = await mcp_client.call_tool("get_task_tool", {"task_ref": task["ref"]})
    assert not get_result.is_error
    assert get_result.structured_content
    view: TaskView = get_result.structured_content

    assert view.get("assignee") == call_args["assignee"]
    assert view.get("description") == call_args["description"]
    assert view.get("status") == call_args["status"]
    assert view.get("priority") == call_args["priority"]
    assert view.get("title") == call_args["title"]
    assert view.get("ref") == task["ref"]