"""
Fleet Calendar Router

API endpoints for the Fleet Calendar feature:
- Calendar page and data
- Job reservation CRUD
- Unit assignment management
- Availability checking
"""

from fastapi import APIRouter, Request, Depends, HTTPException, Query
from fastapi.responses import HTMLResponse, JSONResponse
from sqlalchemy.orm import Session
from datetime import datetime, date, timedelta
from typing import Optional, List
import uuid
import logging

from backend.database import get_db
from backend.models import (
    RosterUnit, JobReservation, JobReservationUnit,
    UserPreferences, Project, MonitoringLocation, UnitAssignment
)
from backend.templates_config import templates
from backend.services.fleet_calendar_service import (
    get_day_summary,
    get_calendar_year_data,
    get_rolling_calendar_data,
    check_calibration_conflicts,
    get_available_units_for_period,
    get_calibration_status
)

router = APIRouter(tags=["fleet-calendar"])
logger = logging.getLogger(__name__)


# ============================================================================
# Calendar Page
# ============================================================================

@router.get("/fleet-calendar", response_class=HTMLResponse)
async def fleet_calendar_page(
    request: Request,
    year: Optional[int] = None,
    month: Optional[int] = None,
    device_type: str = "seismograph",
    db: Session = Depends(get_db)
):
    """Main Fleet Calendar page with rolling 12-month view."""
    today = date.today()

    # Default to current month as the start
    if year is None:
        year = today.year
    if month is None:
        month = today.month

    # Get calendar data for 12 months starting from year/month
    calendar_data = get_rolling_calendar_data(db, year, month, device_type)

    # Get projects for the reservation form dropdown
    projects = db.query(Project).filter(
        Project.status.in_(["active", "upcoming", "on_hold"])
    ).order_by(Project.name).all()

    # Build a serializable list of items with dates for calendar bars
    # Includes both tracked Projects (with dates) and Job Reservations (matching device_type)
    project_colors = ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899', '#06B6D4', '#F97316']
    # Map calendar device_type to project_type_ids
    device_type_to_project_types = {
        "seismograph": ["vibration_monitoring", "combined"],
        "slm": ["sound_monitoring", "combined"],
    }
    relevant_project_types = device_type_to_project_types.get(device_type, [])

    calendar_projects = []
    for i, p in enumerate(projects):
        if p.start_date and p.project_type_id in relevant_project_types:
            calendar_projects.append({
                "id": p.id,
                "name": p.name,
                "start_date": p.start_date.isoformat(),
                "end_date": p.end_date.isoformat() if p.end_date else None,
                "color": project_colors[i % len(project_colors)],
                "confirmed": True,
            })

    # Add job reservations for this device_type as bars
    from sqlalchemy import or_ as _or
    cal_window_end = date(year + ((month + 10) // 12), ((month + 10) % 12) + 1, 1)
    reservations_for_cal = db.query(JobReservation).filter(
        JobReservation.device_type == device_type,
        JobReservation.start_date <= cal_window_end,
        _or(
            JobReservation.end_date >= date(year, month, 1),
            JobReservation.end_date == None,
        )
    ).all()
    for res in reservations_for_cal:
        end = res.end_date or res.estimated_end_date
        calendar_projects.append({
            "id": res.id,
            "name": res.name,
            "start_date": res.start_date.isoformat(),
            "end_date": end.isoformat() if end else None,
            "color": res.color,
            "confirmed": bool(res.project_id),
        })

    # Calculate prev/next month navigation
    prev_year, prev_month = (year - 1, 12) if month == 1 else (year, month - 1)
    next_year, next_month = (year + 1, 1) if month == 12 else (year, month + 1)

    return templates.TemplateResponse(
        "fleet_calendar.html",
        {
            "request": request,
            "start_year": year,
            "start_month": month,
            "prev_year": prev_year,
            "prev_month": prev_month,
            "next_year": next_year,
            "next_month": next_month,
            "device_type": device_type,
            "calendar_data": calendar_data,
            "projects": projects,
            "calendar_projects": calendar_projects,
            "today": today.isoformat()
        }
    )


# ============================================================================
# Calendar Data API
# ============================================================================

@router.get("/api/fleet-calendar/data", response_class=JSONResponse)
async def get_calendar_data(
    year: int,
    device_type: str = "seismograph",
    db: Session = Depends(get_db)
):
    """Get calendar data for a specific year."""
    return get_calendar_year_data(db, year, device_type)


@router.get("/api/fleet-calendar/day/{date_str}", response_class=HTMLResponse)
async def get_day_detail(
    request: Request,
    date_str: str,
    device_type: str = "seismograph",
    db: Session = Depends(get_db)
):
    """Get detailed view for a specific day (HTMX partial)."""
    try:
        check_date = date.fromisoformat(date_str)
    except ValueError:
        raise HTTPException(status_code=400, detail="Invalid date format. Use YYYY-MM-DD")

    day_data = get_day_summary(db, check_date, device_type)

    # Get projects for display names
    projects = {p.id: p for p in db.query(Project).all()}

    return templates.TemplateResponse(
        "partials/fleet_calendar/day_detail.html",
        {
            "request": request,
            "day_data": day_data,
            "date_str": date_str,
            "date_display": check_date.strftime("%B %d, %Y"),
            "device_type": device_type,
            "projects": projects
        }
    )


# ============================================================================
# Reservation CRUD
# ============================================================================

@router.post("/api/fleet-calendar/reservations", response_class=JSONResponse)
async def create_reservation(
    request: Request,
    db: Session = Depends(get_db)
):
    """Create a new job reservation."""
    data = await request.json()

    # Validate required fields
    required = ["name", "start_date", "assignment_type"]
    for field in required:
        if field not in data:
            raise HTTPException(status_code=400, detail=f"Missing required field: {field}")

    # Need either end_date or end_date_tbd
    end_date_tbd = data.get("end_date_tbd", False)
    if not end_date_tbd and not data.get("end_date"):
        raise HTTPException(status_code=400, detail="End date is required unless marked as TBD")

    try:
        start_date = date.fromisoformat(data["start_date"])
        end_date = date.fromisoformat(data["end_date"]) if data.get("end_date") else None
        estimated_end_date = date.fromisoformat(data["estimated_end_date"]) if data.get("estimated_end_date") else None
    except ValueError:
        raise HTTPException(status_code=400, detail="Invalid date format. Use YYYY-MM-DD")

    if end_date and end_date < start_date:
        raise HTTPException(status_code=400, detail="End date must be after start date")

    if estimated_end_date and estimated_end_date < start_date:
        raise HTTPException(status_code=400, detail="Estimated end date must be after start date")

    import json as _json
    reservation = JobReservation(
        id=str(uuid.uuid4()),
        name=data["name"],
        project_id=data.get("project_id"),
        start_date=start_date,
        end_date=end_date,
        estimated_end_date=estimated_end_date,
        end_date_tbd=end_date_tbd,
        assignment_type=data["assignment_type"],
        device_type=data.get("device_type", "seismograph"),
        quantity_needed=data.get("quantity_needed"),
        estimated_units=data.get("estimated_units"),
        location_slots=_json.dumps(data["location_slots"]) if data.get("location_slots") is not None else None,
        notes=data.get("notes"),
        color=data.get("color", "#3B82F6")
    )

    db.add(reservation)

    # If specific units were provided, assign them
    if data.get("unit_ids") and data["assignment_type"] == "specific":
        for unit_id in data["unit_ids"]:
            assignment = JobReservationUnit(
                id=str(uuid.uuid4()),
                reservation_id=reservation.id,
                unit_id=unit_id,
                assignment_source="specific"
            )
            db.add(assignment)

    db.commit()

    logger.info(f"Created reservation: {reservation.name} ({reservation.id})")

    return {
        "success": True,
        "reservation_id": reservation.id,
        "message": f"Created reservation: {reservation.name}"
    }


@router.get("/api/fleet-calendar/reservations/{reservation_id}", response_class=JSONResponse)
async def get_reservation(
    reservation_id: str,
    db: Session = Depends(get_db)
):
    """Get a specific reservation with its assigned units."""
    reservation = db.query(JobReservation).filter_by(id=reservation_id).first()
    if not reservation:
        raise HTTPException(status_code=404, detail="Reservation not found")

    # Get assigned units
    assignments = db.query(JobReservationUnit).filter_by(
        reservation_id=reservation_id
    ).all()

    # Sort assignments by slot_index so order is preserved
    assignments_sorted = sorted(assignments, key=lambda a: (a.slot_index if a.slot_index is not None else 999))
    unit_ids = [a.unit_id for a in assignments_sorted]
    units = db.query(RosterUnit).filter(RosterUnit.id.in_(unit_ids)).all() if unit_ids else []
    units_by_id = {u.id: u for u in units}
    # Build per-unit lookups from assignments
    assignment_map = {a.unit_id: a for a in assignments_sorted}

    import json as _json
    stored_slots = _json.loads(reservation.location_slots) if reservation.location_slots else None

    return {
        "id": reservation.id,
        "name": reservation.name,
        "project_id": reservation.project_id,
        "start_date": reservation.start_date.isoformat(),
        "end_date": reservation.end_date.isoformat() if reservation.end_date else None,
        "estimated_end_date": reservation.estimated_end_date.isoformat() if reservation.estimated_end_date else None,
        "end_date_tbd": reservation.end_date_tbd,
        "assignment_type": reservation.assignment_type,
        "device_type": reservation.device_type,
        "quantity_needed": reservation.quantity_needed,
        "estimated_units": reservation.estimated_units,
        "location_slots": stored_slots,
        "notes": reservation.notes,
        "color": reservation.color,
        "assigned_units": [
            {
                "id": uid,
                "last_calibrated": units_by_id[uid].last_calibrated.isoformat() if uid in units_by_id and units_by_id[uid].last_calibrated else None,
                "deployed": units_by_id[uid].deployed if uid in units_by_id else False,
                "power_type": assignment_map[uid].power_type,
                "notes": assignment_map[uid].notes,
                "location_name": assignment_map[uid].location_name,
                "slot_index": assignment_map[uid].slot_index,
            }
            for uid in unit_ids
        ]
    }


@router.put("/api/fleet-calendar/reservations/{reservation_id}", response_class=JSONResponse)
async def update_reservation(
    reservation_id: str,
    request: Request,
    db: Session = Depends(get_db)
):
    """Update an existing reservation."""
    reservation = db.query(JobReservation).filter_by(id=reservation_id).first()
    if not reservation:
        raise HTTPException(status_code=404, detail="Reservation not found")

    data = await request.json()

    # Update fields if provided
    if "name" in data:
        reservation.name = data["name"]
    if "project_id" in data:
        reservation.project_id = data["project_id"]
    if "start_date" in data:
        reservation.start_date = date.fromisoformat(data["start_date"])
    if "end_date" in data:
        reservation.end_date = date.fromisoformat(data["end_date"]) if data["end_date"] else None
    if "estimated_end_date" in data:
        reservation.estimated_end_date = date.fromisoformat(data["estimated_end_date"]) if data["estimated_end_date"] else None
    if "end_date_tbd" in data:
        reservation.end_date_tbd = data["end_date_tbd"]
    if "assignment_type" in data:
        reservation.assignment_type = data["assignment_type"]
    if "quantity_needed" in data:
        reservation.quantity_needed = data["quantity_needed"]
    if "estimated_units" in data:
        reservation.estimated_units = data["estimated_units"]
    if "location_slots" in data:
        import json as _json
        reservation.location_slots = _json.dumps(data["location_slots"]) if data["location_slots"] is not None else None
    if "notes" in data:
        reservation.notes = data["notes"]
    if "color" in data:
        reservation.color = data["color"]

    reservation.updated_at = datetime.utcnow()

    db.commit()

    logger.info(f"Updated reservation: {reservation.name} ({reservation.id})")

    return {
        "success": True,
        "message": f"Updated reservation: {reservation.name}"
    }


@router.delete("/api/fleet-calendar/reservations/{reservation_id}", response_class=JSONResponse)
async def delete_reservation(
    reservation_id: str,
    db: Session = Depends(get_db)
):
    """Delete a reservation and its unit assignments."""
    reservation = db.query(JobReservation).filter_by(id=reservation_id).first()
    if not reservation:
        raise HTTPException(status_code=404, detail="Reservation not found")

    # Delete unit assignments first
    db.query(JobReservationUnit).filter_by(reservation_id=reservation_id).delete()

    # Delete the reservation
    db.delete(reservation)
    db.commit()

    logger.info(f"Deleted reservation: {reservation.name} ({reservation_id})")

    return {
        "success": True,
        "message": "Reservation deleted"
    }


# ============================================================================
# Unit Assignment
# ============================================================================

@router.post("/api/fleet-calendar/reservations/{reservation_id}/assign-units", response_class=JSONResponse)
async def assign_units_to_reservation(
    reservation_id: str,
    request: Request,
    db: Session = Depends(get_db)
):
    """Assign specific units to a reservation."""
    reservation = db.query(JobReservation).filter_by(id=reservation_id).first()
    if not reservation:
        raise HTTPException(status_code=404, detail="Reservation not found")

    data = await request.json()
    unit_ids = data.get("unit_ids", [])
    # Optional per-unit dicts keyed by unit_id
    power_types = data.get("power_types", {})
    location_notes = data.get("location_notes", {})
    location_names = data.get("location_names", {})
    # slot_indices: {"BE17354": 0, "BE9441": 1, ...}
    slot_indices = data.get("slot_indices", {})

    # Verify units exist (allow empty list to clear all assignments)
    if unit_ids:
        units = db.query(RosterUnit).filter(RosterUnit.id.in_(unit_ids)).all()
        found_ids = {u.id for u in units}
        missing = set(unit_ids) - found_ids
        if missing:
            raise HTTPException(status_code=404, detail=f"Units not found: {', '.join(missing)}")

    # Full replace: delete all existing assignments for this reservation first
    db.query(JobReservationUnit).filter_by(reservation_id=reservation_id).delete()
    db.flush()

    # Check for conflicts with other reservations and insert new assignments
    conflicts = []
    for unit_id in unit_ids:
        # Check overlapping reservations
        if reservation.end_date:
            overlapping = db.query(JobReservation).join(
                JobReservationUnit, JobReservation.id == JobReservationUnit.reservation_id
            ).filter(
                JobReservationUnit.unit_id == unit_id,
                JobReservation.id != reservation_id,
                JobReservation.start_date <= reservation.end_date,
                JobReservation.end_date >= reservation.start_date
            ).first()

            if overlapping:
                conflicts.append({
                    "unit_id": unit_id,
                    "conflict_reservation": overlapping.name,
                    "conflict_dates": f"{overlapping.start_date} - {overlapping.end_date}"
                })
                continue

        # Add assignment
        assignment = JobReservationUnit(
            id=str(uuid.uuid4()),
            reservation_id=reservation_id,
            unit_id=unit_id,
            assignment_source="filled" if reservation.assignment_type == "quantity" else "specific",
            power_type=power_types.get(unit_id),
            notes=location_notes.get(unit_id),
            location_name=location_names.get(unit_id),
            slot_index=slot_indices.get(unit_id),
        )
        db.add(assignment)

    db.commit()

    # Check for calibration conflicts
    cal_conflicts = check_calibration_conflicts(db, reservation_id)

    assigned_count = db.query(JobReservationUnit).filter_by(
        reservation_id=reservation_id
    ).count()

    return {
        "success": True,
        "assigned_count": assigned_count,
        "conflicts": conflicts,
        "calibration_warnings": cal_conflicts,
        "message": f"Assigned {len(unit_ids) - len(conflicts)} units"
    }


@router.delete("/api/fleet-calendar/reservations/{reservation_id}/units/{unit_id}", response_class=JSONResponse)
async def remove_unit_from_reservation(
    reservation_id: str,
    unit_id: str,
    db: Session = Depends(get_db)
):
    """Remove a unit from a reservation."""
    assignment = db.query(JobReservationUnit).filter_by(
        reservation_id=reservation_id,
        unit_id=unit_id
    ).first()

    if not assignment:
        raise HTTPException(status_code=404, detail="Unit assignment not found")

    db.delete(assignment)
    db.commit()

    return {
        "success": True,
        "message": f"Removed {unit_id} from reservation"
    }


# ============================================================================
# Availability & Conflicts
# ============================================================================

@router.get("/api/fleet-calendar/availability", response_class=JSONResponse)
async def check_availability(
    start_date: str,
    end_date: str,
    device_type: str = "seismograph",
    exclude_reservation_id: Optional[str] = None,
    db: Session = Depends(get_db)
):
    """Get units available for a specific date range."""
    try:
        start = date.fromisoformat(start_date)
        end = date.fromisoformat(end_date)
    except ValueError:
        raise HTTPException(status_code=400, detail="Invalid date format. Use YYYY-MM-DD")

    available = get_available_units_for_period(
        db, start, end, device_type, exclude_reservation_id
    )

    return {
        "start_date": start_date,
        "end_date": end_date,
        "device_type": device_type,
        "available_units": available,
        "count": len(available)
    }


@router.get("/api/fleet-calendar/reservations/{reservation_id}/conflicts", response_class=JSONResponse)
async def get_reservation_conflicts(
    reservation_id: str,
    db: Session = Depends(get_db)
):
    """Check for calibration conflicts in a reservation."""
    reservation = db.query(JobReservation).filter_by(id=reservation_id).first()
    if not reservation:
        raise HTTPException(status_code=404, detail="Reservation not found")

    conflicts = check_calibration_conflicts(db, reservation_id)

    return {
        "reservation_id": reservation_id,
        "reservation_name": reservation.name,
        "conflicts": conflicts,
        "has_conflicts": len(conflicts) > 0
    }


# ============================================================================
# HTMX Partials
# ============================================================================

@router.get("/api/fleet-calendar/reservations-list", response_class=HTMLResponse)
async def get_reservations_list(
    request: Request,
    year: Optional[int] = None,
    month: Optional[int] = None,
    device_type: str = "seismograph",
    db: Session = Depends(get_db)
):
    """Get list of reservations as HTMX partial."""
    from sqlalchemy import or_

    today = date.today()
    if year is None:
        year = today.year
    if month is None:
        month = today.month

    # Calculate 12-month window
    start_date = date(year, month, 1)
    # End date is 12 months later
    end_year = year + ((month + 10) // 12)
    end_month = ((month + 10) % 12) + 1
    if end_month == 12:
        end_date = date(end_year, 12, 31)
    else:
        end_date = date(end_year, end_month + 1, 1) - timedelta(days=1)

    # Filter by device_type and date window
    reservations = db.query(JobReservation).filter(
        JobReservation.device_type == device_type,
        JobReservation.start_date <= end_date,
        or_(
            JobReservation.end_date >= start_date,
            JobReservation.end_date == None  # TBD reservations
        )
    ).order_by(JobReservation.start_date).all()

    # Get assignment counts
    reservation_data = []
    for res in reservations:
        assignments = db.query(JobReservationUnit).filter_by(
            reservation_id=res.id
        ).all()
        assigned_count = len(assignments)

        # Enrich assignments with unit details, sorted by slot_index
        assignments_sorted = sorted(assignments, key=lambda a: (a.slot_index if a.slot_index is not None else 999))
        unit_ids = [a.unit_id for a in assignments_sorted]
        units = db.query(RosterUnit).filter(RosterUnit.id.in_(unit_ids)).all() if unit_ids else []
        units_by_id = {u.id: u for u in units}
        assigned_units = [
            {
                "id": a.unit_id,
                "power_type": a.power_type,
                "notes": a.notes,
                "location_name": a.location_name,
                "slot_index": a.slot_index,
                "deployed": units_by_id[a.unit_id].deployed if a.unit_id in units_by_id else False,
                "last_calibrated": units_by_id[a.unit_id].last_calibrated if a.unit_id in units_by_id else None,
            }
            for a in assignments_sorted
        ]

        # Check for calibration conflicts
        conflicts = check_calibration_conflicts(db, res.id)

        location_count = res.quantity_needed or assigned_count
        reservation_data.append({
            "reservation": res,
            "assigned_count": assigned_count,
            "location_count": location_count,
            "assigned_units": assigned_units,
            "has_conflicts": len(conflicts) > 0,
            "conflict_count": len(conflicts)
        })

    return templates.TemplateResponse(
        "partials/fleet_calendar/reservations_list.html",
        {
            "request": request,
            "reservations": reservation_data,
            "year": year,
            "device_type": device_type
        }
    )


@router.get("/api/fleet-calendar/planner-availability", response_class=JSONResponse)
async def get_planner_availability(
    device_type: str = "seismograph",
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
    exclude_reservation_id: Optional[str] = None,
    db: Session = Depends(get_db)
):
    """Get available units for the reservation planner split-panel UI.
    Dates are optional — if omitted, returns all non-retired units regardless of reservations.
    """
    if start_date and end_date:
        try:
            start = date.fromisoformat(start_date)
            end = date.fromisoformat(end_date)
        except ValueError:
            raise HTTPException(status_code=400, detail="Invalid date format. Use YYYY-MM-DD")
        units = get_available_units_for_period(db, start, end, device_type, exclude_reservation_id)
    else:
        # No dates: return all non-retired units of this type, with current reservation info
        from backend.models import RosterUnit as RU
        from datetime import timedelta
        today = date.today()
        all_units = db.query(RU).filter(
            RU.device_type == device_type,
            RU.retired == False
        ).all()

        # Build a map: unit_id -> list of active/upcoming reservations
        active_assignments = db.query(JobReservationUnit).join(
            JobReservation, JobReservationUnit.reservation_id == JobReservation.id
        ).filter(
            JobReservation.device_type == device_type,
            JobReservation.end_date >= today
        ).all()
        unit_reservations = {}
        for assignment in active_assignments:
            res = db.query(JobReservation).filter(JobReservation.id == assignment.reservation_id).first()
            if not res:
                continue
            unit_reservations.setdefault(assignment.unit_id, []).append({
                "reservation_id": res.id,
                "reservation_name": res.name,
                "start_date": res.start_date.isoformat() if res.start_date else None,
                "end_date": res.end_date.isoformat() if res.end_date else None,
                "color": res.color or "#3B82F6"
            })

        units = []
        for u in all_units:
            expiry = (u.last_calibrated + timedelta(days=365)) if u.last_calibrated else None
            units.append({
                "id": u.id,
                "last_calibrated": u.last_calibrated.isoformat() if u.last_calibrated else None,
                "expiry_date": expiry.isoformat() if expiry else None,
                "calibration_status": "needs_calibration" if not u.last_calibrated else "valid",
                "deployed": u.deployed,
                "out_for_calibration": u.out_for_calibration or False,
                "allocated": getattr(u, 'allocated', False) or False,
                "allocated_to_project_id": getattr(u, 'allocated_to_project_id', None) or "",
                "note": u.note or "",
                "reservations": unit_reservations.get(u.id, [])
            })

    # Sort: benched first (easier to assign), then deployed, then by ID
    units.sort(key=lambda u: (1 if u["deployed"] else 0, u["id"]))

    return {
        "units": units,
        "start_date": start_date,
        "end_date": end_date,
        "count": len(units)
    }


@router.get("/api/fleet-calendar/unit-quick-info/{unit_id}", response_class=JSONResponse)
async def get_unit_quick_info(unit_id: str, db: Session = Depends(get_db)):
    """Return at-a-glance info for the planner quick-view modal."""
    from backend.models import Emitter
    u = db.query(RosterUnit).filter(RosterUnit.id == unit_id).first()
    if not u:
        raise HTTPException(status_code=404, detail="Unit not found")

    today = date.today()
    expiry = (u.last_calibrated + timedelta(days=365)) if u.last_calibrated else None

    # Active/upcoming reservations
    assignments = db.query(JobReservationUnit).filter(JobReservationUnit.unit_id == unit_id).all()
    reservations = []
    for a in assignments:
        res = db.query(JobReservation).filter(
            JobReservation.id == a.reservation_id,
            JobReservation.end_date >= today
        ).first()
        if res:
            reservations.append({
                "name": res.name,
                "start_date": res.start_date.isoformat() if res.start_date else None,
                "end_date": res.end_date.isoformat() if res.end_date else None,
                "end_date_tbd": res.end_date_tbd,
                "color": res.color or "#3B82F6",
                "location_name": a.location_name,
            })

    # Last seen from emitter
    emitter = db.query(Emitter).filter(Emitter.unit_type == unit_id).first()

    from backend.services.unit_location import get_active_location
    loc = get_active_location(db, u.id)
    return {
        "id": u.id,
        "unit_type": u.unit_type,
        "deployed": u.deployed,
        "out_for_calibration": u.out_for_calibration or False,
        "note": u.note or "",
        "project_id": (loc or {}).get("project_id") or u.project_id or "",
        "address": (loc or {}).get("address") or "",
        "coordinates": (loc or {}).get("coordinates") or "",
        "deployed_with_modem_id": u.deployed_with_modem_id or "",
        "last_calibrated": u.last_calibrated.isoformat() if u.last_calibrated else None,
        "next_calibration_due": u.next_calibration_due.isoformat() if u.next_calibration_due else (expiry.isoformat() if expiry else None),
        "cal_expired": not u.last_calibrated or (expiry and expiry < today),
        "last_seen": emitter.last_seen.isoformat() if emitter and emitter.last_seen else None,
        "reservations": reservations,
    }


@router.get("/api/fleet-calendar/available-units", response_class=HTMLResponse)
async def get_available_units_partial(
    request: Request,
    start_date: str,
    end_date: str,
    device_type: str = "seismograph",
    reservation_id: Optional[str] = None,
    db: Session = Depends(get_db)
):
    """Get available units as HTMX partial for the assignment modal."""
    try:
        start = date.fromisoformat(start_date)
        end = date.fromisoformat(end_date)
    except ValueError:
        raise HTTPException(status_code=400, detail="Invalid date format")

    available = get_available_units_for_period(
        db, start, end, device_type, reservation_id
    )

    return templates.TemplateResponse(
        "partials/fleet_calendar/available_units.html",
        {
            "request": request,
            "units": available,
            "start_date": start_date,
            "end_date": end_date,
            "device_type": device_type,
            "reservation_id": reservation_id
        }
    )


@router.get("/api/fleet-calendar/month/{year}/{month}", response_class=HTMLResponse)
async def get_month_partial(
    request: Request,
    year: int,
    month: int,
    device_type: str = "seismograph",
    db: Session = Depends(get_db)
):
    """Get a single month calendar as HTMX partial."""
    calendar_data = get_calendar_year_data(db, year, device_type)
    month_data = calendar_data["months"].get(month)

    if not month_data:
        raise HTTPException(status_code=404, detail="Invalid month")

    return templates.TemplateResponse(
        "partials/fleet_calendar/month_grid.html",
        {
            "request": request,
            "year": year,
            "month": month,
            "month_data": month_data,
            "device_type": device_type,
            "today": date.today().isoformat()
        }
    )


# ============================================================================
# Promote Reservation to Project
# ============================================================================

@router.post("/api/fleet-calendar/reservations/{reservation_id}/promote-to-project", response_class=JSONResponse)
async def promote_reservation_to_project(
    reservation_id: str,
    request: Request,
    db: Session = Depends(get_db)
):
    """
    Promote a job reservation to a full project in the projects DB.
    Creates: Project + MonitoringLocations + UnitAssignments.
    """
    reservation = db.query(JobReservation).filter_by(id=reservation_id).first()
    if not reservation:
        raise HTTPException(status_code=404, detail="Reservation not found")

    data = await request.json()
    project_number = data.get("project_number") or None
    client_name = data.get("client_name") or None

    # Map device_type to project_type_id
    if reservation.device_type == "slm":
        project_type_id = "sound_monitoring"
        location_type = "sound"
    else:
        project_type_id = "vibration_monitoring"
        location_type = "vibration"

    # Check for duplicate project name
    existing = db.query(Project).filter_by(name=reservation.name).first()
    if existing:
        raise HTTPException(status_code=409, detail=f"A project named '{reservation.name}' already exists.")

    # Create the project
    project_id = str(uuid.uuid4())
    project = Project(
        id=project_id,
        name=reservation.name,
        project_number=project_number,
        client_name=client_name,
        project_type_id=project_type_id,
        status="upcoming",
        start_date=reservation.start_date,
        end_date=reservation.end_date,
        description=reservation.notes,
    )
    db.add(project)
    db.flush()

    # Load assignments sorted by slot_index
    assignments = db.query(JobReservationUnit).filter_by(reservation_id=reservation_id).all()
    assignments_sorted = sorted(assignments, key=lambda a: (a.slot_index if a.slot_index is not None else 999))

    locations_created = 0
    units_assigned = 0

    for i, assignment in enumerate(assignments_sorted):
        loc_num = str(i + 1).zfill(3)
        loc_name = assignment.location_name or f"Location {i + 1}"

        location = MonitoringLocation(
            id=str(uuid.uuid4()),
            project_id=project_id,
            location_type=location_type,
            name=loc_name,
            description=assignment.notes,
        )
        db.add(location)
        db.flush()
        locations_created += 1

        if assignment.unit_id:
            unit_assignment = UnitAssignment(
                id=str(uuid.uuid4()),
                unit_id=assignment.unit_id,
                location_id=location.id,
                project_id=project_id,
                device_type=reservation.device_type or "seismograph",
                status="active",
                notes=f"Power: {assignment.power_type}" if assignment.power_type else None,
            )
            db.add(unit_assignment)
            units_assigned += 1

    db.commit()

    logger.info(f"Promoted reservation '{reservation.name}' to project {project_id}")

    return {
        "success": True,
        "project_id": project_id,
        "project_name": reservation.name,
        "locations_created": locations_created,
        "units_assigned": units_assigned,
    }
