"""Simulation plan API router (CRUD + download + upload results)."""
import json
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
from sqlalchemy.orm import Session

from ..database import get_db
from ..models.project import Project
from ..models.simulation_plan import SimulationPlan
from ..models.simulation_result import SimulationResult
from ..schemas.simulation_plan import (
    PlanCreate, PlanUpdate, PlanResponse, PlanListResponse, PlanDownloadResponse,
)
from ..schemas.simulation_result import ResultListResponse

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


def _generate_plan_id() -> str:
    return f"SP-{datetime.now().strftime('%Y%m%d-%H%M%S')}"


def _plan_to_response(db: Session, plan: SimulationPlan) -> PlanResponse:
    result_count = db.query(SimulationResult).filter(SimulationResult.plan_id == plan.id).count()
    variables_summary = {}
    try:
        variables_summary = json.loads(plan.variables_summary) if plan.variables_summary else {}
    except (json.JSONDecodeError, TypeError):
        pass
    return PlanResponse(
        id=plan.id,
        project_id=plan.project_id,
        name=plan.name,
        plan_id=plan.plan_id,
        status=plan.status,
        plan_data=plan.get_plan_dict(),
        variables_summary=variables_summary,
        estimated_points=plan.estimated_points or 0,
        estimated_time_min=plan.estimated_time_min or 0,
        notes=plan.notes or "",
        result_count=result_count,
        created_at=plan.created_at,
        updated_at=plan.updated_at,
    )


@router.get("", response_model=PlanListResponse)
def list_plans(
    project_id: int | None = None,
    skip: int = 0,
    limit: int = 50,
    db: Session = Depends(get_db),
):
    """List simulation plans, optionally filtered by project."""
    query = db.query(SimulationPlan)
    if project_id:
        query = query.filter(SimulationPlan.project_id == project_id)
    total = query.count()
    plans = query.order_by(SimulationPlan.updated_at.desc()).offset(skip).limit(limit).all()
    return PlanListResponse(total=total, items=[_plan_to_response(db, p) for p in plans])


@router.post("", response_model=PlanResponse, status_code=201)
def create_plan(data: PlanCreate, db: Session = Depends(get_db)):
    """Create a new simulation plan."""
    project = db.query(Project).filter(Project.id == data.project_id).first()
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")

    plan_id = _generate_plan_id()
    plan_data = data.plan_data
    plan_data["plan_id"] = plan_id

    # Extract variables summary for display
    variables_summary = {}
    estimated_points = 1
    for var in plan_data.get("variables", []):
        name = var.get("name", "unknown")
        values = var.get("values", [])
        variables_summary[name] = {
            "unit": var.get("unit", ""),
            "values": values,
            "count": len(values),
        }
        estimated_points *= len(values) if values else 1

    plan = SimulationPlan(
        project_id=data.project_id,
        name=data.name,
        plan_id=plan_id,
        status="draft",
        estimated_points=estimated_points,
        estimated_time_min=estimated_points * 3,  # ~3 min per point estimate
        notes=data.notes,
    )
    plan.set_plan_dict(plan_data)
    plan.variables_summary = json.dumps(variables_summary, ensure_ascii=False)

    db.add(plan)
    db.commit()
    db.refresh(plan)
    return _plan_to_response(db, plan)


@router.get("/{plan_id}", response_model=PlanResponse)
def get_plan(plan_id: int, db: Session = Depends(get_db)):
    """Get a plan by ID."""
    plan = db.query(SimulationPlan).filter(SimulationPlan.id == plan_id).first()
    if not plan:
        raise HTTPException(status_code=404, detail="Plan not found")
    return _plan_to_response(db, plan)


@router.put("/{plan_id}", response_model=PlanResponse)
def update_plan(plan_id: int, data: PlanUpdate, db: Session = Depends(get_db)):
    """Update a plan."""
    plan = db.query(SimulationPlan).filter(SimulationPlan.id == plan_id).first()
    if not plan:
        raise HTTPException(status_code=404, detail="Plan not found")
    update_data = data.model_dump(exclude_unset=True)
    if "plan_data" in update_data:
        plan.set_plan_dict(update_data.pop("plan_data"))
    for key, value in update_data.items():
        setattr(plan, key, value)
    db.commit()
    db.refresh(plan)
    return _plan_to_response(db, plan)


@router.delete("/{plan_id}", status_code=204)
def delete_plan(plan_id: int, db: Session = Depends(get_db)):
    """Delete a plan and its results."""
    plan = db.query(SimulationPlan).filter(SimulationPlan.id == plan_id).first()
    if not plan:
        raise HTTPException(status_code=404, detail="Plan not found")
    db.delete(plan)
    db.commit()
    return None


# ---------------------------------------------------------------------------
# API integration endpoints (for local executor)
# ---------------------------------------------------------------------------

@router.get("/{plan_id}/download", response_model=PlanDownloadResponse)
def download_plan(plan_id: int, db: Session = Depends(get_db)):
    """Download a plan as simulation_plan.json (compatible with local executor).

    This is the API endpoint used by the local execution system to fetch plans.
    Returns the raw plan JSON in the exact format expected by src/plan_schema.py.
    """
    plan = db.query(SimulationPlan).filter(SimulationPlan.id == plan_id).first()
    if not plan:
        raise HTTPException(status_code=404, detail="Plan not found")
    # Mark as executing when downloaded
    if plan.status == "draft" or plan.status == "confirmed":
        plan.status = "executing"
        db.commit()
    return PlanDownloadResponse(plan_id=plan.plan_id, plan_data=plan.get_plan_dict())


@router.get("/by-plan-id/{plan_uuid}/download", response_model=PlanDownloadResponse)
def download_plan_by_uuid(plan_uuid: str, db: Session = Depends(get_db)):
    """Download a plan by its plan_id string (e.g. SP-20260827-001)."""
    plan = db.query(SimulationPlan).filter(SimulationPlan.plan_id == plan_uuid).first()
    if not plan:
        raise HTTPException(status_code=404, detail="Plan not found")
    if plan.status in ("draft", "confirmed"):
        plan.status = "executing"
        db.commit()
    return PlanDownloadResponse(plan_id=plan.plan_id, plan_data=plan.get_plan_dict())


@router.post("/{plan_id}/upload-results", status_code=201)
async def upload_results(
    plan_id: int,
    file: UploadFile = File(...),
    db: Session = Depends(get_db),
):
    """Upload scan_results.csv from local executor.

    Parses the CSV and stores each row as a SimulationResult.
    This is the API endpoint used by the local execution system to push results back.
    """
    import csv
    import io

    plan = db.query(SimulationPlan).filter(SimulationPlan.id == plan_id).first()
    if not plan:
        raise HTTPException(status_code=404, detail="Plan not found")

    # Clear existing results for this plan (re-upload replaces)
    db.query(SimulationResult).filter(SimulationResult.plan_id == plan_id).delete()

    content = await file.read()
    text = content.decode("utf-8-sig")
    reader = csv.DictReader(io.StringIO(text))

    # Known metric keys (from solver_core)
    metric_keys = {
        "tavg_nm", "tmax_nm", "tmin_nm", "ripple_pct", "ripple_abs_nm",
        "efficiency_pct", "output_power_w", "input_power_w", "total_losses_w",
        "copper_loss_w", "iron_loss_w", "magnet_loss_w", "back_emf_v",
        "no_load_speed_rpm", "power_factor",
    }
    standard_keys = {"run_index", "status", "seconds", "error"}

    count = 0
    for row in reader:
        params = {}
        metrics = {}
        for key, val in row.items():
            if key in standard_keys or val == "" or val is None:
                continue
            try:
                fval = float(val)
            except (ValueError, TypeError):
                continue
            if key in metric_keys:
                metrics[key] = fval
            else:
                params[key] = fval

        result = SimulationResult(
            plan_id=plan_id,
            run_index=int(row.get("run_index", count + 1)),
            status=row.get("status", "OK"),
            solve_time_s=float(row.get("seconds", 0) or 0),
            error_message=row.get("error", ""),
        )
        result.set_params(params)
        result.set_metrics(metrics)
        db.add(result)
        count += 1

    # Update plan status
    plan.status = "completed"
    db.commit()

    return {"message": f"Uploaded {count} results", "count": count, "plan_id": plan.plan_id}


@router.get("/{plan_id}/results", response_model=ResultListResponse)
def get_plan_results(plan_id: int, db: Session = Depends(get_db)):
    """Get all results for a plan."""
    plan = db.query(SimulationPlan).filter(SimulationPlan.id == plan_id).first()
    if not plan:
        raise HTTPException(status_code=404, detail="Plan not found")
    results = (
        db.query(SimulationResult)
        .filter(SimulationResult.plan_id == plan_id)
        .order_by(SimulationResult.run_index.asc())
        .all()
    )
    from ..schemas.simulation_result import ResultResponse
    items = [
        ResultResponse(
            id=r.id, plan_id=r.plan_id, run_index=r.run_index,
            status=r.status, solve_time_s=r.solve_time_s,
            params=r.get_params(), metrics=r.get_metrics(),
            error_message=r.error_message or "", created_at=r.created_at,
        )
        for r in results
    ]
    return ResultListResponse(total=len(items), items=items)
