"""Kanban dashboard plugin: the board's done column is completion history.

``GET /board`` buckets one ``list_tasks()`` fetch, so every column inherits
the shared ``priority DESC, created_at ASC`` order — which says nothing
about when finished work finished. These pin the done column to
newest-completed-first while a queue lane keeps the FIFO default.
"""

from __future__ import annotations

import importlib.util
import sys
from pathlib import Path

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient

from hermes_cli import kanban_db as kb
from hermes_cli import kanban_db_connect as kbc


def _load_plugin_router():
    repo_root = Path(__file__).resolve().parents[2]
    plugin_file = repo_root / "plugins" / "kanban" / "dashboard" / "plugin_api.py"
    spec = importlib.util.spec_from_file_location("hermes_kanban_plugin_done_order_test", plugin_file)
    mod = importlib.util.module_from_spec(spec)
    sys.modules[spec.name] = mod
    spec.loader.exec_module(mod)
    return mod.router


@pytest.fixture
def client(tmp_path, monkeypatch):
    home = tmp_path / ".hermes"
    home.mkdir()
    monkeypatch.setenv("HERMES_HOME", str(home))
    monkeypatch.setattr(Path, "home", lambda: tmp_path)
    kb.init_db()

    app = FastAPI()
    app.include_router(_load_plugin_router(), prefix="/api/plugins/kanban")
    return TestClient(app)


def _column(client, name):
    board = client.get("/api/plugins/kanban/board").json()
    return next(c["tasks"] for c in board["columns"] if c["name"] == name)


def _finish(client, task_id):
    r = client.patch(
        f"/api/plugins/kanban/tasks/{task_id}",
        json={"status": "done", "result": "done", "summary": "done"},
    )
    assert r.status_code == 200, r.text


def test_done_column_orders_by_completion_time_not_creation(client):
    first = client.post("/api/plugins/kanban/tasks", json={"title": "created first"}).json()["task"]
    second = client.post("/api/plugins/kanban/tasks", json={"title": "created second"}).json()["task"]
    _finish(client, first["id"])
    _finish(client, second["id"])

    # complete_task stamps one wall clock for both, so pin distinct
    # timestamps: the card created first also finished last.
    with kbc.connect() as conn:
        conn.execute("UPDATE tasks SET completed_at = 2000 WHERE id = ?", (first["id"],))
        conn.execute("UPDATE tasks SET completed_at = 1000 WHERE id = ?", (second["id"],))
        conn.commit()

    done = _column(client, "done")
    assert [t["id"] for t in done] == [first["id"], second["id"]]


def test_done_column_survives_non_integer_completed_at(client):
    """A writer that bypassed complete_task can leave a non-integer completed_at
    behind. The board must render (no 500 from negating a string), order a
    parseable ISO stamp by its parsed time, and park garbage last with None."""
    early = client.post("/api/plugins/kanban/tasks", json={"title": "epoch int"}).json()["task"]
    iso = client.post("/api/plugins/kanban/tasks", json={"title": "iso string"}).json()["task"]
    garbage = client.post("/api/plugins/kanban/tasks", json={"title": "garbage"}).json()["task"]
    _finish(client, early["id"])
    _finish(client, iso["id"])
    _finish(client, garbage["id"])

    with kbc.connect() as conn:
        conn.execute("UPDATE tasks SET completed_at = 1000 WHERE id = ?", (early["id"],))
        conn.execute("UPDATE tasks SET completed_at = '2026-09-30T14:22:11', created_at = '2026-09-30T14:00:00', "
                     "started_at = '2026-09-30T14:10:00' WHERE id = ?", (iso["id"],))
        conn.execute("UPDATE tasks SET completed_at = 'not-a-timestamp' WHERE id = ?", (garbage["id"],))
        conn.commit()

    r = client.get("/api/plugins/kanban/board")
    assert r.status_code == 200, r.text
    done = next(c["tasks"] for c in r.json()["columns"] if c["name"] == "done")
    # The ISO stamp parses to a present-day epoch far above 1000; garbage
    # coerces to None and sorts last.
    assert [t["id"] for t in done] == [iso["id"], early["id"], garbage["id"]]
    # Served coerced: the client gets an epoch int, never the raw text.
    assert all(isinstance(done[0][k], int) for k in ("created_at", "started_at", "completed_at"))
    assert done[2]["completed_at"] is None


def test_queue_lane_keeps_fifo_default(client):
    a = client.post("/api/plugins/kanban/tasks", json={"title": "a"}).json()["task"]
    b = client.post("/api/plugins/kanban/tasks", json={"title": "b"}).json()["task"]
    # Same-second creations tie on the shared order; pin explicit ages.
    with kbc.connect() as conn:
        conn.execute("UPDATE tasks SET created_at = 200 WHERE id = ?", (a["id"],))
        conn.execute("UPDATE tasks SET created_at = 100 WHERE id = ?", (b["id"],))
        conn.commit()

    ready = _column(client, "ready")  # no parents -> both land ready (dispatch order)
    assert [t["id"] for t in ready] == [b["id"], a["id"]]
