"""Background jobs (app/core/jobs.py) as a tenant's administrators see them:
what ran, what's waiting, what failed and why — and a failed job retried,
or a waiting one cancelled. Same permission as scheduled jobs. A job's raw
payload isn't shown (it can hold message text and addresses); its kind and
a short description are."""

import uuid
from datetime import datetime

from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession

from app.core import jobs
from app.core.database import get_db
from app.core.permissions import require_permission
from app.core.security import AuthContext, get_auth_context

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

_KIND_LABELS = {
    "field_indexes.sync": "Update field indexes",
    "records.import": "Import records",
    "summary_cache.refresh": "Refresh a dashboard summary",
    "storage.build": "Build a storage table",
    "scheduled_job.run": "Scheduled job",
    "notifications.deliver": "Deliver a notification",
}


class JobRead(BaseModel):
    id: uuid.UUID
    kind: str
    label: str
    description: str | None
    status: str
    attempts: int
    max_attempts: int
    run_after: datetime
    last_error: str | None
    result: str | None
    created_at: datetime
    started_at: datetime | None
    finished_at: datetime | None


class JobCounts(BaseModel):
    queued: int = 0
    running: int = 0
    done: int = 0
    failed: int = 0
    cancelled: int = 0


class JobList(BaseModel):
    jobs: list[JobRead]
    counts: JobCounts


def _description(kind: str, payload: dict) -> str | None:
    if kind == "notifications.deliver":
        to = [d["to"] for d in payload.get("deliveries", []) if d.get("to")]
        return f"{payload.get('subject', '')}" + (f" → {', '.join(to)}" if to else "")
    return None


@router.get("", response_model=JobList)
async def list_jobs(
    status: str | None = Query(None, pattern="^(queued|running|done|failed|cancelled)$"),
    limit: int = Query(100, ge=1, le=500),
    ctx: AuthContext = Depends(get_auth_context),
    db: AsyncSession = Depends(get_db),
    _: AuthContext = Depends(require_permission("tasks", "view")),
) -> JobList:
    where = "tenant_id = :tenant" + (" AND status = :status" if status else "")
    rows = await db.execute(
        text(
            "SELECT id, kind, payload, status, attempts, max_attempts, run_after, last_error, result, created_at, started_at, finished_at "
            f"FROM jobs WHERE {where} ORDER BY created_at DESC LIMIT :limit"
        ),
        {"tenant": ctx.tenant_id, "status": status, "limit": limit},
    )
    out = [
        JobRead(
            id=r.id, kind=r.kind, label=_KIND_LABELS.get(r.kind, r.kind), description=_description(r.kind, r.payload or {}),
            status=r.status, attempts=r.attempts, max_attempts=r.max_attempts, run_after=r.run_after, last_error=r.last_error,
            result=r.result, created_at=r.created_at, started_at=r.started_at, finished_at=r.finished_at,
        )
        for r in rows
    ]
    counts = {r.status: r.n for r in await db.execute(text("SELECT status, count(*) AS n FROM jobs WHERE tenant_id = :tenant GROUP BY status"), {"tenant": ctx.tenant_id})}
    return JobList(jobs=out, counts=JobCounts(**counts))


@router.post("/{job_id}/retry", status_code=204)
async def retry_job(
    job_id: uuid.UUID,
    ctx: AuthContext = Depends(get_auth_context),
    db: AsyncSession = Depends(get_db),
    _: AuthContext = Depends(require_permission("tasks", "edit")),
) -> None:
    if not await jobs.retry_now(db, ctx.tenant_id, job_id):
        raise HTTPException(409, "Only a failed or cancelled job can be retried — and not while a newer one like it is waiting.")


@router.post("/{job_id}/cancel", status_code=204)
async def cancel_job(
    job_id: uuid.UUID,
    ctx: AuthContext = Depends(get_auth_context),
    db: AsyncSession = Depends(get_db),
    _: AuthContext = Depends(require_permission("tasks", "edit")),
) -> None:
    if not await jobs.cancel(db, ctx.tenant_id, job_id):
        raise HTTPException(409, "Only a job that's still waiting can be cancelled.")
