"""Decision log API — queryable "why" behind every agent action.

Also hosts the scope-revert endpoints, since a revert is scoped by the
same correlation_id that identifies a decision chain.
"""

from __future__ import annotations

from dataclasses import asdict
from pathlib import Path

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

router = APIRouter(prefix="/decisions", tags=["decisions"])


@router.get("")
async def list_decisions(
    request: Request,
    actor: str | None = None,
    action_type: str | None = None,
    goal_id: str | None = None,
    since: str | None = None,
    limit: int = 100,
    offset: int = 0,
) -> dict:
    """Return decision records newest-first, filterable by actor/action/goal/time."""
    decisions = getattr(request.app.state, "decisions", None)
    if decisions is None:
        return {"items": [], "total": 0}
    items = await decisions.query(
        actor=actor,
        action_type=action_type,
        goal_id=goal_id,
        since=since,
        limit=limit,
        offset=offset,
    )
    return {"items": items, "total": len(items)}


@router.get("/stats")
async def decision_stats(request: Request) -> dict:
    """Aggregate stats: total, by actor, by action type, blocked count."""
    decisions = getattr(request.app.state, "decisions", None)
    if decisions is None:
        return {"total": 0, "by_actor": {}, "by_action_type": {}, "blocked": 0}
    return await decisions.stats()


@router.get("/health")
async def decision_health(request: Request) -> dict:
    """Meta-monitoring (gap #10): attempts, failures, last error, retention state.

    Lets a caller verify the observability layer itself is healthy. If
    `failure_rate` starts climbing, the silent except-pass in record() is
    losing data — check `last_error` for the cause.
    """
    decisions = getattr(request.app.state, "decisions", None)
    if decisions is None:
        return {"status": "unavailable"}
    return decisions.health()


@router.post("/prune")
async def decision_prune(request: Request) -> dict:
    """Apply the retention policy now (usually scheduled, but callable on demand)."""
    decisions = getattr(request.app.state, "decisions", None)
    if decisions is None:
        return {"by_age": 0, "by_count": 0, "total_pruned": 0}
    return await decisions.prune()


class _RetentionConfig(BaseModel):
    max_rows: int | None = None
    max_age_days: int | None = None


@router.put("/retention")
async def update_retention(request: Request, body: _RetentionConfig) -> dict:
    """Update the retention policy at runtime.

    Sent from the admin UI — does not persist across restart (retention
    defaults live in the DecisionLogger constructor). For permanent
    changes, edit main.py or ship a config override.
    """
    decisions = getattr(request.app.state, "decisions", None)
    if decisions is None:
        return {"ok": False, "reason": "DecisionLogger unavailable"}
    if body.max_rows is not None and body.max_rows > 0:
        decisions._max_rows = int(body.max_rows)  # noqa: SLF001
    if body.max_age_days is not None and body.max_age_days > 0:
        decisions._max_age_days = int(body.max_age_days)  # noqa: SLF001
    return {
        "ok": True,
        "max_rows": decisions._max_rows,  # noqa: SLF001
        "max_age_days": decisions._max_age_days,  # noqa: SLF001
    }


# ─────────────────────────────────────────────────────────────────
# Scope revert (turn checkpoint)
# ─────────────────────────────────────────────────────────────────

@router.get("/{correlation_id}/revert-preview")
async def revert_preview(request: Request, correlation_id: str) -> dict:
    """Describe what a revert would do for this correlation_id, no mutations."""
    from agntspace.vault.scope_writes import ScopeWritesRecorder

    db = getattr(request.app.state, "db", None)
    vault_writer = getattr(request.app.state, "vault_writer", None)
    if db is None or vault_writer is None:
        raise HTTPException(status_code=503, detail="vault/db not configured")
    vault_root = Path(vault_writer._vault_dir)  # noqa: SLF001

    entries = await ScopeWritesRecorder.preview_revert(db, correlation_id, vault_root)
    return {
        "correlation_id": correlation_id,
        "entries": [asdict(e) for e in entries],
        "total": len(entries),
    }


@router.post("/{correlation_id}/revert")
async def revert_scope(request: Request, correlation_id: str) -> dict:
    """Restore files touched in this correlation scope.

    Deletes files created in the scope, restores modified files from
    the captured pre-write snapshot. Never raises — returns a summary
    with per-file outcomes so the UI can surface what happened.
    """
    from agntspace.vault.scope_writes import ScopeWritesRecorder

    db = getattr(request.app.state, "db", None)
    vault_writer = getattr(request.app.state, "vault_writer", None)
    if db is None or vault_writer is None:
        raise HTTPException(status_code=503, detail="vault/db not configured")
    vault_root = Path(vault_writer._vault_dir)  # noqa: SLF001

    summary = await ScopeWritesRecorder.revert(
        db, correlation_id, vault_root, vault_writer,
    )
    summary["correlation_id"] = correlation_id
    return summary
