from __future__ import annotations

import asyncio
from pathlib import Path

import pytest

from aicd.bus.event_bus import AsyncEventBus
from aicd.config import PathPolicyConfig
from aicd.db.store import TaskStore
from aicd.tasks.main_inbox import main_inbox_id
from aicd.tasks.manager import TaskManager
from aicd.tasks.safe_ids import is_safe_task_id, validate_topic_slug
from aicd.tools.path_policy import PathPolicy
from aicd.tools.process_tool import ProcessTool


def test_task_messages_roundtrip(tmp_path: Path) -> None:
    async def _go() -> None:
        db = tmp_path / "t.db"
        store = TaskStore(db)
        conn = await store.connect()
        try:
            tid = await store.insert_task(
                conn,
                task_type="ai_agent",
                status="pending",
                parent_id=None,
                payload={"instruction": "x"},
            )
            mid = await store.insert_task_message(
                conn,
                to_task_id=tid,
                from_task_id="parent-1",
                kind="status",
                body={"pct": 50},
                thread_root_id="thr1",
            )
            rows = await store.list_task_messages(
                conn, to_task_id=tid, since_id=0, limit=10
            )
            assert len(rows) == 1
            assert rows[0].id == mid
            assert rows[0].kind == "status"
            assert rows[0].body == {"pct": 50}
            await store.mark_task_messages_read(conn, [mid])
            rows2 = await store.list_task_messages(
                conn, to_task_id=tid, since_id=0, limit=10
            )
            assert rows2[0].read_at is not None
        finally:
            await conn.close()

    asyncio.run(_go())


def test_patch_task_payload_children_progress_merge(tmp_path: Path) -> None:
    async def _go() -> None:
        db = tmp_path / "ct.db"
        store = TaskStore(db)
        conn = await store.connect()
        try:
            bus = AsyncEventBus()
            pol = PathPolicy(PathPolicyConfig(full_disk=True))
            pt = ProcessTool(pol, cwd=tmp_path)
            tm = TaskManager(
                store=store,
                conn=conn,
                bus=bus,
                tasks_root=tmp_path / "tr",
                process_tool=pt,
            )
            pid = await store.insert_task(
                conn,
                task_type="ai_agent",
                status="running",
                parent_id=None,
                payload={"instruction": "parent"},
            )
            cid = await store.insert_task(
                conn,
                task_type="ai_agent",
                status="running",
                parent_id=pid,
                payload={"instruction": "child"},
            )
            await tm.patch_task_payload(
                pid, {"children_progress": {cid: {"step_index": 1}}}
            )
            await tm.patch_task_payload(
                pid,
                {
                    "children_progress": {
                        cid: {"step_index": 2, "current_focus": "scan"},
                    }
                },
            )
            row = await store.get_task(conn, pid)
            ch = row.payload["children_progress"][cid]
            assert ch["step_index"] == 2
            assert ch["current_focus"] == "scan"
        finally:
            await conn.close()

    asyncio.run(_go())


def test_topic_slug_validation() -> None:
    assert validate_topic_slug(None) is None
    assert validate_topic_slug("My-Topic_2") == "my-topic_2"
    with pytest.raises(ValueError):
        validate_topic_slug("Bad/Slash")
    with pytest.raises(ValueError):
        validate_topic_slug("../x")


def test_safe_task_id() -> None:
    assert is_safe_task_id("abc-def_12")
    assert not is_safe_task_id("../x")
    assert not is_safe_task_id("")


def test_main_inbox_and_session_thread_tasks(tmp_path: Path) -> None:
    async def _go() -> None:
        db = tmp_path / "t.db"
        store = TaskStore(db)
        conn = await store.connect()
        try:
            sess = "chat-session-abc"
            tid = await store.insert_task(
                conn,
                task_type="ai_agent",
                status="completed",
                parent_id=None,
                payload={"thread_root_id": sess, "display_title": "Job"},
            )
            listed = await store.list_top_level_tasks_for_session_thread(
                conn, sess, limit=10
            )
            assert len(listed) == 1
            assert listed[0].id == tid
            inbox = main_inbox_id(sess)
            mid = await store.insert_task_message(
                conn,
                to_task_id=inbox,
                from_task_id=tid,
                kind="lifecycle",
                body={"phase": "completed", "child_task_id": tid},
                thread_root_id=sess,
            )
            rows = await store.list_task_messages(
                conn, to_task_id=inbox, since_id=0, limit=10
            )
            assert len(rows) == 1
            assert rows[0].id == mid
            assert rows[0].kind == "lifecycle"
        finally:
            await conn.close()

    asyncio.run(_go())


def test_main_inbox_unread_lifecycle_only_matches_host_dispatch(tmp_path: Path) -> None:
    """与 host_dispatch.main_inbox_continue 中 lifecycle_only 过滤一致。"""

    async def _go() -> None:
        db = tmp_path / "t.db"
        store = TaskStore(db)
        conn = await store.connect()
        try:
            sess = "chat-session-def"
            tid = await store.insert_task(
                conn,
                task_type="ai_agent",
                status="completed",
                parent_id=None,
                payload={"thread_root_id": sess},
            )
            inbox = main_inbox_id(sess)
            await store.insert_task_message(
                conn,
                to_task_id=inbox,
                from_task_id=tid,
                kind="status",
                body={"pct": 1},
                thread_root_id=sess,
            )
            await store.insert_task_message(
                conn,
                to_task_id=inbox,
                from_task_id=tid,
                kind="lifecycle",
                body={"phase": "completed"},
                thread_root_id=sess,
            )
            rows = await store.list_task_messages_unread(
                conn, to_task_id=inbox, limit=50
            )
            assert len(rows) == 2
            lifecycle_only = True
            if lifecycle_only and rows:
                rows = [r for r in rows if (r.kind or "") == "lifecycle"]
            assert len(rows) == 1
            assert rows[0].kind == "lifecycle"
        finally:
            await conn.close()

    asyncio.run(_go())
