from unittest.mock import AsyncMock, patch

import pytest
from tests.helpers import fake_note


pytestmark = pytest.mark.usefixtures("_bind_user")


@pytest.mark.asyncio
async def test_create_task_passes_kind():
    # kind=plan is retired (plans are milestones); 'issue' exercises passthrough.
    mock = AsyncMock(return_value=fake_note(task_kind="issue"))
    with patch("scribe.mcp.tools.tasks.notes_svc.create_note", mock):
        from scribe.mcp.tools.tasks import create_task
        await create_task(title="P", kind="issue")
    assert mock.call_args.kwargs["task_kind"] == "issue"


@pytest.mark.asyncio
async def test_list_tasks_passes_kind_filter():
    mock = AsyncMock(return_value=([], 0))
    with patch("scribe.mcp.tools.tasks.notes_svc.list_notes", mock):
        from scribe.mcp.tools.tasks import list_tasks
        await list_tasks(kind="plan")
    assert mock.call_args.kwargs["task_kind"] == "plan"


@pytest.mark.asyncio
async def test_list_tasks_kind_empty_means_no_filter():
    mock = AsyncMock(return_value=([], 0))
    with patch("scribe.mcp.tools.tasks.notes_svc.list_notes", mock):
        from scribe.mcp.tools.tasks import list_tasks
        await list_tasks()
    assert mock.call_args.kwargs["task_kind"] is None


@pytest.mark.asyncio
async def test_create_task_passes_spike():
    """The kind a failed rule-check asks for (milestone 312).

    Time-boxed, and its output is knowledge rather than a change — filing one
    as `work` makes a finished investigation look like an abandoned change.
    """
    mock = AsyncMock(return_value=fake_note(task_kind="spike"))
    with patch("scribe.mcp.tools.tasks.notes_svc.create_note", mock):
        from scribe.mcp.tools.tasks import create_task
        await create_task(title="Can the runner be given a bash shell?", kind="spike")
    assert mock.call_args.kwargs["task_kind"] == "spike"


@pytest.mark.asyncio
async def test_update_task_can_re_file_a_kind():
    """A task's kind must be CORRECTABLE, not write-once (#3129).

    What a piece of work turns out to be is often clear only once it is under
    way. Before this, `update_task` had no `kind` parameter at all and the
    REST route dropped the field the editor was already sending — the save
    reported success and reverted on reload.
    """
    mock = AsyncMock(return_value=fake_note(task_kind="spike"))
    with patch("scribe.mcp.tools.tasks.notes_svc.update_note", mock):
        from scribe.mcp.tools.tasks import update_task
        await update_task(task_id=1, kind="spike")
    assert mock.call_args.kwargs["task_kind"] == "spike"


@pytest.mark.asyncio
async def test_update_task_leaves_kind_alone_when_not_given():
    """"" means leave unchanged, as it does for every other field here."""
    mock = AsyncMock(return_value=fake_note())
    with patch("scribe.mcp.tools.tasks.notes_svc.update_note", mock):
        from scribe.mcp.tools.tasks import update_task
        await update_task(task_id=1, title="renamed")
    assert "task_kind" not in mock.call_args.kwargs


@pytest.mark.asyncio
async def test_minting_a_plan_is_refused_at_both_doors():
    """`plan` is a valid stored value and NOT a mintable one.

    Historical plan-tasks carry it and must stay writable, so the column
    keeps accepting it — but plans became milestones in 0066, so no door
    hands out a new one. The error names start_planning rather than just
    refusing, because a caller reaching for kind='plan' wants a plan.
    """
    from scribe.services.notes import minted_kind

    with pytest.raises(ValueError, match="start_planning"):
        minted_kind("plan")


@pytest.mark.asyncio
async def test_an_unknown_kind_raises_rather_than_defaulting():
    """A silently-corrected kind is the defect this whole fix exists to end."""
    from scribe.services.notes import minted_kind

    with pytest.raises(ValueError, match="kind must be one of"):
        minted_kind("investigation")
