"""
Tests for Phase 51: Priority Task Queue with Ordered Execution.
"""

import json
import time
from datetime import datetime, timezone, timedelta

import pytest


@pytest.fixture(autouse=True)
def _isolate(tmp_path, monkeypatch):
    """Redirect all task queue storage to tmp_path."""
    queue_dir = tmp_path / "queue"
    queue_dir.mkdir(parents=True, exist_ok=True)

    import brynq.server as srv
    monkeypatch.setattr(srv, "BROKER_DIR", tmp_path)

    import brynq.task_queue as tq
    monkeypatch.setattr(tq, "QUEUE_DIR", queue_dir)
    monkeypatch.setattr(tq, "TASKS_FILE", queue_dir / "tasks.json")
    monkeypatch.setattr(tq, "HISTORY_FILE", queue_dir / "history.jsonl")


# ── Enqueue ──────────────────────────────────────────────────────────────

class TestEnqueue:
    def test_enqueue_basic(self):
        from brynq.task_queue import enqueue
        task = enqueue("t1")
        assert task["task_id"] == "t1"
        assert task["priority"] == "normal"
        assert task["status"] == "queued"

    def test_enqueue_with_priority(self):
        from brynq.task_queue import enqueue
        task = enqueue("t1", priority="critical")
        assert task["priority"] == "critical"

    def test_enqueue_with_deadline(self):
        from brynq.task_queue import enqueue
        dl = datetime.now(timezone.utc).isoformat()
        task = enqueue("t1", deadline=dl)
        assert task["deadline"] == dl

    def test_enqueue_with_tags(self):
        from brynq.task_queue import enqueue
        task = enqueue("t1", tags=["python", "backend"])
        assert task["tags"] == ["python", "backend"]

    def test_enqueue_invalid_priority(self):
        from brynq.task_queue import enqueue
        with pytest.raises(ValueError, match="Invalid priority"):
            enqueue("t1", priority="ultra")

    def test_enqueue_duplicate_rejected(self):
        from brynq.task_queue import enqueue
        enqueue("t1")
        with pytest.raises(ValueError, match="already in the queue"):
            enqueue("t1")

    def test_enqueue_creates_timestamp(self):
        from brynq.task_queue import enqueue
        task = enqueue("t1")
        assert "enqueued_at" in task
        # Should be parseable ISO timestamp
        datetime.fromisoformat(task["enqueued_at"])


# ── Dequeue ──────────────────────────────────────────────────────────────

class TestDequeue:
    def test_dequeue_empty_returns_none(self):
        from brynq.task_queue import dequeue
        assert dequeue() is None

    def test_dequeue_returns_highest_priority(self):
        from brynq.task_queue import enqueue, dequeue
        enqueue("low_task", priority="low")
        enqueue("critical_task", priority="critical")
        enqueue("normal_task", priority="normal")
        task = dequeue()
        assert task["task_id"] == "critical_task"

    def test_dequeue_removes_from_queue(self):
        from brynq.task_queue import enqueue, dequeue, peek
        enqueue("t1")
        dequeue()
        assert len(peek()) == 0

    def test_dequeue_with_agent_id(self):
        from brynq.task_queue import enqueue, dequeue
        enqueue("t1")
        task = dequeue(agent_id="agent-1")
        assert task["agent_id"] == "agent-1"

    def test_dequeue_filtered_by_skills(self):
        from brynq.task_queue import enqueue, dequeue
        enqueue("python_task", tags=["python"])
        enqueue("rust_task", tags=["rust"])
        task = dequeue(skills=["rust"])
        assert task["task_id"] == "rust_task"

    def test_dequeue_no_skill_match_returns_none(self):
        from brynq.task_queue import enqueue, dequeue
        enqueue("python_task", tags=["python"])
        result = dequeue(skills=["java"])
        assert result is None

    def test_dequeue_sets_status_dequeued(self):
        from brynq.task_queue import enqueue, dequeue
        enqueue("t1")
        task = dequeue()
        assert task["status"] == "dequeued"
        assert "dequeued_at" in task


# ── Peek ─────────────────────────────────────────────────────────────────

class TestPeek:
    def test_peek_empty(self):
        from brynq.task_queue import peek
        assert peek() == []

    def test_peek_does_not_remove(self):
        from brynq.task_queue import enqueue, peek
        enqueue("t1")
        peek()
        assert len(peek()) == 1

    def test_peek_respects_limit(self):
        from brynq.task_queue import enqueue, peek
        for i in range(10):
            enqueue(f"t{i}")
        assert len(peek(3)) == 3

    def test_peek_priority_order(self):
        from brynq.task_queue import enqueue, peek
        enqueue("low", priority="low")
        enqueue("high", priority="high")
        enqueue("crit", priority="critical")
        top = peek(3)
        assert top[0]["task_id"] == "crit"
        assert top[1]["task_id"] == "high"
        assert top[2]["task_id"] == "low"


# ── Requeue ──────────────────────────────────────────────────────────────

class TestRequeue:
    def test_requeue_after_dequeue(self):
        from brynq.task_queue import enqueue, dequeue, requeue, peek
        enqueue("t1")
        dequeue()
        requeue("t1")
        top = peek()
        assert len(top) == 1
        assert top[0]["task_id"] == "t1"

    def test_requeue_with_new_priority(self):
        from brynq.task_queue import enqueue, dequeue, requeue, peek
        enqueue("t1", priority="low")
        dequeue()
        requeue("t1", new_priority="critical")
        top = peek()
        assert top[0]["priority"] == "critical"

    def test_requeue_in_queue_updates_priority(self):
        from brynq.task_queue import enqueue, requeue, peek
        enqueue("t1", priority="low")
        requeue("t1", new_priority="high")
        top = peek()
        assert top[0]["priority"] == "high"

    def test_requeue_invalid_priority(self):
        from brynq.task_queue import requeue
        with pytest.raises(ValueError, match="Invalid priority"):
            requeue("t1", new_priority="ultra")


# ── Stats ────────────────────────────────────────────────────────────────

class TestStats:
    def test_stats_empty(self):
        from brynq.task_queue import get_queue_stats
        stats = get_queue_stats()
        assert stats["total"] == 0

    def test_stats_counts_by_priority(self):
        from brynq.task_queue import enqueue, get_queue_stats
        enqueue("t1", priority="critical")
        enqueue("t2", priority="critical")
        enqueue("t3", priority="low")
        stats = get_queue_stats()
        assert stats["total"] == 3
        assert stats["counts_by_priority"]["critical"] == 2
        assert stats["counts_by_priority"]["low"] == 1

    def test_stats_avg_wait_time(self):
        from brynq.task_queue import enqueue, get_queue_stats
        enqueue("t1")
        stats = get_queue_stats()
        assert stats["avg_wait_seconds"] >= 0

    def test_stats_overdue_count(self):
        from brynq.task_queue import enqueue, get_queue_stats
        past = (datetime.now(timezone.utc) - timedelta(hours=1)).isoformat()
        enqueue("t1", deadline=past)
        enqueue("t2")  # no deadline
        stats = get_queue_stats()
        assert stats["overdue_count"] == 1


# ── Overdue ──────────────────────────────────────────────────────────────

class TestOverdue:
    def test_overdue_past_deadline(self):
        from brynq.task_queue import enqueue, get_overdue
        past = (datetime.now(timezone.utc) - timedelta(hours=1)).isoformat()
        enqueue("t1", deadline=past)
        overdue = get_overdue()
        assert len(overdue) == 1
        assert overdue[0]["task_id"] == "t1"

    def test_overdue_none_when_all_fresh(self):
        from brynq.task_queue import enqueue, get_overdue
        future = (datetime.now(timezone.utc) + timedelta(hours=1)).isoformat()
        enqueue("t1", deadline=future)
        overdue = get_overdue(threshold_minutes=9999)
        assert len(overdue) == 0


# ── Clear Completed ──────────────────────────────────────────────────────

class TestClearCompleted:
    def test_clear_completed(self):
        from brynq.task_queue import enqueue, dequeue, clear_completed, peek, _read_queue, _save_queue
        enqueue("t1")
        enqueue("t2")
        # Manually mark t2 as completed in queue (simulate)
        queue = _read_queue()
        for t in queue:
            if t["task_id"] == "t2":
                t["status"] = "completed"
        _save_queue(queue)
        removed = clear_completed()
        assert removed == 1
        remaining = peek()
        assert len(remaining) == 1
        assert remaining[0]["task_id"] == "t1"

    def test_clear_nothing_to_clear(self):
        from brynq.task_queue import enqueue, clear_completed
        enqueue("t1")
        removed = clear_completed()
        assert removed == 0
