"""Plugin HTTP router.

Mounted by ``PluginManager`` at ``/api/plugins/onenote-import/``.

Routes:
    POST   /run                    — kick off an import asynchronously.
                                     Returns {job_id, state}; poll /progress.
    GET    /progress/{job_id}      — live counters + ETA for a running job.
    GET    /jobs                   — newest-first list of recent jobs.
    DELETE /jobs/{job_id}          — cooperative cancel.
    GET    /status                 — static plugin state + last_run summary.

Both POST /run and the agent-invoked ``onenote_import`` tool register their
run in the same ``JobRegistry``, so ``/progress`` works regardless of which
path started the import.
"""

from __future__ import annotations

import asyncio
import json
import sys
from pathlib import Path
from typing import TYPE_CHECKING

from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel, Field

from .importer import OneNoteImporter
from .jobs import JobRegistry, JobState

if TYPE_CHECKING:
    from agntspace.plugins.base import PluginContext


class RunRequest(BaseModel):
    target_folder: str = Field("OneNote", description="Vault subfolder for imports")
    notebooks: list[str] | None = Field(
        None,
        description="Optional list of notebook display names to include. Omit for all.",
    )
    incremental: bool = Field(True, description="Skip pages unchanged since last import.")


def make_router(ctx: PluginContext, registry: JobRegistry) -> APIRouter:
    router = APIRouter()

    @router.get("/status")
    async def status() -> dict:
        """Current plugin state. Includes last-run summary for the /plugins UI card."""
        target_folder = "OneNote"
        state_path = _resolve_vault_dir(ctx) / target_folder / ".onenote-state.json"
        state_exists = state_path.exists()
        page_count = 0
        if state_exists:
            try:
                data = json.loads(state_path.read_text(encoding="utf-8"))
                page_count = len(data.get("pages", {}) or {})
            except Exception:  # noqa: BLE001
                pass

        # Find the most recent finished job to show last-run stats.
        recent = await registry.list_recent(limit=20)
        last_finished: JobState | None = next(
            (
                j for j in recent
                if j.state in {"completed", "failed", "cancelled"}
            ),
            None,
        )
        # Running jobs — show active work if any.
        active = [j for j in recent if j.state == "running"]

        return {
            "platform": sys.platform,
            "platform_supported": sys.platform == "win32",
            "default_target_folder": target_folder,
            "state_exists": state_exists,
            "state_path": str(state_path),
            "page_count": page_count,
            "active_jobs": [j.to_public_dict() for j in active],
            "last_run": last_finished.to_public_dict() if last_finished else None,
        }

    @router.post("/run")
    async def run(req: RunRequest, request: Request) -> dict:
        """Kick off an import asynchronously. Returns immediately with a job_id.

        Poll ``GET /progress/{job_id}`` for live counters. A big notebook
        tree can take several minutes; blocking the HTTP request for that
        long is poor UX. The old blocking behaviour can still be obtained
        via the ``onenote_import`` agent tool, which waits on the same job.
        """
        if sys.platform != "win32":
            raise HTTPException(
                status_code=400,
                detail=(
                    "OneNote COM is Windows-only. This server is running on "
                    f"{sys.platform}. Use a Windows machine with the OneNote "
                    "desktop app installed."
                ),
            )

        job = await registry.create(
            target_folder=req.target_folder,
            notebooks_filter=req.notebooks,
            incremental=req.incremental,
        )
        importer = OneNoteImporter(
            vault_dir=_resolve_vault_dir(ctx),
            target_folder=req.target_folder,
            notebooks_filter=req.notebooks,
            incremental=req.incremental,
            activity_logger=ctx.activity_logger,
            decisions=ctx.decisions,
            job_state=job,
        )

        async def _run_in_background() -> None:
            # Never let a background task raise unhandled — it'd log but
            # the user wouldn't see why. The importer's own top-level
            # try/except already marks the job as failed; this is just
            # a final safety net for impossible-to-reach paths.
            try:
                await importer.run()
            except Exception as exc:  # noqa: BLE001
                job.mark_failed(f"task crashed: {exc}")

        asyncio.create_task(_run_in_background())

        return {
            "job_id": job.job_id,
            "state": job.state,
            "target_folder": job.target_folder,
            "progress_url": f"/api/plugins/onenote-import/progress/{job.job_id}",
        }

    @router.get("/progress/{job_id}")
    async def progress(job_id: str) -> dict:
        job = await registry.get(job_id)
        if job is None:
            raise HTTPException(status_code=404, detail=f"Unknown job_id: {job_id}")
        return job.to_public_dict()

    @router.get("/jobs")
    async def list_jobs(limit: int = 20) -> dict:
        limit = max(1, min(100, limit))
        jobs = await registry.list_recent(limit=limit)
        return {"jobs": [j.to_public_dict() for j in jobs]}

    @router.delete("/jobs/{job_id}")
    async def cancel_job(job_id: str) -> dict:
        ok = await registry.request_cancel(job_id)
        if not ok:
            raise HTTPException(
                status_code=404,
                detail=f"Unknown or already-finished job: {job_id}",
            )
        return {"job_id": job_id, "cancel_requested": True}

    return router


def _resolve_vault_dir(ctx: PluginContext) -> Path:
    # ``root_dir`` is the user's chosen vault folder (sibling of the
    # ``agntspace-*`` dirs). ``settings.vault_dir`` = ``root_dir/agntspace/``
    # which is agent internals and NOT where we want user-facing imports.
    return Path(ctx.settings.root_dir)
