# app/modules/dispatch/service.py
"""
Business logic for the Dispatch module.
Handles:
  - Dispatch queue (PDI-approved chassis)
  - Thermal checksheet submission + photo upload
  - Gate pass generation + driver photo upload
"""
import os
import uuid
from datetime import datetime, date, timezone
from decimal import Decimal
from typing import Optional

from fastapi import HTTPException, UploadFile
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.models.dispatch_models import (
    DispatchThermalChecksheet,
    DispatchThermalChecksheetItem,
    DispatchGatePass,
    DispatchGatePassItem,
)
from app.models.yard_models import ChassisMaster, VehicleModelMaster
from app.models.master_list_models import MASTER_LIST_MODELS
from app.modules.dispatch import schemas

# ─── Upload directory helpers ─────────────────────────────────────────────────

UPLOAD_ROOT = "uploads/dispatch"


def _ensure_dir(path: str) -> None:
    os.makedirs(path, exist_ok=True)


async def _save_upload(file: UploadFile, subdir: str, filename_prefix: str) -> str:
    """Save an UploadFile to disk and return the /uploads/... URL path."""
    folder = os.path.join(UPLOAD_ROOT, subdir)
    _ensure_dir(folder)
    ext = os.path.splitext(file.filename or "upload.jpg")[1] or ".jpg"
    fname = f"{filename_prefix}_{uuid.uuid4().hex[:8]}{ext}"
    fpath = os.path.join(folder, fname)
    contents = await file.read()
    with open(fpath, "wb") as f:
        f.write(contents)
    return f"/uploads/dispatch/{subdir}/{fname}"


# ─── Dispatch Queue ───────────────────────────────────────────────────────────

# Chassis whose tracking_status is READY_FOR_DISPATCH are in the dispatch queue.
# We map them to PENDING_THERMAL / PENDING_GATEPASS / DISPATCHED.

async def get_dispatch_queue(db: AsyncSession) -> list[schemas.DispatchQueueItem]:
    """
    Return all chassis in the dispatch pipeline:
      READY_FOR_DISPATCH → PENDING_THERMAL or PENDING_GATEPASS
      DISPATCHED         → DISPATCHED
    """
    result = await db.execute(
        select(ChassisMaster, VehicleModelMaster.model_number)
        .outerjoin(VehicleModelMaster, VehicleModelMaster.model_id == ChassisMaster.model_id)
        .where(ChassisMaster.tracking_status.in_(["READY_FOR_DISPATCH", "DISPATCHED"]))
        .order_by(ChassisMaster.created_at.desc())
    )
    chassis_list = result.all()

    queue: list[schemas.DispatchQueueItem] = []
    for c, model_number in chassis_list:
        # Determine dispatch status
        if c.tracking_status == "DISPATCHED":
            dispatch_status = "DISPATCHED"
        else:
            # Check if thermal checksheet exists
            cs_result = await db.execute(
                select(DispatchThermalChecksheet).where(
                    DispatchThermalChecksheet.chassis_id == c.chassis_id
                )
            )
            checksheet = cs_result.scalars().first()
            if checksheet:
                dispatch_status = "PENDING_GATEPASS"
            else:
                dispatch_status = "PENDING_THERMAL"

        queue.append(schemas.DispatchQueueItem(
            id=c.chassis_id,
            chassis_no=c.chassis_no,
            model=model_number,
            pdi_approved_date=c.dispatched_at.date().isoformat() if c.dispatched_at else None,
            dispatch_status=dispatch_status,
        ))

    return queue


# ─── Thermal Checksheet ───────────────────────────────────────────────────────

# Item texts come from their master tables (Setup > Dropdown Lists) and are copied onto each
# saved row, so the audit trail keeps the wording that was on screen at the time.
async def _lookup_labels(db: AsyncSession, category: str) -> dict:
    model = MASTER_LIST_MODELS[category]
    rows = (await db.execute(select(model.code, model.label))).all()
    return {code: label for code, label in rows}


async def get_thermal_checksheet(
    db: AsyncSession, chassis_id: uuid.UUID
) -> schemas.ThermalChecksheetOut:
    result = await db.execute(
        select(DispatchThermalChecksheet).where(
            DispatchThermalChecksheet.chassis_id == chassis_id
        )
    )
    cs = result.scalars().first()
    if not cs:
        raise HTTPException(status_code=404, detail="Thermal checksheet not found for this chassis")

    # Always fetch real chassis_no from ChassisMaster (stored value may be stale/UUID)
    chassis_result = await db.execute(
        select(ChassisMaster).where(ChassisMaster.chassis_id == chassis_id)
    )
    chassis = chassis_result.scalars().first()
    real_chassis_no = chassis.chassis_no if chassis else cs.chassis_no

    items_result = await db.execute(
        select(DispatchThermalChecksheetItem).where(
            DispatchThermalChecksheetItem.checksheet_id == cs.checksheet_id
        ).order_by(DispatchThermalChecksheetItem.item_no)
    )
    items = items_result.scalars().all()

    return schemas.ThermalChecksheetOut(
        checksheet_id=cs.checksheet_id,
        chassis_id=cs.chassis_id,
        chassis_no=real_chassis_no,  # always use real chassis_no from master table
        model=cs.model,
        engine_type=cs.engine_type,
        check_date=cs.check_date,
        signature=cs.signature,
        photo_front=cs.photo_front,
        photo_rear=cs.photo_rear,
        photo_rh=cs.photo_rh,
        photo_lh=cs.photo_lh,
        all_ok=cs.all_ok,
        submitted_at=cs.submitted_at,
        items=[
            schemas.ThermalItemOut(
                item_result_id=i.item_result_id,
                item_no=i.item_no,
                item_text=i.item_text,
                is_ok=i.is_ok,
                photo_url=i.photo_url,
                authority_name=i.authority_name,
                fault_type=i.fault_type,
                remarks=i.remarks,
                mail_screenshot_url=i.mail_screenshot_url,
            )
            for i in items
        ],
    )


async def submit_thermal_checksheet(
    db: AsyncSession,
    chassis_id: uuid.UUID,
    data: schemas.ThermalChecksheetData,
    form_files: dict,   # key -> UploadFile
    submitted_by_id: Optional[uuid.UUID] = None,
) -> schemas.ThermalChecksheetOut:
    """
    Create or replace the thermal checksheet for a chassis.
    form_files keys:
      item_photo_{item_no}        — per-item camera photo
      mail_screenshot_{item_no}   — mail screenshot for manufacturing defect
      bus_photo_front/rear/rh/lh  — 4 bus views
    """

    # Validate chassis exists
    chassis_result = await db.execute(
        select(ChassisMaster).where(ChassisMaster.chassis_id == chassis_id)
    )
    chassis = chassis_result.scalars().first()
    if not chassis:
        raise HTTPException(status_code=404, detail="Chassis not found")

    # Remove existing checksheet if any (idempotent re-submit)
    existing_result = await db.execute(
        select(DispatchThermalChecksheet).where(
            DispatchThermalChecksheet.chassis_id == chassis_id
        )
    )
    existing = existing_result.scalars().first()
    if existing:
        # Delete old items and flush immediately so FK is satisfied
        old_items_result = await db.execute(
            select(DispatchThermalChecksheetItem).where(
                DispatchThermalChecksheetItem.checksheet_id == existing.checksheet_id
            )
        )
        for old_item in old_items_result.scalars().all():
            await db.delete(old_item)
        await db.flush()

        # Delete any gate passes (and their items) for this chassis
        # so the user gets a fresh gate pass form after resubmitting checksheet
        gp_result = await db.execute(
            select(DispatchGatePass).where(
                DispatchGatePass.chassis_id == chassis_id
            )
        )
        for gp in gp_result.scalars().all():
            # Delete gate pass items first
            gp_items = await db.execute(
                select(DispatchGatePassItem).where(
                    DispatchGatePassItem.gate_pass_id == gp.gate_pass_id
                )
            )
            for gp_item in gp_items.scalars().all():
                await db.delete(gp_item)
            await db.delete(gp)
        await db.flush()

        # Now safe to delete the checksheet
        await db.delete(existing)
        await db.flush()

    # Save bus photos
    async def _maybe_save(key: str, subdir: str, prefix: str) -> Optional[str]:
        f = form_files.get(key)
        if f and f.filename:
            return await _save_upload(f, subdir, prefix)
        return None

    photo_front = await _maybe_save("bus_photo_front", "bus_photos", "front")
    photo_rear  = await _maybe_save("bus_photo_rear",  "bus_photos", "rear")
    photo_rh    = await _maybe_save("bus_photo_rh",    "bus_photos", "rh")
    photo_lh    = await _maybe_save("bus_photo_lh",    "bus_photos", "lh")

    # Determine check_date
    check_date = None
    if data.date:
        try:
            check_date = date.fromisoformat(data.date)
        except ValueError:
            pass

    all_ok = all(r.ok for r in data.rows)

    # Always store the real chassis_no from ChassisMaster
    cs = DispatchThermalChecksheet(
        chassis_id=chassis_id,
        chassis_no=chassis.chassis_no,
        model=data.model,
        engine_type=data.engine_type,
        check_date=check_date,
        signature=data.signature,
        photo_front=photo_front,
        photo_rear=photo_rear,
        photo_rh=photo_rh,
        photo_lh=photo_lh,
        all_ok=all_ok,
        submitted_by=submitted_by_id,
        submitted_at=datetime.now(timezone.utc),
    )
    db.add(cs)
    await db.flush()  # get checksheet_id

    # Create item rows
    item_texts = await _lookup_labels(db, 'THERMAL_CHECK_ITEM')
    item_objs = []
    for row in data.rows:
        # Item photo
        item_photo_url = None
        item_photo_file = form_files.get(f"item_photo_{row.item_no}")
        if item_photo_file and item_photo_file.filename:
            item_photo_url = await _save_upload(
                item_photo_file, "item_photos", f"item_{row.item_no}"
            )

        # Mail screenshot
        mail_screenshot_url = None
        mail_file = form_files.get(f"mail_screenshot_{row.item_no}")
        if mail_file and mail_file.filename:
            mail_screenshot_url = await _save_upload(
                mail_file, "mail_screenshots", f"mail_{row.item_no}"
            )

        item_obj = DispatchThermalChecksheetItem(
            checksheet_id=cs.checksheet_id,
            item_no=row.item_no,
            item_text=item_texts.get(str(row.item_no)),
            is_ok=row.ok,
            photo_url=item_photo_url,
            authority_name=row.authority_name,
            fault_type=row.fault_type,
            remarks=row.remarks,
            mail_screenshot_url=mail_screenshot_url,
        )
        db.add(item_obj)
        item_objs.append(item_obj)

    await db.flush()

    # Update chassis tracking_status if all OK → mark ready for gate pass
    if all_ok and chassis.tracking_status == "READY_FOR_DISPATCH":
        pass  # stays READY_FOR_DISPATCH until gate pass generated

    return schemas.ThermalChecksheetOut(
        checksheet_id=cs.checksheet_id,
        chassis_id=cs.chassis_id,
        chassis_no=cs.chassis_no,
        model=cs.model,
        engine_type=cs.engine_type,
        check_date=cs.check_date,
        signature=cs.signature,
        photo_front=cs.photo_front,
        photo_rear=cs.photo_rear,
        photo_rh=cs.photo_rh,
        photo_lh=cs.photo_lh,
        all_ok=cs.all_ok,
        submitted_at=cs.submitted_at,
        items=[
            schemas.ThermalItemOut(
                item_result_id=i.item_result_id,
                item_no=i.item_no,
                item_text=i.item_text,
                is_ok=i.is_ok,
                photo_url=i.photo_url,
                authority_name=i.authority_name,
                fault_type=i.fault_type,
                remarks=i.remarks,
                mail_screenshot_url=i.mail_screenshot_url,
            )
            for i in item_objs
        ],
    )


# ─── Gate Pass ────────────────────────────────────────────────────────────────

async def get_gate_pass(
    db: AsyncSession, chassis_id: uuid.UUID
) -> schemas.GatePassOut:
    result = await db.execute(
        select(DispatchGatePass).where(
            DispatchGatePass.chassis_id == chassis_id
        ).order_by(DispatchGatePass.created_at.desc())
    )
    gp = result.scalars().first()
    if not gp:
        raise HTTPException(status_code=404, detail="Gate pass not found for this chassis")

    # Always fetch real chassis_no from ChassisMaster
    chassis_result = await db.execute(
        select(ChassisMaster).where(ChassisMaster.chassis_id == chassis_id)
    )
    chassis = chassis_result.scalars().first()
    real_chassis_no = chassis.chassis_no if chassis else gp.chassis_no

    items_result = await db.execute(
        select(DispatchGatePassItem).where(
            DispatchGatePassItem.gate_pass_id == gp.gate_pass_id
        )
    )
    items = items_result.scalars().all()

    return _build_gate_pass_out(gp, items, real_chassis_no)


async def generate_gate_pass(
    db: AsyncSession,
    chassis_id: uuid.UUID,
    data: schemas.GatePassData,
    form_files: dict,
    created_by_id: Optional[uuid.UUID] = None,
) -> schemas.GatePassOut:
    """Create a gate pass record. Marks chassis as DISPATCHED."""

    chassis_result = await db.execute(
        select(ChassisMaster).where(ChassisMaster.chassis_id == chassis_id)
    )
    chassis = chassis_result.scalars().first()
    if not chassis:
        raise HTTPException(status_code=404, detail="Chassis not found")

    # Get checksheet (for FK link)
    cs_result = await db.execute(
        select(DispatchThermalChecksheet).where(
            DispatchThermalChecksheet.chassis_id == chassis_id
        )
    )
    checksheet = cs_result.scalars().first()

    # Remove existing gate pass if any (clear all duplicates if any exist)
    gp_existing_result = await db.execute(
        select(DispatchGatePass).where(DispatchGatePass.chassis_id == chassis_id)
    )
    for gp_existing in gp_existing_result.scalars().all():
        # Delete old items
        old_items_result = await db.execute(
            select(DispatchGatePassItem).where(
                DispatchGatePassItem.gate_pass_id == gp_existing.gate_pass_id
            )
        )
        for old_item in old_items_result.scalars().all():
            await db.delete(old_item)
        await db.flush()
        
        await db.delete(gp_existing)
        await db.flush()

    # Driver photo
    driver_photo_url = None
    driver_file = form_files.get("driver_photo")
    if driver_file and driver_file.filename:
        driver_photo_url = await _save_upload(driver_file, "driver_photos", "driver")

    # Parse fuel values
    def _to_decimal(val: Optional[str]) -> Optional[Decimal]:
        if val is None or str(val).strip() == "":
            return None
        try:
            return Decimal(str(val))
        except Exception:
            return None

    gp = DispatchGatePass(
        chassis_id=chassis_id,
        checksheet_id=checksheet.checksheet_id if checksheet else None,
        chassis_no=chassis.chassis_no,  # always use real chassis_no from ChassisMaster
        generated_at=datetime.now(timezone.utc),
        fuel_before_ltr=_to_decimal(data.fuel_before_ltr),
        filling_fuel_qty_ltr=_to_decimal(data.filling_fuel_qty_ltr),
        driver_name=data.driver_name,
        driver_photo_url=driver_photo_url,
        dispatch_authority=data.dispatch_authority,
        created_by=created_by_id,
    )
    db.add(gp)
    await db.flush()

    # Gate pass items
    gate_item_labels = await _lookup_labels(db, 'GATE_PASS_ITEM')
    item_objs = []
    for item_data in data.items:
        label = gate_item_labels.get(item_data.key, item_data.key.replace("_", " ").title())
        item_obj = DispatchGatePassItem(
            gate_pass_id=gp.gate_pass_id,
            item_key=item_data.key,
            item_label=label,
            is_checked=item_data.checked,
            qty=item_data.qty,
        )
        db.add(item_obj)
        item_objs.append(item_obj)

    # Mark chassis as dispatched
    chassis.tracking_status = "DISPATCHED"
    chassis.dispatched_at = datetime.now(timezone.utc)

    await db.flush()
    return _build_gate_pass_out(gp, item_objs, chassis.chassis_no)


def _build_gate_pass_out(
    gp: DispatchGatePass, items: list[DispatchGatePassItem], real_chassis_no: Optional[str] = None
) -> schemas.GatePassOut:
    return schemas.GatePassOut(
        gate_pass_id=gp.gate_pass_id,
        chassis_id=gp.chassis_id,
        chassis_no=real_chassis_no or gp.chassis_no,
        generated_at=gp.generated_at,
        fuel_before_ltr=gp.fuel_before_ltr,
        filling_fuel_qty_ltr=gp.filling_fuel_qty_ltr,
        driver_name=gp.driver_name,
        driver_photo_url=gp.driver_photo_url,
        dispatch_authority=gp.dispatch_authority,
        items=[
            schemas.GatePassItemOut(
                gate_pass_item_id=i.gate_pass_item_id,
                item_key=i.item_key,
                item_label=i.item_label,
                is_checked=i.is_checked,
                qty=i.qty,
            )
            for i in items
        ],
    )
