"""Scheduling-related API routes."""

from __future__ import annotations

from datetime import datetime, timezone, timedelta

from fastapi import APIRouter, Depends, HTTPException, Query

from peon_mcp.common.constants import VALID_PRIORITIES
from peon_mcp.common.dependencies import get_db
from peon_mcp.common.schemas import OkResponse
from peon_mcp.db import row_to_dict
from peon_mcp.scheduling.cron import get_next_run_at, get_upcoming_runs, validate_cron_expression
from peon_mcp.scheduling.evaluator import compute_next_run
from peon_mcp.scheduling.builtin_templates import BUILTIN_TEMPLATES, get_builtin_template
from peon_mcp.scheduling.schemas import (
    BuiltinTemplateResponse,
    TaskTemplateCreate,
    TaskTemplateResponse,
    TaskTemplateUpdate,
    TriggerResponse,
    UpcomingTask,
)

router = APIRouter(tags=["Scheduling"])


def _enrich_template(row: dict) -> dict:
    """Add computed next_run_at to a raw task_template row dict."""
    cron_expr = row.get("cron_expression", "")
    next_dt = get_next_run_at(cron_expr)
    row["next_run_at"] = next_dt.isoformat() if next_dt else None
    # Normalize enabled from int to bool
    row["enabled"] = bool(row.get("enabled", 1))
    return row


@router.post("/api/schedules", response_model=TaskTemplateResponse, status_code=201)
async def create_schedule(body: TaskTemplateCreate, db=Depends(get_db)):
    """Create a new task template (scheduled task)."""
    title = body.title.strip()
    if not title:
        raise HTTPException(400, detail="title is required")

    if body.priority not in VALID_PRIORITIES:
        raise HTTPException(
            400,
            detail=f"Invalid priority '{body.priority}'. Must be one of: {', '.join(sorted(VALID_PRIORITIES))}"
        )

    if not validate_cron_expression(body.cron_expression):
        raise HTTPException(400, detail=f"Invalid cron expression: '{body.cron_expression}'")

    # Validate project exists
    proj_rows = await db.execute_fetchall(
        "SELECT id FROM projects WHERE id = ?", (body.project_id,)
    )
    if not proj_rows:
        raise HTTPException(400, detail=f"Project '{body.project_id}' does not exist.")

    # Validate feature exists and belongs to same project
    if body.feature_id is not None:
        feat_rows = await db.execute_fetchall(
            "SELECT project_id FROM features WHERE id = ?", (body.feature_id,)
        )
        if not feat_rows:
            raise HTTPException(400, detail=f"Feature {body.feature_id} does not exist.")
        if feat_rows[0]["project_id"] != body.project_id:
            raise HTTPException(
                400,
                detail=f"Feature {body.feature_id} belongs to project '{feat_rows[0]['project_id']}', not '{body.project_id}'."
            )

    # Pre-compute initial next_run_at so the evaluator can poll this template immediately
    initial_next_run = compute_next_run(body.cron_expression, body.timezone, datetime.now(timezone.utc))
    initial_next_run_iso = initial_next_run.isoformat() if initial_next_run else None

    cursor = await db.execute(
        """
        INSERT INTO task_templates
            (project_id, feature_id, title, description, priority, cron_expression, timezone, enabled, next_run_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
        """,
        (
            body.project_id,
            body.feature_id,
            title,
            body.description,
            body.priority,
            body.cron_expression,
            body.timezone,
            1 if body.enabled else 0,
            initial_next_run_iso,
        ),
    )
    await db.commit()

    rows = await db.execute_fetchall(
        "SELECT * FROM task_templates WHERE id = ?", (cursor.lastrowid,)
    )
    return _enrich_template(row_to_dict(rows[0]))


@router.get("/api/schedules", response_model=list[TaskTemplateResponse])
async def list_schedules(
    project_id: str = Query(...),
    db=Depends(get_db),
):
    """List all task templates for a project."""
    rows = await db.execute_fetchall(
        "SELECT * FROM task_templates WHERE project_id = ? ORDER BY created_at DESC",
        (project_id,),
    )
    return [_enrich_template(row_to_dict(r)) for r in rows]


@router.get("/api/schedules/upcoming", response_model=list[UpcomingTask])
async def list_upcoming(
    project_id: str = Query(...),
    hours: int = Query(default=24, ge=1, le=720),
    db=Depends(get_db),
):
    """Preview tasks that will be created in the next N hours for a project."""
    rows = await db.execute_fetchall(
        "SELECT * FROM task_templates WHERE project_id = ? AND enabled = 1",
        (project_id,),
    )

    now = datetime.now(timezone.utc)
    cutoff = now + timedelta(hours=hours)

    upcoming: list[dict] = []
    for row in rows:
        template = row_to_dict(row)
        runs = get_upcoming_runs(template["cron_expression"], count=hours + 1, after=now)
        for run_dt in runs:
            if run_dt > cutoff:
                break
            upcoming.append({
                "template_id": template["id"],
                "template_title": template["title"],
                "scheduled_at": run_dt.isoformat(),
                "priority": template["priority"],
            })

    # Sort by scheduled_at
    upcoming.sort(key=lambda x: x["scheduled_at"])
    return upcoming


@router.get("/api/schedules/builtin", response_model=list[BuiltinTemplateResponse])
async def list_builtin_templates():
    """List all available built-in schedule templates."""
    return list(BUILTIN_TEMPLATES)


@router.post("/api/schedules/seed", response_model=TaskTemplateResponse, status_code=201)
async def seed_schedule(
    project_id: str = Query(...),
    template: str = Query(...),
    db=Depends(get_db),
):
    """Create a task template from a built-in preset for the given project."""
    builtin = get_builtin_template(template)
    if builtin is None:
        available = ", ".join(t["key"] for t in BUILTIN_TEMPLATES)
        raise HTTPException(400, detail=f"Unknown built-in template '{template}'. Available: {available}")

    proj_rows = await db.execute_fetchall(
        "SELECT id FROM projects WHERE id = ?", (project_id,)
    )
    if not proj_rows:
        raise HTTPException(400, detail=f"Project '{project_id}' does not exist.")

    initial_next_run = compute_next_run(builtin["cron_expression"], builtin["timezone"], datetime.now(timezone.utc))
    initial_next_run_iso = initial_next_run.isoformat() if initial_next_run else None

    cursor = await db.execute(
        """
        INSERT INTO task_templates
            (project_id, title, description, priority, cron_expression, timezone, enabled, next_run_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?)
        """,
        (
            project_id,
            builtin["title"],
            builtin["description"],
            builtin["priority"],
            builtin["cron_expression"],
            builtin["timezone"],
            1,
            initial_next_run_iso,
        ),
    )
    await db.commit()

    rows = await db.execute_fetchall(
        "SELECT * FROM task_templates WHERE id = ?", (cursor.lastrowid,)
    )
    return _enrich_template(row_to_dict(rows[0]))


@router.post("/api/schedules/seed-all", response_model=list[TaskTemplateResponse], status_code=201)
async def seed_all_schedules(
    project_id: str = Query(...),
    enabled: bool = Query(default=False),
    db=Depends(get_db),
):
    """Create all built-in schedule templates for a project (disabled by default)."""
    proj_rows = await db.execute_fetchall(
        "SELECT id FROM projects WHERE id = ?", (project_id,)
    )
    if not proj_rows:
        raise HTTPException(400, detail=f"Project '{project_id}' does not exist.")

    now = datetime.now(timezone.utc)
    created = []

    for builtin in BUILTIN_TEMPLATES:
        initial_next_run = compute_next_run(builtin["cron_expression"], builtin["timezone"], now)
        initial_next_run_iso = initial_next_run.isoformat() if initial_next_run else None

        cursor = await db.execute(
            """
            INSERT INTO task_templates
                (project_id, title, description, priority, cron_expression, timezone, enabled, next_run_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (
                project_id,
                builtin["title"],
                builtin["description"],
                builtin["priority"],
                builtin["cron_expression"],
                builtin["timezone"],
                1 if enabled else 0,
                initial_next_run_iso,
            ),
        )
        rows = await db.execute_fetchall(
            "SELECT * FROM task_templates WHERE id = ?", (cursor.lastrowid,)
        )
        created.append(_enrich_template(row_to_dict(rows[0])))

    await db.commit()
    return created


# CRITICAL: /builtin, /seed, /seed-all, /upcoming must be registered BEFORE /{template_id} to avoid path conflicts
@router.get("/api/schedules/{template_id}", response_model=TaskTemplateResponse)
async def get_schedule(template_id: int, db=Depends(get_db)):
    """Get a single task template by ID."""
    rows = await db.execute_fetchall(
        "SELECT * FROM task_templates WHERE id = ?", (template_id,)
    )
    if not rows:
        raise HTTPException(404, detail="Task template not found")
    return _enrich_template(row_to_dict(rows[0]))


@router.put("/api/schedules/{template_id}", response_model=TaskTemplateResponse)
async def update_schedule(template_id: int, body: TaskTemplateUpdate, db=Depends(get_db)):
    """Update a task template."""
    provided = body.model_dump(exclude_unset=True)

    if "priority" in provided and provided["priority"] not in VALID_PRIORITIES:
        raise HTTPException(
            400,
            detail=f"Invalid priority '{provided['priority']}'. Must be one of: {', '.join(sorted(VALID_PRIORITIES))}"
        )

    if "cron_expression" in provided and not validate_cron_expression(provided["cron_expression"]):
        raise HTTPException(400, detail=f"Invalid cron expression: '{provided['cron_expression']}'")

    # Check template exists
    existing_rows = await db.execute_fetchall(
        "SELECT * FROM task_templates WHERE id = ?", (template_id,)
    )
    if not existing_rows:
        raise HTTPException(404, detail="Task template not found")

    existing = row_to_dict(existing_rows[0])

    fields: list[str] = []
    params: list = []

    for key in ("title", "description", "priority", "feature_id", "cron_expression", "timezone"):
        if key in provided:
            fields.append(f"{key} = ?")
            params.append(provided[key])

    if "enabled" in provided:
        fields.append("enabled = ?")
        params.append(1 if provided["enabled"] else 0)

    if not fields:
        raise HTTPException(400, detail="No fields to update")

    # Recompute next_run_at if cron_expression or timezone changed
    new_cron = provided.get("cron_expression", existing["cron_expression"])
    new_tz = provided.get("timezone", existing.get("timezone", "UTC"))
    if "cron_expression" in provided or "timezone" in provided:
        recomputed = compute_next_run(new_cron, new_tz, datetime.now(timezone.utc))
        fields.append("next_run_at = ?")
        params.append(recomputed.isoformat() if recomputed else None)

    fields.append("updated_at = CURRENT_TIMESTAMP")
    params.append(template_id)

    await db.execute(
        f"UPDATE task_templates SET {', '.join(fields)} WHERE id = ?", params
    )
    await db.commit()

    rows = await db.execute_fetchall(
        "SELECT * FROM task_templates WHERE id = ?", (template_id,)
    )
    return _enrich_template(row_to_dict(rows[0]))


@router.delete("/api/schedules/{template_id}", response_model=OkResponse)
async def delete_schedule(template_id: int, db=Depends(get_db)):
    """Delete a task template."""
    cursor = await db.execute(
        "DELETE FROM task_templates WHERE id = ?", (template_id,)
    )
    await db.commit()
    if cursor.rowcount == 0:
        raise HTTPException(404, detail="Task template not found")
    return {"ok": True}


@router.post("/api/schedules/{template_id}/trigger", response_model=TriggerResponse, status_code=201)
async def trigger_schedule(template_id: int, db=Depends(get_db)):
    """Manually trigger a task template to create a task immediately."""
    rows = await db.execute_fetchall(
        "SELECT * FROM task_templates WHERE id = ?", (template_id,)
    )
    if not rows:
        raise HTTPException(404, detail="Task template not found")

    template = row_to_dict(rows[0])

    cursor = await db.execute(
        """
        INSERT INTO tasks
            (project_id, feature_id, title, description, priority, status)
        VALUES (?, ?, ?, ?, ?, 'todo')
        """,
        (
            template["project_id"],
            template["feature_id"],
            template["title"],
            template["description"],
            template["priority"],
        ),
    )
    task_id = cursor.lastrowid

    # Record the trigger time
    await db.execute(
        "UPDATE task_templates SET last_run_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = ?",
        (template_id,),
    )
    await db.commit()

    return {
        "task_id": task_id,
        "template_id": template_id,
        "message": f"Task #{task_id} created from template #{template_id}",
    }
