"""Unit tests for the background task queue system."""

from __future__ import annotations

import pytest
from pathlib import Path

from vastian.tasks.queue import TaskQueue, BackgroundTask, TaskStatus


@pytest.fixture
def tq(temp_dir: Path) -> TaskQueue:
    q = TaskQueue(temp_dir / "test_tasks.db", state_dir=temp_dir)
    q.initialize()
    yield q
    q.close()


class TestTaskQueue:
    """Unit tests for TaskQueue CRUD operations."""

    def test_initialize_creates_db(self, temp_dir: Path) -> None:
        q = TaskQueue(temp_dir / "init_test.db")
        q.initialize()
        assert (temp_dir / "init_test.db").exists()
        q.close()

    def test_enqueue_creates_task(self, tq: TaskQueue) -> None:
        task = tq.enqueue("delegation", "liam", "Test task", {"key": "val"})
        assert isinstance(task, BackgroundTask)
        assert task.task_type == "delegation"
        assert task.agent_id == "liam"
        assert task.status == TaskStatus.QUEUED
        assert task.payload == {"key": "val"}

    def test_enqueue_generates_unique_ids(self, tq: TaskQueue) -> None:
        t1 = tq.enqueue("delegation", "liam", "Task 1")
        t2 = tq.enqueue("delegation", "liam", "Task 2")
        assert t1.task_id != t2.task_id

    def test_get_pending(self, tq: TaskQueue) -> None:
        tq.enqueue("delegation", "liam", "Pending task")
        pending = tq.get_pending()
        assert len(pending) == 1
        assert pending[0].description == "Pending task"

    def test_mark_running(self, tq: TaskQueue) -> None:
        task = tq.enqueue("delegation", "liam", "To run")
        tq.mark_running(task.task_id)
        running = tq.get_running()
        assert len(running) == 1
        assert running[0].task_id == task.task_id

    def test_mark_completed(self, tq: TaskQueue) -> None:
        task = tq.enqueue("delegation", "liam", "To complete")
        tq.mark_running(task.task_id)
        tq.mark_completed(task.task_id, "Done successfully")
        running = tq.get_running()
        assert len(running) == 0
        recent = tq.get_recent(5)
        completed = [t for t in recent if t.status == TaskStatus.COMPLETED]
        assert len(completed) == 1
        assert completed[0].result == "Done successfully"

    def test_mark_failed(self, tq: TaskQueue) -> None:
        task = tq.enqueue("delegation", "liam", "To fail")
        tq.mark_running(task.task_id)
        tq.mark_failed(task.task_id, "Something went wrong")
        recent = tq.get_recent(5)
        failed = [t for t in recent if t.status == TaskStatus.FAILED]
        assert len(failed) == 1
        assert "Something went wrong" in (failed[0].result or "")

    def test_get_recent_respects_limit(self, tq: TaskQueue) -> None:
        for i in range(10):
            tq.enqueue("delegation", "liam", f"Task {i}")
        recent = tq.get_recent(5)
        assert len(recent) == 5

    def test_add_notification(self, tq: TaskQueue) -> None:
        tq.add_notification("charles", "Inbox pickup: update.txt", "info")
        notifs = tq.get_notifications(limit=5)
        assert len(notifs) == 1
        assert notifs[0]["message"] == "Inbox pickup: update.txt"


class TestDelegationReports:
    """Unit tests for the delegation report storage."""

    def test_save_and_get_report(self, tq: TaskQueue) -> None:
        task = tq.enqueue("batch_delegation", "liam", "Batch test")
        report_id = tq.save_delegation_report(
            parent_task_id=task.task_id,
            agent_id="finley",
            description="Update financial projections",
            full_result="I updated the financial projections document...",
            tools_used=["write_document", "read_document"],
            documents_written=["finley/financial-projections.md"],
        )
        assert report_id

        reports = tq.get_reports_for_task(task.task_id)
        assert len(reports) == 1
        rpt = reports[0]
        assert rpt["agent_id"] == "finley"
        assert rpt["full_result"].startswith("I updated")
        assert "write_document" in rpt["tools_used"]
        assert "finley/financial-projections.md" in rpt["documents_written"]

    def test_get_reports_for_agent(self, tq: TaskQueue) -> None:
        task = tq.enqueue("batch_delegation", "liam", "Batch")
        tq.save_delegation_report(task.task_id, "henry", "Review arch", "Result 1")
        tq.save_delegation_report(task.task_id, "henry", "Code review", "Result 2")
        tq.save_delegation_report(task.task_id, "finley", "Budget", "Result 3")

        henry_reports = tq.get_reports_for_agent("henry")
        assert len(henry_reports) == 2

        finley_reports = tq.get_reports_for_agent("finley")
        assert len(finley_reports) == 1

    def test_get_single_report(self, tq: TaskQueue) -> None:
        task = tq.enqueue("delegation", "liam", "Test")
        report_id = tq.save_delegation_report(
            task.task_id,
            "theo",
            "Quality review",
            "All good",
        )
        rpt = tq.get_report(report_id)
        assert rpt is not None
        assert rpt["agent_id"] == "theo"

    def test_get_nonexistent_report_returns_none(self, tq: TaskQueue) -> None:
        assert tq.get_report("nonexistent") is None

    def test_multiple_tasks_separate_reports(self, tq: TaskQueue) -> None:
        t1 = tq.enqueue("batch_delegation", "liam", "Batch 1")
        t2 = tq.enqueue("batch_delegation", "liam", "Batch 2")
        tq.save_delegation_report(t1.task_id, "finley", "T1 work", "R1")
        tq.save_delegation_report(t2.task_id, "finley", "T2 work", "R2")

        r1 = tq.get_reports_for_task(t1.task_id)
        r2 = tq.get_reports_for_task(t2.task_id)
        assert len(r1) == 1
        assert len(r2) == 1
        assert r1[0]["full_result"] == "R1"
        assert r2[0]["full_result"] == "R2"


class TestKanbanWave5:
    """Wave 5: board, ticket_id, domain, tags, sprints, blocked_by."""

    def test_suggest_creates_ticket_id(self, tq: TaskQueue) -> None:
        kid = tq.suggest_kanban_task(
            suggested_by="orion",
            title="Test",
            description="Desc",
            board="user",
        )
        task = tq.get_kanban_task(kid)
        assert task is not None
        assert task["ticket_id"].startswith("USR-")
        assert task["board"] == "user"

    def test_suggest_default_board_is_agent(self, tq: TaskQueue) -> None:
        kid = tq.suggest_kanban_task(
            suggested_by="theo",
            title="Fix",
            description="Desc",
        )
        task = tq.get_kanban_task(kid)
        assert task["board"] == "agent"
        assert task["ticket_id"].startswith("CYC-")

    def test_all_board_prefixes(self, tq: TaskQueue) -> None:
        for board, prefix in TaskQueue.BOARD_PREFIXES.items():
            kid = tq.suggest_kanban_task(
                suggested_by="test",
                title=f"{board} task",
                description="D",
                board=board,
            )
            task = tq.get_kanban_task(kid)
            assert task["ticket_id"].startswith(prefix), f"Board {board} should use prefix {prefix}"

    def test_suggest_with_tags(self, tq: TaskQueue) -> None:
        kid = tq.suggest_kanban_task(
            suggested_by="liam",
            title="Tagged",
            description="D",
            tags=["sprint-1", "urgent"],
        )
        task = tq.get_kanban_task(kid)
        assert task["tags"] == ["sprint-1", "urgent"]

    def test_suggest_with_domain(self, tq: TaskQueue) -> None:
        kid = tq.suggest_kanban_task(
            suggested_by="liam",
            title="Domain task",
            description="D",
            domain="side_venture",
        )
        task = tq.get_kanban_task(kid)
        assert task["domain"] == "side_venture"

    def test_suggest_with_blocked_by(self, tq: TaskQueue) -> None:
        k1 = tq.suggest_kanban_task("a", "First", "D")
        k2 = tq.suggest_kanban_task(
            "a",
            "Second",
            "D",
            blocked_by=[k1],
        )
        task = tq.get_kanban_task(k2)
        assert k1 in task["blocked_by"]

    def test_suggest_without_approval(self, tq: TaskQueue) -> None:
        kid = tq.suggest_kanban_task(
            suggested_by="liam",
            title="Auto",
            description="D",
            requires_approval=False,
        )
        task = tq.get_kanban_task(kid)
        assert task["status"] == "suggested"

    def test_filter_by_board(self, tq: TaskQueue) -> None:
        tq.suggest_kanban_task("a", "User T", "D", board="user")
        tq.suggest_kanban_task("a", "Agent T", "D", board="agent")
        tq.suggest_kanban_task("a", "Comms T", "D", board="comms")

        user_tasks = tq.get_kanban_tasks(board="user")
        assert all(t["board"] == "user" for t in user_tasks)
        assert len(user_tasks) == 1

        agent_tasks = tq.get_kanban_tasks(board="agent")
        assert len(agent_tasks) == 1

    def test_filter_by_domain(self, tq: TaskQueue) -> None:
        tq.suggest_kanban_task("a", "Default", "D")
        tq.suggest_kanban_task("a", "Venture", "D", domain="side_venture")

        v_tasks = tq.get_kanban_tasks(domain="side_venture")
        assert len(v_tasks) == 1
        assert v_tasks[0]["domain"] == "side_venture"

    def test_sprint_points(self, tq: TaskQueue) -> None:
        kid = tq.suggest_kanban_task(
            "a",
            "Pointed",
            "D",
            sprint_points=5,
        )
        task = tq.get_kanban_task(kid)
        assert task["sprint_points"] == 5

    def test_default_domain_is_vastian(self, tq: TaskQueue) -> None:
        kid = tq.suggest_kanban_task("a", "T", "D")
        task = tq.get_kanban_task(kid)
        assert task["domain"] == "vastian"

    def test_backward_compat_legacy_tasks(self, tq: TaskQueue) -> None:
        """Legacy tasks (no board/ticket_id) should still be queryable."""
        # Simulate a legacy record by inserting directly
        from vastian.storage.file_store import FileStore

        legacy = {
            "kanban_id": "legacy123",
            "suggested_by": "charles",
            "title": "Legacy task",
            "description": "Old format",
            "category": "improvement",
            "priority": "normal",
            "status": "suggested",
            "assigned_to": "",
            "result": "",
            "user_notes": "",
            "created_at": "2025-01-01T00:00:00+00:00",
        }
        tq._kanban.insert(legacy)
        task = tq.get_kanban_task("legacy123")
        assert task is not None
        # Board filter: legacy tasks default to "agent" in filter
        all_tasks = tq.get_kanban_tasks()
        assert any(t["kanban_id"] == "legacy123" for t in all_tasks)


class TestSprintManagement:
    """Wave 5: sprint CRUD and ticket assignment."""

    def test_create_sprint(self, tq: TaskQueue) -> None:
        sid = tq.create_sprint("Sprint 1", goal="Ship Wave 5")
        assert sid.startswith("sprint-")
        sprints = tq.get_sprints()
        assert len(sprints) == 1
        assert sprints[0]["name"] == "Sprint 1"
        assert sprints[0]["status"] == "active"

    def test_close_sprint(self, tq: TaskQueue) -> None:
        sid = tq.create_sprint("Sprint 2")
        assert tq.close_sprint(sid)
        sprints = tq.get_sprints(status="active")
        assert len(sprints) == 0
        closed = tq.get_sprints(status="closed")
        assert len(closed) == 1

    def test_close_nonexistent_sprint(self, tq: TaskQueue) -> None:
        assert not tq.close_sprint("sprint-nonexistent")

    def test_assign_ticket_to_sprint(self, tq: TaskQueue) -> None:
        sid = tq.create_sprint("Sprint 3")
        kid = tq.suggest_kanban_task("a", "In sprint", "D")
        assert tq.assign_ticket_to_sprint(kid, sid)

        # Check sprint ticket_ids
        sprints = tq.get_sprints()
        assert kid in sprints[0]["ticket_ids"]

        # Check kanban task sprint_id
        task = tq.get_kanban_task(kid)
        assert task["sprint_id"] == sid

    def test_filter_by_sprint(self, tq: TaskQueue) -> None:
        sid = tq.create_sprint("Sprint 4")
        k1 = tq.suggest_kanban_task("a", "Sprinted", "D")
        k2 = tq.suggest_kanban_task("a", "Not sprinted", "D")
        tq.assign_ticket_to_sprint(k1, sid)

        sprint_tasks = tq.get_kanban_tasks(sprint_id=sid)
        assert len(sprint_tasks) == 1
        assert sprint_tasks[0]["kanban_id"] == k1

    def test_sprint_burndown(self, tq: TaskQueue) -> None:
        sid = tq.create_sprint("Sprint 5")
        k1 = tq.suggest_kanban_task(
            "a",
            "Task A",
            "D",
            sprint_points=3,
        )
        k2 = tq.suggest_kanban_task(
            "a",
            "Task B",
            "D",
            sprint_points=5,
        )
        tq.assign_ticket_to_sprint(k1, sid)
        tq.assign_ticket_to_sprint(k2, sid)

        burn = tq.get_sprint_burndown(sid)
        assert burn["total_tickets"] == 2
        assert burn["total_points"] == 8
        assert burn["done_points"] == 0

    def test_sprint_with_board(self, tq: TaskQueue) -> None:
        sid = tq.create_sprint("Sprint 6", board="user")
        sprints = tq.get_sprints()
        assert sprints[0]["board"] == "user"
