"""Unit tests for the tasks tool (list/check/wait/cancel of background jobs)."""

import asyncio

import pytest

from navi.core import tasks as tasks_mod
from navi.core.tasks import TaskManager
from navi.tools._internal.base import ToolContext, ToolResult
from navi.tools.tasks import TasksTool


@pytest.fixture(autouse=True)
def fresh_manager(monkeypatch):
    manager = TaskManager()
    monkeypatch.setattr(tasks_mod, "_manager", manager)
    yield manager
    for job in list(manager._jobs.values()):
        if job.task is not None and not job.task.done():
            job.task.cancel()
    if manager._sweeper_task is not None and not manager._sweeper_task.done():
        manager._sweeper_task.cancel()


def make_ctx(session_id="s1"):
    return ToolContext(
        session_id=session_id, event_sink=None, stop_event=None, model=None,
        user_id=None, user_role="admin", user_info=None, cwd=None,
    )


async def submit_ok(manager, session_id="s1", output="done", delay=0.0):
    async def factory(bg_ctx):
        if delay:
            await asyncio.sleep(delay)
        return ToolResult(success=True, output=output)

    return manager.submit(session_id, "code_exec", {"code": "x"}, factory,
                          make_ctx(session_id))


class TestList:
    async def test_empty(self):
        result = await TasksTool().execute({"action": "list"}, ctx=make_ctx())
        assert result.success
        assert "No background tasks" in result.output

    async def test_lists_session_jobs_only(self, fresh_manager):
        await submit_ok(fresh_manager, "s1")
        await submit_ok(fresh_manager, "s2")
        result = await TasksTool().execute({"action": "list"}, ctx=make_ctx("s1"))
        assert result.success
        assert "code_exec" in result.output

    async def test_list_shows_finished_preview(self, fresh_manager):
        job = await submit_ok(fresh_manager, "s1", output="42 files")
        await job.done.wait()
        result = await TasksTool().execute({"action": "list"}, ctx=make_ctx())
        assert "completed" in result.output
        assert "42 files" in result.output


class TestCheck:
    async def test_check_finished_shows_result(self, fresh_manager):
        job = await submit_ok(fresh_manager, output="the answer")
        await job.done.wait()
        result = await TasksTool().execute(
            {"action": "check", "task_id": job.task_id}, ctx=make_ctx())
        assert result.success
        assert "completed" in result.output
        assert "the answer" in result.output

    async def test_check_running_shows_progress_placeholder(self, fresh_manager):
        job = await submit_ok(fresh_manager, delay=5)
        result = await TasksTool().execute(
            {"action": "check", "task_id": job.task_id}, ctx=make_ctx())
        assert "running" in result.output
        fresh_manager.cancel(job)

    async def test_check_unknown_task(self):
        result = await TasksTool().execute(
            {"action": "check", "task_id": "bt-nope"}, ctx=make_ctx())
        assert not result.success
        assert result.error == "task_not_found"

    async def test_task_id_required(self):
        result = await TasksTool().execute({"action": "check"}, ctx=make_ctx())
        assert not result.success

    async def test_check_is_session_scoped(self, fresh_manager):
        job = await submit_ok(fresh_manager, session_id="other")
        result = await TasksTool().execute(
            {"action": "check", "task_id": job.task_id}, ctx=make_ctx("s1"))
        assert result.error == "task_not_found"


class TestWait:
    async def test_wait_returns_result(self, fresh_manager):
        job = await submit_ok(fresh_manager, output="late result", delay=0.05)
        result = await TasksTool().execute(
            {"action": "wait", "task_id": job.task_id}, ctx=make_ctx())
        assert result.success
        assert "late result" in result.output

    async def test_wait_timeout(self, fresh_manager):
        job = await submit_ok(fresh_manager, delay=30)
        result = await TasksTool().execute(
            {"action": "wait", "task_id": job.task_id, "timeout": 0.05},
            ctx=make_ctx())
        assert not result.success
        assert result.error == "wait_timeout"
        assert "still running" in result.output
        fresh_manager.cancel(job)

    async def test_wait_timeout_capped_at_120(self, fresh_manager):
        job = await submit_ok(fresh_manager, delay=0.01)
        # timeout above the cap must not blow up; job finishes fast anyway
        result = await TasksTool().execute(
            {"action": "wait", "task_id": job.task_id, "timeout": 9999},
            ctx=make_ctx())
        assert result.success


class TestCancel:
    async def test_cancel_running(self, fresh_manager):
        job = await submit_ok(fresh_manager, delay=30)
        result = await TasksTool().execute(
            {"action": "cancel", "task_id": job.task_id}, ctx=make_ctx())
        assert result.success
        await job.done.wait()
        assert job.status == "cancelled"

    async def test_cancel_finished(self, fresh_manager):
        job = await submit_ok(fresh_manager)
        await job.done.wait()
        result = await TasksTool().execute(
            {"action": "cancel", "task_id": job.task_id}, ctx=make_ctx())
        assert not result.success
        assert result.error == "not_running"


class TestFallbacks:
    async def test_session_falls_back_to_contextvar(self, fresh_manager, monkeypatch):
        from navi.tools._internal.base import current_session_id

        job = await submit_ok(fresh_manager, "ctx-session")
        token = current_session_id.set("ctx-session")
        try:
            await asyncio.sleep(0)  # let the background job run
            result = await TasksTool().execute(
                {"action": "check", "task_id": job.task_id}, ctx=None)
        finally:
            current_session_id.reset(token)
        assert result.success
        assert "completed" in result.output