"""Bounded task worker outcome tests."""

import asyncio
import uuid
from typing import cast

from app.tasks.repository import Task, TaskRepository
from app.tasks.worker import TaskWorker


class FakeRepository:
    def __init__(self, task: Task) -> None:
        self.task = task
        self.finishes: list[tuple[str, str | None]] = []

    async def get(self, _task_id: uuid.UUID) -> Task:
        return self.task

    async def finish(
        self,
        _task_id: uuid.UUID,
        _worker_id: str,
        *,
        status: str,
        result: dict[str, object] | None = None,
        error: str | None = None,
    ) -> None:
        del result
        self.finishes.append((status, error))


def make_task(*, task_type: str = "test", cancelled: bool = False) -> Task:
    return Task(uuid.uuid4(), "UPID:test", task_type, "running", {}, 0, cancelled, 1)


async def test_worker_persists_success_error_and_unsupported() -> None:
    task = make_task()
    repository = FakeRepository(task)

    async def success(_task: Task) -> dict[str, object]:
        return {"ok": True}

    worker = TaskWorker(cast(TaskRepository, repository), "worker", {"test": success})
    await worker._execute(task)
    assert repository.finishes == [("success", None)]

    unsupported = make_task(task_type="missing")
    repository.task = unsupported
    await worker._execute(unsupported)
    assert repository.finishes[-1] == ("error", "unsupported task type")

    async def failure(_task: Task) -> None:
        raise RuntimeError("private detail")

    failed = make_task()
    repository.task = failed
    worker.handlers["test"] = failure
    await worker._execute(failed)
    assert repository.finishes[-1] == ("error", "RuntimeError")


async def test_worker_honors_persisted_cancellation() -> None:
    task = make_task(cancelled=True)
    repository = FakeRepository(task)
    called = False

    async def handler(_task: Task) -> None:
        nonlocal called
        called = True

    worker = TaskWorker(cast(TaskRepository, repository), "worker", {"test": handler})
    await worker._execute(task)

    assert not called
    assert repository.finishes == [("cancelled", None)]


async def test_worker_retries_after_claim_failure() -> None:
    class RecoveringRepository:
        attempts = 0

        async def claim(self, _worker_id: str, _lease_seconds: float) -> None:
            self.attempts += 1
            if self.attempts == 1:
                raise RuntimeError("database schema is not ready")
            return None

    repository = RecoveringRepository()
    worker = TaskWorker(
        cast(TaskRepository, repository),
        "worker",
        {},
        poll_seconds=0.001,
    )
    running = asyncio.create_task(worker.run())
    for _ in range(200):
        if repository.attempts > 1:
            break
        await asyncio.sleep(0.01)
    worker.stop()
    await running

    assert repository.attempts > 1
