from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from sqlalchemy import func, delete, case
from sqlalchemy.orm import selectinload
from typing import List, Optional
import uuid

from datetime import datetime, date, timedelta

from app.core.database import get_db
from app.core.security import require_permission, require_permission_any
from app.models.core_models import LoginUsers
from app.models.sales_models import (
    CustomerMaster, CustomerContact, CustomerTypeMaster, TransportMaster, SalesEnquiry,
    SparePartsOrder, SparePartsOrderDetail,
    ServiceOrder, SalesActivityLog,
    CustomerPaymentLog, GoodwillApprovalLog,
    ClientMaster, DealerClientMap,
    SalesVehicleTemplate, SalesVehicleTemplateFeature
)
from app.models.rm_models import StoreMaster
from app.models.dealer_models import DealerClientMaster, DealerOrder
from decimal import Decimal
from app.modules.sales import schemas
from app.modules.sales import dashboard as sales_dashboard_service
from app.modules.inventory.service import InventoryService

router = APIRouter(tags=["Sales Module"])

@router.get("/dashboard")
async def sales_dashboard(
    start_date: Optional[date] = Query(None),
    end_date: Optional[date] = Query(None),
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission('SALES', 'view')),
):
    """Live pipeline plus period figures (default period: the last 30 days)."""
    end = end_date or date.today()
    start = start_date or end - timedelta(days=30)
    if start > end:
        raise HTTPException(status_code=400, detail="Start date must be on or before the end date.")
    return await sales_dashboard_service.build_dashboard(db, start, end)


@router.get("/customer-types", response_model=List[schemas.CustomerTypeMasterResponse])
async def list_customer_types(db: AsyncSession = Depends(get_db), current_user: LoginUsers = Depends(require_permission_any(['SALES', 'YARD'], 'view'))):
    result = await db.execute(select(CustomerTypeMaster).where(CustomerTypeMaster.is_active == True))
    return result.scalars().all()

@router.get("/transports", response_model=List[schemas.TransportMasterResponse])
async def list_transports(db: AsyncSession = Depends(get_db), current_user: LoginUsers = Depends(require_permission_any(['SALES', 'YARD'], 'view'))):
    result = await db.execute(select(TransportMaster).order_by(TransportMaster.name))
    return result.scalars().all()


@router.get("/customers", response_model=List[schemas.CustomerMasterResponse])
async def list_customers(
    type_code: Optional[str] = None,
    region: Optional[str] = None,
    city: Optional[str] = None,
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission_any(['SALES', 'YARD'], 'view'))
):
    stmt = select(CustomerMaster).options(
        selectinload(CustomerMaster.contacts),
        selectinload(CustomerMaster.customer_type)
    ).where(CustomerMaster.is_active == True)
    if type_code:
        stmt = stmt.where(CustomerMaster.type_code == type_code)
    if region:
        stmt = stmt.where(CustomerMaster.region == region)
    if city:
        stmt = stmt.where(CustomerMaster.city.ilike(f"%{city}%"))

    result = await db.execute(stmt)
    customers = result.scalars().all()

    counts = dict((await db.execute(
        select(DealerClientMaster.dealer_id, func.count(DealerClientMaster.client_id))
        .where(DealerClientMaster.is_active == True)
        .group_by(DealerClientMaster.dealer_id)
    )).all())
    for c in customers:
        c.active_client_count = counts.get(c.customer_id, 0)

    return customers

@router.get("/customers/{customer_id}", response_model=schemas.CustomerMasterResponse)
async def get_customer(customer_id: str, db: AsyncSession = Depends(get_db), current_user: LoginUsers = Depends(require_permission_any(['SALES', 'YARD'], 'view'))):
    stmt = select(CustomerMaster).options(
        selectinload(CustomerMaster.contacts),
        selectinload(CustomerMaster.customer_type)
    ).where(
        CustomerMaster.customer_id == customer_id,
        CustomerMaster.is_active == True
    )
    result = await db.execute(stmt)
    customer = result.scalars().first()
    if not customer:
        raise HTTPException(status_code=404, detail="Customer not found")
    return customer

@router.post("/customers", response_model=schemas.CustomerMasterResponse)
async def create_customer(
    data: schemas.CustomerMasterCreate,
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission('SALES', 'create'))
):
    if not data.customer_id:
        data.customer_id = f"CUST-{str(uuid.uuid4())[:6].upper()}"
        
    result = await db.execute(select(CustomerMaster).where(CustomerMaster.customer_id == data.customer_id))
    if result.scalars().first():
        raise HTTPException(status_code=400, detail="Customer ID already exists")

    existing_name = await db.execute(select(CustomerMaster).where(CustomerMaster.name.ilike(data.name.strip()), CustomerMaster.is_active == True))
    if existing_name.scalars().first():
        raise HTTPException(status_code=400, detail=f"A customer named '{data.name.strip()}' already exists")

    new_customer = CustomerMaster(
        customer_id=data.customer_id,
        name=data.name,
        type_code=data.type_code,
        goodwill_reason=data.goodwill_reason,
        gstin=data.gstin,
        pan=data.pan,
        billing_address=data.billing_address,
        shipping_address=data.shipping_address,
        city=data.city,
        state=data.state,
        pincode=data.pincode,
        region=data.region,
        is_active=data.is_active,
        allow_chassis_tracking=data.allow_chassis_tracking
    )
    db.add(new_customer)
    
    for contact_data in data.contacts:
        new_contact = CustomerContact(
            contact_id=contact_data.contact_id or str(uuid.uuid4()),
            customer_id=data.customer_id,
            name=contact_data.name,
            designation=contact_data.designation,
            email=contact_data.email,
            phone=contact_data.phone,
            is_primary=contact_data.is_primary,
            is_active=contact_data.is_active
        )
        db.add(new_contact)
        
    await db.commit()
    stmt = select(CustomerMaster).options(selectinload(CustomerMaster.contacts), selectinload(CustomerMaster.customer_type)).where(CustomerMaster.customer_id == new_customer.customer_id)
    res = await db.execute(stmt)
    return res.scalars().first()

@router.put("/customers/{customer_id}", response_model=schemas.CustomerMasterResponse)
async def update_customer(
    customer_id: str,
    data: schemas.CustomerMasterUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission('SALES', 'edit'))
):
    stmt = select(CustomerMaster).options(
        selectinload(CustomerMaster.contacts),
        selectinload(CustomerMaster.customer_type)
    ).where(
        CustomerMaster.customer_id == customer_id,
        CustomerMaster.is_active == True
    )
    result = await db.execute(stmt)
    customer = result.scalars().first()
    if not customer:
        raise HTTPException(status_code=404, detail="Customer not found")

    if data.name is not None and data.name.strip() != customer.name:
        existing_name = await db.execute(
            select(CustomerMaster)
            .where(CustomerMaster.name.ilike(data.name.strip()), CustomerMaster.is_active == True, CustomerMaster.customer_id != customer_id)
        )
        if existing_name.scalars().first():
            raise HTTPException(status_code=400, detail=f"A customer named '{data.name.strip()}' already exists")

    update_data = data.model_dump(exclude_unset=True, exclude={"contacts"})
    for key, value in update_data.items():
        setattr(customer, key, value)
        
    if data.contacts is not None:
        # Soft delete existing contacts (or hard delete if preferred)
        for existing in customer.contacts:
            existing.is_active = False
            
        for contact_data in data.contacts:
            new_contact = CustomerContact(
                contact_id=contact_data.contact_id or str(uuid.uuid4()),
                customer_id=customer_id,
                name=contact_data.name,
                designation=contact_data.designation,
                email=contact_data.email,
                phone=contact_data.phone,
                is_primary=contact_data.is_primary,
                is_active=contact_data.is_active
            )
            db.add(new_contact)
            
    await db.commit()
    await db.refresh(customer, ["contacts", "customer_type"])
    return customer





@router.get("/enquiries", response_model=List[schemas.SalesEnquiryResponse])
async def list_enquiries(db: AsyncSession = Depends(get_db), current_user: LoginUsers = Depends(require_permission('SALES', 'view'))):
    stmt = select(SalesEnquiry).order_by(SalesEnquiry.created_at.desc())
    result = await db.execute(stmt)
    return result.scalars().all()

@router.post("/enquiries", response_model=schemas.SalesEnquiryResponse)
async def create_enquiry(
    data: schemas.SalesEnquiryCreate,
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission('SALES', 'create'))
):
    new_enq = SalesEnquiry(**data.model_dump(), created_by=current_user.user_id)
    db.add(new_enq)
    await db.commit()
    await db.refresh(new_enq)
    return new_enq












# SPARE PARTS

@router.post("/spare-parts", response_model=schemas.SparePartsOrderResponse)
async def create_spare_parts_order(
    payload: schemas.SparePartsOrderCreate,
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission('SALES', 'create'))
):
    order = SparePartsOrder(
        customer_id=payload.customer_id,
        status="PENDING",
        total_amount=payload.total_amount,
        created_by=current_user.user_id
    )
    db.add(order)
    await db.flush()
    
    for det in payload.details:
        detail = SparePartsOrderDetail(
            order_id=order.order_id,
            rm_id=det.rm_id,
            qty=det.qty,
            unit_price=det.unit_price,
            line_amount=det.line_amount
        )
        db.add(detail)
        
    activity = SalesActivityLog(
        entity_type="SPARE_PARTS",
        entity_id=order.order_id,
        activity_type="CREATED",
        performed_by=current_user.user_id
    )
    db.add(activity)
    
    await db.commit()
    await db.refresh(order)
    
    stmt = select(SparePartsOrder).options(selectinload(SparePartsOrder.details)).where(SparePartsOrder.order_id == order.order_id)
    res = await db.execute(stmt)
    return res.scalars().first()

@router.get("/spare-parts", response_model=List[schemas.SparePartsOrderResponse])
async def list_spare_parts_orders(
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission('SALES', 'view'))
):
    stmt = select(SparePartsOrder).options(selectinload(SparePartsOrder.details)).order_by(SparePartsOrder.created_at.desc())
    result = await db.execute(stmt)
    return result.scalars().all()

@router.post("/spare-parts/{order_id}/issue", response_model=schemas.SparePartsOrderResponse)
async def issue_spare_parts_order(
    order_id: str,
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission('SALES', 'edit'))
):
    stmt = select(SparePartsOrder).options(selectinload(SparePartsOrder.details)).where(SparePartsOrder.order_id == order_id)
    result = await db.execute(stmt)
    order = result.scalars().first()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")
        
    if order.status == "ISSUED":
        raise HTTPException(status_code=400, detail="Already issued")
        
    # High value approval
    if order.total_amount > 10000 and current_user.role == "STORE_KEEPER":
        raise HTTPException(status_code=403, detail="Store Manager approval required for high value parts")
        
    main_store = (await db.execute(select(StoreMaster).where(StoreMaster.store_name == 'Main Store'))).scalars().first()
    if not main_store:
        raise HTTPException(status_code=500, detail="Main Store not configured")

    inv_service = InventoryService(db)
    
    # Issue from inventory
    try:
        for det in order.details:
            await inv_service.post_transaction(
                rm_id=det.rm_id,
                store_id=main_store.store_id,
                qty=Decimal(str(-det.qty)),
                transaction_type='ISSUE',
                reference_type='SPARE_PARTS_ORDER',
                reference_id=order.order_id,
                remarks=f"Issue by {current_user.username}"
            )
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
        
    order.status = "ISSUED"
    
    activity = SalesActivityLog(
        entity_type="SPARE_PARTS",
        entity_id=order.order_id,
        activity_type="ISSUED",
        performed_by=current_user.user_id
    )
    db.add(activity)
    
    await db.commit()
    await db.refresh(order)
    return order

# SERVICE ORDERS

@router.post("/service-orders", response_model=schemas.ServiceOrderResponse)
async def create_service_order(
    payload: schemas.ServiceOrderCreate,
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission('SALES', 'create'))
):
    order = ServiceOrder(**payload.dict())
    db.add(order)
    await db.flush()
    
    activity = SalesActivityLog(
        entity_type="SERVICE",
        entity_id=order.service_id,
        activity_type="CREATED",
        performed_by=current_user.user_id
    )
    db.add(activity)
    
    await db.commit()
    await db.refresh(order)
    return order

@router.get("/service-orders", response_model=List[schemas.ServiceOrderResponse])
async def list_service_orders(db: AsyncSession = Depends(get_db), current_user: LoginUsers = Depends(require_permission('SALES', 'view'))):
    result = await db.execute(select(ServiceOrder).order_by(ServiceOrder.created_at.desc()))
    return result.scalars().all()

@router.patch("/service-orders/{service_id}/status", response_model=schemas.ServiceOrderResponse)
async def update_service_order_status(
    service_id: str,
    status: str,
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission('SALES', 'edit'))
):
    stmt = select(ServiceOrder).where(ServiceOrder.service_id == service_id)
    result = await db.execute(stmt)
    order = result.scalars().first()
    if not order:
        raise HTTPException(status_code=404, detail="Not found")
        
    order.status = status
    
    activity = SalesActivityLog(
        entity_type="SERVICE",
        entity_id=order.service_id,
        activity_type="STATUS_UPDATE",
        new_value=status,
        performed_by=current_user.user_id
    )
    db.add(activity)
    
    await db.commit()
    await db.refresh(order)
    return order

# PAYMENTS

@router.post("/payments", response_model=schemas.CustomerPaymentLogResponse)
async def record_payment(
    payload: schemas.CustomerPaymentLogCreate,
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission('SALES', 'create'))
):
    pay = CustomerPaymentLog(
        customer_id=payload.customer_id,
        order_id=payload.order_id,
        amount=payload.amount,
        payment_mode=payload.payment_mode,
        reference_no=payload.reference_no,
        status="PENDING",
        recorded_by=current_user.user_id
    )
    db.add(pay)
    await db.commit()
    await db.refresh(pay)
    return pay

@router.post("/payments/{payment_id}/confirm", response_model=schemas.CustomerPaymentLogResponse)
async def confirm_payment(
    payment_id: str,
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission('SALES', 'approve'))
):
    stmt = select(CustomerPaymentLog).where(CustomerPaymentLog.payment_id == payment_id)
    result = await db.execute(stmt)
    pay = result.scalars().first()
    if not pay:
        raise HTTPException(status_code=404, detail="Not found")
        
    if pay.status == "CONFIRMED":
        raise HTTPException(status_code=400, detail="Already confirmed")
        
    pay.status = "CONFIRMED"
    pay.confirmed_by = current_user.user_id
    await db.commit()
    await db.refresh(pay)
    return pay

@router.get("/payments", response_model=List[schemas.CustomerPaymentLogResponse])
async def list_payments(db: AsyncSession = Depends(get_db), current_user: LoginUsers = Depends(require_permission('SALES', 'view'))):
    result = await db.execute(select(CustomerPaymentLog).order_by(CustomerPaymentLog.created_at.desc()))
    return result.scalars().all()

# GOODWILL

@router.get("/goodwill-queue", response_model=List[schemas.GoodwillApprovalLogResponse])
async def list_goodwill_queue(db: AsyncSession = Depends(get_db), current_user: LoginUsers = Depends(require_permission('SALES', 'view'))):
    result = await db.execute(select(GoodwillApprovalLog).where(GoodwillApprovalLog.status == "PENDING"))
    return result.scalars().all()

@router.post("/goodwill-queue/{approval_id}/approve", response_model=schemas.GoodwillApprovalLogResponse)
async def approve_goodwill(
    approval_id: str,
    notes: Optional[str] = None,
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission('SALES', 'approve'))
):
    stmt = select(GoodwillApprovalLog).where(GoodwillApprovalLog.approval_id == approval_id)
    result = await db.execute(stmt)
    gw = result.scalars().first()
    if not gw:
        raise HTTPException(status_code=404, detail="Not found")
        
    gw.status = "APPROVED"
    gw.manager_id = current_user.user_id
    gw.manager_notes = notes
    await db.commit()
    await db.refresh(gw)
    return gw

@router.post("/goodwill-queue/{approval_id}/reject", response_model=schemas.GoodwillApprovalLogResponse)
async def reject_goodwill(
    approval_id: str,
    notes: str, # mandatory for rejection
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission('SALES', 'approve'))
):
    if not notes:
        raise HTTPException(status_code=400, detail="Notes required for rejection")
        
    stmt = select(GoodwillApprovalLog).where(GoodwillApprovalLog.approval_id == approval_id)
    result = await db.execute(stmt)
    gw = result.scalars().first()
    if not gw:
        raise HTTPException(status_code=404, detail="Not found")
        
    gw.status = "REJECTED"
    gw.manager_id = current_user.user_id
    gw.manager_notes = notes
    await db.commit()
    await db.refresh(gw)
    return gw



@router.post('/goodwill-queue', response_model=schemas.GoodwillApprovalLogResponse)
async def create_goodwill_request(
    data: schemas.GoodwillApprovalLogCreate,
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission('SALES', 'create'))
):
    goodwill = GoodwillApprovalLog(
        approval_id=uuid.uuid4(),
        entity_type=data.entity_type,
        entity_id=data.entity_id,
        reason=data.reason,
        status='PENDING',
        requested_by=current_user.user_id
    )
    db.add(goodwill)
    await db.commit()
    await db.refresh(goodwill)
    return goodwill







@router.post("/transports", response_model=schemas.TransportMasterResponse)
async def create_transport(data: schemas.TransportMasterCreate, db: AsyncSession = Depends(get_db), current_user: LoginUsers = Depends(require_permission('SALES', 'create'))):
    payload = data.model_dump()
    if not payload.get('transport_id'):
        clean_code = "".join([c for c in payload['name'].upper() if c.isalnum()])[:15]
        payload['transport_id'] = clean_code or f"TPT-{uuid.uuid4().hex[:8].upper()}"
    new_transport = TransportMaster(**payload)
    db.add(new_transport)
    await db.commit()
    await db.refresh(new_transport)
    return new_transport

@router.put("/transports/{transport_id}", response_model=schemas.TransportMasterResponse)
async def update_transport(transport_id: str, data: schemas.TransportMasterCreate, db: AsyncSession = Depends(get_db), current_user: LoginUsers = Depends(require_permission('SALES', 'edit'))):
    result = await db.execute(select(TransportMaster).where(TransportMaster.transport_id == transport_id))
    transport = result.scalars().first()
    if not transport:
        raise HTTPException(status_code=404, detail="Transport not found")
    update_data = data.model_dump(exclude_unset=True)
    for key, value in update_data.items():
        setattr(transport, key, value)
    await db.commit()
    await db.refresh(transport)
    return transport

@router.delete("/transports/{transport_id}")
async def delete_transport(transport_id: str, db: AsyncSession = Depends(get_db), current_user: LoginUsers = Depends(require_permission('SALES', 'delete'))):
    result = await db.execute(select(TransportMaster).where(TransportMaster.transport_id == transport_id))
    transport = result.scalars().first()
    if not transport:
        raise HTTPException(status_code=404, detail="Transport not found")
    
    # Hard delete; a transporter referenced elsewhere is protected by its foreign keys.
    try:
        await db.delete(transport)
        await db.commit()
    except Exception:
        await db.rollback()
        raise HTTPException(status_code=400, detail="Cannot delete transporter (might be in use).")
    
    return {"status": "success"}

from app.models.dealer_models import DealerRequest
from app.modules.dealer_portal.schemas import DealerRequestOut

@router.get("/dealer-requests", response_model=List[DealerRequestOut])
async def admin_list_dealer_requests(
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission('SALES', 'view'))
):
    stmt = (
        select(DealerRequest, CustomerMaster.name)
        .join(CustomerMaster, CustomerMaster.customer_id == DealerRequest.dealer_id)
        .order_by(DealerRequest.created_at.desc())
    )
    result = await db.execute(stmt)
    
    out = []
    for req, name in result.all():
        req.dealer_name = name
        out.append(req)
        
    return out

@router.get("/dealer-requests/{request_id}", response_model=DealerRequestOut)
async def admin_get_dealer_request(
    request_id: uuid.UUID,
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission('SALES', 'view'))
):
    stmt = (
        select(DealerRequest, CustomerMaster.name)
        .join(CustomerMaster, CustomerMaster.customer_id == DealerRequest.dealer_id)
        .where(DealerRequest.request_id == request_id)
    )
    result = await db.execute(stmt)
    row = result.first()
    
    if not row:
        raise HTTPException(status_code=404, detail="Dealer request not found")
        
    req, name = row
    req.dealer_name = name
    return req

@router.patch("/dealer-requests/{request_id}/acknowledge", response_model=DealerRequestOut)
async def admin_acknowledge_dealer_request(
    request_id: uuid.UUID,
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission('SALES', 'edit'))
):
    req = await db.get(DealerRequest, request_id)
    if not req:
        raise HTTPException(status_code=404, detail="Request not found")
    if req.status != 'PENDING':
        raise HTTPException(status_code=400, detail="Request is already acknowledged or completed")
    
    req.status = 'ACKNOWLEDGED'
    req.acknowledged_by = current_user.user_id
    req.acknowledged_at = datetime.utcnow()
    await db.commit()
    await db.refresh(req)
    return req

# ---------------------------------------------------------
# CLIENT MASTER (Global End-Clients)
# ---------------------------------------------------------

@router.get("/clients", response_model=List[schemas.ClientMasterResponse])
async def list_clients(db: AsyncSession = Depends(get_db), current_user: LoginUsers = Depends(require_permission_any(['SALES', 'YARD'], 'view'))):
    result = await db.execute(select(ClientMaster).where(ClientMaster.is_active == True).order_by(ClientMaster.name))
    clients = result.scalars().all()
    
    # Fetch maps
    maps = await db.execute(select(DealerClientMap.client_id, DealerClientMap.dealer_id))
    client_dealers = {}
    for cid, did in maps.all():
        client_dealers.setdefault(cid, []).append(did)
        
    for c in clients:
        c.dealers = client_dealers.get(c.client_id, [])
    return clients

@router.get("/clients/{client_id}", response_model=schemas.ClientMasterResponse)
async def get_client(client_id: str, db: AsyncSession = Depends(get_db), current_user: LoginUsers = Depends(require_permission_any(['SALES', 'YARD'], 'view'))):
    client = await db.get(ClientMaster, client_id)
    if not client:
        raise HTTPException(status_code=404, detail="Client not found")
        
    maps = await db.execute(select(DealerClientMap.dealer_id).where(DealerClientMap.client_id == client_id))
    client.dealers = [r[0] for r in maps.all()]
    return client

@router.post("/clients", response_model=schemas.ClientMasterResponse)
async def create_client(data: schemas.ClientMasterCreate, db: AsyncSession = Depends(get_db), current_user: LoginUsers = Depends(require_permission('SALES', 'create'))):
    new_client = ClientMaster(
        client_id=data.client_id or f"CLI-{str(uuid.uuid4())[:6].upper()}",
        name=data.name,
        contact_person=data.contact_person,
        phone=data.phone,
        email=data.email,
        address=data.address,
        city=data.city,
        state=data.state,
        created_by_user_id=current_user.user_id
    )
    db.add(new_client)
    
    for did in data.dealer_ids:
        db.add(DealerClientMap(dealer_id=did, client_id=new_client.client_id))
        
    await db.commit()
    await db.refresh(new_client)
    new_client.dealers = data.dealer_ids
    return new_client

@router.put("/clients/{client_id}", response_model=schemas.ClientMasterResponse)
async def update_client(client_id: str, data: schemas.ClientMasterUpdate, db: AsyncSession = Depends(get_db), current_user: LoginUsers = Depends(require_permission('SALES', 'edit'))):
    client = await db.get(ClientMaster, client_id)
    if not client:
        raise HTTPException(status_code=404, detail="Client not found")
        
    update_data = data.dict(exclude_unset=True)
    dealer_ids = update_data.pop('dealer_ids', None)
    
    for key, value in update_data.items():
        setattr(client, key, value)
        
    if dealer_ids is not None:
        await db.execute(DealerClientMap.__table__.delete().where(DealerClientMap.client_id == client_id))
        for did in dealer_ids:
            db.add(DealerClientMap(dealer_id=did, client_id=client_id))
            
    await db.commit()
    await db.refresh(client)
    
    maps = await db.execute(select(DealerClientMap.dealer_id).where(DealerClientMap.client_id == client_id))
    client.dealers = [r[0] for r in maps.all()]
    return client

@router.get("/clients/{client_id}/tracking", response_model=schemas.ClientTrackingDashboardResponse)
async def get_client_tracking(client_id: str, db: AsyncSession = Depends(get_db), current_user: LoginUsers = Depends(require_permission('SALES', 'view'))):
    client = await db.get(ClientMaster, client_id)
    if not client:
        raise HTTPException(status_code=404, detail="Client not found")
        
    # Count orders
    orders = await db.execute(select(DealerOrder.status).where(DealerOrder.client_id == client_id))
    orders = [r[0] for r in orders.all()]
    
    from app.models.yard_models import ChassisMaster
    chassis = await db.execute(select(ChassisMaster.current_status).where(ChassisMaster.client_id == client_id))
    chassis = [r[0] for r in chassis.all()]
    
    return {
        "client_id": client.client_id,
        "name": client.name,
        "total_orders": len(orders),
        "pending_orders": len([o for o in orders if o not in ('CONFIRMED', 'CANCELLED')]),
        "completed_orders": len([o for o in orders if o == 'CONFIRMED']),
        "chassis_in_yard": len([c for c in chassis if c in ('YARD_PENDING_CHECK', 'YARD_CHECKED')]),
        "chassis_in_production": len([c for c in chassis if c == 'HANDED_TO_PRODUCTION']),
        "chassis_dispatched": len([c for c in chassis if c == 'DISPATCHED'])
    }

# ---------------------------------------------------------
# SALES VEHICLE TEMPLATES
# ---------------------------------------------------------

@router.get("/vehicle-templates", response_model=List[schemas.SalesVehicleTemplateResponse])
async def list_vehicle_templates(db: AsyncSession = Depends(get_db), current_user: LoginUsers = Depends(require_permission('SALES', 'view'))):
    result = await db.execute(
        select(SalesVehicleTemplate)
        .options(selectinload(SalesVehicleTemplate.features))
        .order_by(SalesVehicleTemplate.name)
    )
    return result.scalars().all()

@router.post("/vehicle-templates", response_model=schemas.SalesVehicleTemplateResponse)
async def create_vehicle_template(data: schemas.SalesVehicleTemplateCreate, db: AsyncSession = Depends(get_db), current_user: LoginUsers = Depends(require_permission('SALES', 'create'))):
    template = SalesVehicleTemplate(
        name=data.name,
        model_id=data.model_id,
        body_type_id=data.body_type_id,
        segment_id=data.segment_id,
        created_by_user_id=current_user.user_id
    )
    db.add(template)
    await db.flush()
    
    for f in data.features:
        feat = SalesVehicleTemplateFeature(
            template_id=template.template_id,
            feature_id=f.feature_id,
            feature_option_id=f.feature_option_id,
            bool_value=f.bool_value,
            text_value=f.text_value,
            image_url=f.image_url
        )
        db.add(feat)
        
    await db.commit()
    
    result = await db.execute(
        select(SalesVehicleTemplate)
        .options(selectinload(SalesVehicleTemplate.features))
        .where(SalesVehicleTemplate.template_id == template.template_id)
    )
    return result.scalars().first()

@router.put("/vehicle-templates/{template_id}", response_model=schemas.SalesVehicleTemplateResponse)
async def update_vehicle_template(template_id: uuid.UUID, data: schemas.SalesVehicleTemplateUpdate, db: AsyncSession = Depends(get_db), current_user: LoginUsers = Depends(require_permission('SALES', 'edit'))):
    result = await db.execute(select(SalesVehicleTemplate).options(selectinload(SalesVehicleTemplate.features)).filter(SalesVehicleTemplate.template_id == template_id))
    template = result.scalars().first()
    if not template:
        raise HTTPException(status_code=404, detail="Template not found")
        
    if data.name is not None:
        template.name = data.name
    
    # We allow nulling these out, so we need a way to differentiate between omitted and explicitly null.
    # But since Pydantic's Optional default is None, it's hard. For simplicity, we just set them.
    # Since our frontend always sends the full payload, we can just overwrite them.
    template.model_id = data.model_id
    template.body_type_id = data.body_type_id
    template.segment_id = data.segment_id
    
    if data.features is not None:
        # Delete existing features using direct query to avoid relationship mutation deadlocks
        await db.execute(delete(SalesVehicleTemplateFeature).where(SalesVehicleTemplateFeature.template_id == template_id))
        await db.flush()
        
        # Add new features
        for f in data.features:
            feat = SalesVehicleTemplateFeature(
                template_id=template.template_id,
                feature_id=f.feature_id,
                feature_option_id=f.feature_option_id,
                bool_value=f.bool_value,
                text_value=f.text_value,
                image_url=f.image_url
            )
            db.add(feat)
            
    await db.commit()
    
    result = await db.execute(
        select(SalesVehicleTemplate)
        .options(selectinload(SalesVehicleTemplate.features))
        .where(SalesVehicleTemplate.template_id == template.template_id)
    )
    return result.scalars().first()

@router.delete("/vehicle-templates/{template_id}")
async def delete_vehicle_template(template_id: uuid.UUID, db: AsyncSession = Depends(get_db), current_user: LoginUsers = Depends(require_permission('SALES', 'delete'))):
    template = await db.get(SalesVehicleTemplate, template_id)
    if not template:
        raise HTTPException(status_code=404, detail="Template not found")
    await db.delete(template)
    await db.commit()
    return {"message": "Template deleted"}

# ---------------------------------------------------------
# DEALER ORDER CONFIGURATION EDIT
# ---------------------------------------------------------
from app.models.dealer_models import DealerOrderVehicle, DealerOrderVehicleFeature

@router.patch("/dealer-orders/{order_id}/vehicles/{vehicle_id}/config")
async def update_dealer_order_config(
    order_id: uuid.UUID,
    vehicle_id: uuid.UUID,
    data: schemas.DealerOrderConfigUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission('SALES', 'edit'))
):
    vehicle = await db.get(DealerOrderVehicle, vehicle_id)
    if not vehicle or vehicle.order_id != order_id:
        raise HTTPException(status_code=404, detail="Vehicle not found in order")
        
    old_features = (await db.execute(
        select(DealerOrderVehicleFeature)
        .where(DealerOrderVehicleFeature.order_vehicle_id == vehicle_id)
    )).scalars().all()
    old_f_map = {str(f.feature_id): f for f in old_features}

    edited_fields = vehicle.edited_fields or []
    if str(vehicle.body_type_id) != str(data.body_type_id) and "body_type" not in edited_fields:
        edited_fields.append("body_type")
    if str(vehicle.segment_id) != str(data.segment_id) and "segment" not in edited_fields:
        edited_fields.append("segment")
    if str(vehicle.model_id) != str(data.model_id) and "model" not in edited_fields:
        edited_fields.append("model")

    for pf in data.features:
        old_f = old_f_map.get(str(pf.feature_id))
        is_f_edited = False
        if not old_f:
            is_f_edited = True
        elif str(old_f.feature_option_id or "") != str(pf.feature_option_id or "") or bool(old_f.bool_value) != bool(pf.bool_value) or str(old_f.text_value or "") != str(pf.text_value or "") or str(old_f.image_url or "") != str(pf.image_url or ""):
            is_f_edited = True
        
        if is_f_edited and str(pf.feature_id) not in edited_fields:
            edited_fields.append(str(pf.feature_id))

    vehicle.edited_fields = edited_fields

    vehicle.model_id = data.model_id
    vehicle.body_type_id = data.body_type_id
    vehicle.segment_id = data.segment_id
    vehicle.is_edited = True
        
    # Delete old features
    await db.execute(DealerOrderVehicleFeature.__table__.delete().where(DealerOrderVehicleFeature.order_vehicle_id == vehicle_id))
    
    # Append remark
    order = await db.get(DealerOrder, order_id)
    if order:
        edited_names = []
        feature_ids_to_fetch = []
        for field in edited_fields:
            if field == 'model': edited_names.append('Model')
            elif field == 'body_type': edited_names.append('Body Type')
            elif field == 'segment': edited_names.append('Segment')
            elif field == 'qty': edited_names.append('Quantity')
            else:
                try:
                    import uuid
                    feature_ids_to_fetch.append(uuid.UUID(field))
                except:
                    pass
        
        if feature_ids_to_fetch:
            from app.models.dealer_models import SpecialFeatureMaster
            feats = (await db.execute(select(SpecialFeatureMaster).where(SpecialFeatureMaster.feature_id.in_(feature_ids_to_fetch)))).scalars().all()
            for f in feats:
                edited_names.append(f.feature_name)
                
        edited_str = f" ({', '.join(edited_names)})" if edited_names else ""
        new_remark = f"Salesperson edited Config {vehicle.sequence_no + 1}{edited_str}."
        if not order.remarks:
            order.remarks = new_remark
        elif new_remark not in order.remarks:
            order.remarks += f"\n{new_remark}"
    
    # Add new features
    for f in data.features:
        feat = DealerOrderVehicleFeature(
            order_vehicle_id=vehicle_id,
            feature_id=f.feature_id,
            feature_option_id=f.feature_option_id,
            bool_value=f.bool_value,
            text_value=f.text_value,
            image_url=f.image_url
        )
        db.add(feat)
        
    await db.commit()
    return {"message": "Configuration updated successfully"}


# ─── Induction Ready Routes ──────────────────────────────────────────

@router.get("/induction-ready", response_model=List[schemas.InductionReadyChassisOut])
async def list_induction_ready_chassis(
    dealer_id: Optional[str] = None,
    da_lite_status: Optional[str] = None,
    schedule_filter: Optional[str] = Query(None, description="ALL / TODAY / TOMORROW / UPCOMING / UNSCHEDULED / YYYY-MM-DD"),
    search: Optional[str] = None,
    include_inducted: bool = False,
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission_any(['SALES', 'DEALER_PORTAL_ADMIN', 'YARD', 'PRODUCTION'], 'view')),
):
    """
    Returns all individual chassis that meet the two Induction Ready criteria:
    1. Has an active dealer order / build order associated with it.
    2. Is available in the yard (inwarded, arrival_date is set).
    Also supports filtering by schedule status (TODAY, TOMORROW, UPCOMING, UNSCHEDULED).
    """
    from datetime import date, timedelta
    from app.models.yard_models import ChassisMaster, VehicleModelMaster
    from app.models.dealer_models import (
        DealerOrder, DealerOrderVehicle, DealerOrderVehicleChassis,
        DealerOrderVehicleFeature, BodyTypeMaster, CustomerSegmentMaster,
        SpecialFeatureMaster, SpecialFeatureOptionMaster
    )

    today = date.today()
    tomorrow = today + timedelta(days=1)

    # Base chassis query for inwarded chassis
    stmt = (
        select(ChassisMaster)
        .where(ChassisMaster.arrival_date.isnot(None))
    )

    if not include_inducted:
        stmt = stmt.where(
            ChassisMaster.current_status != 'HANDED_TO_PRODUCTION',
            ChassisMaster.tracking_status.notin_(['WIP', 'READY_FOR_DISPATCH', 'DISPATCHED'])
        )

    if dealer_id:
        stmt = stmt.where(ChassisMaster.dealer_id == dealer_id)

    if da_lite_status:
        stmt = stmt.where(ChassisMaster.da_lite_status == da_lite_status)

    chassis_list = (await db.execute(stmt.order_by(ChassisMaster.arrival_date.desc(), ChassisMaster.created_at.desc()))).scalars().all()

    if not chassis_list:
        return []

    # Map chassis to DealerOrderVehicle for CONFIRMED orders
    chassis_nos = [c.chassis_no.strip() for c in chassis_list if c.chassis_no]
    chassis_to_dovc: dict[str, DealerOrderVehicleChassis] = {}
    if chassis_nos:
        dovc_rows = (await db.execute(
            select(DealerOrderVehicleChassis)
            .join(DealerOrderVehicle, DealerOrderVehicleChassis.order_vehicle_id == DealerOrderVehicle.order_vehicle_id)
            .join(DealerOrder, DealerOrderVehicle.order_id == DealerOrder.order_id)
            .where(
                DealerOrderVehicleChassis.chassis_no.in_(chassis_nos),
                DealerOrder.status == 'CONFIRMED'
            )
        )).scalars().all()
        for dovc in dovc_rows:
            if dovc.chassis_no:
                chassis_to_dovc[dovc.chassis_no.strip()] = dovc

    # Collect all needed DealerOrderVehicle IDs from direct link or confirmed DOVC
    ov_ids_set = set()
    for c in chassis_list:
        if c.dealer_order_vehicle_id:
            ov_ids_set.add(c.dealer_order_vehicle_id)
        c_no_clean = c.chassis_no.strip() if c.chassis_no else ''
        if c_no_clean in chassis_to_dovc:
            ov_ids_set.add(chassis_to_dovc[c_no_clean].order_vehicle_id)

    # Load DealerOrderVehicle details (ONLY for CONFIRMED orders)
    ov_map = {}
    if ov_ids_set:
        ov_stmt = (
            select(
                DealerOrderVehicle,
                DealerOrder.order_no,
                DealerOrder.status.label('order_status'),
                DealerOrder.dealer_id.label('order_dealer_id'),
                DealerOrder.client_id.label('order_client_id'),
                CustomerMaster.name.label('dealer_name'),
                ClientMaster.name.label('client_name'),
                VehicleModelMaster.model_number.label('model_name'),
                BodyTypeMaster.name.label('body_type_name'),
                CustomerSegmentMaster.name.label('segment_name'),
            )
            .join(DealerOrder, DealerOrderVehicle.order_id == DealerOrder.order_id)
            .outerjoin(CustomerMaster, DealerOrder.dealer_id == CustomerMaster.customer_id)
            .outerjoin(ClientMaster, DealerOrder.client_id == ClientMaster.client_id)
            .outerjoin(VehicleModelMaster, DealerOrderVehicle.model_id == VehicleModelMaster.model_id)
            .outerjoin(BodyTypeMaster, DealerOrderVehicle.body_type_id == BodyTypeMaster.body_type_id)
            .outerjoin(CustomerSegmentMaster, DealerOrderVehicle.segment_id == CustomerSegmentMaster.segment_id)
            .where(
                DealerOrderVehicle.order_vehicle_id.in_(list(ov_ids_set)),
                DealerOrder.status == 'CONFIRMED'
            )
        )
        for row in (await db.execute(ov_stmt)).all():
            ov = row[0]
            ov_map[ov.order_vehicle_id] = {
                'ov': ov,
                'order_no': row.order_no,
                'order_status': row.order_status,
                'dealer_id': row.order_dealer_id,
                'client_id': row.order_client_id,
                'dealer_name': row.dealer_name,
                'client_name': row.client_name,
                'model_name': row.model_name,
                'body_type_name': row.body_type_name,
                'segment_name': row.segment_name,
            }

    # Load features for these order vehicles
    features_summary_map: dict[uuid.UUID, str] = {}
    if ov_ids_set:
        feat_stmt = (
            select(
                DealerOrderVehicleFeature.order_vehicle_id,
                SpecialFeatureMaster.feature_name,
                SpecialFeatureMaster.input_type,
                SpecialFeatureOptionMaster.option_value,
                DealerOrderVehicleFeature.bool_value,
                DealerOrderVehicleFeature.text_value,
            )
            .join(SpecialFeatureMaster, DealerOrderVehicleFeature.feature_id == SpecialFeatureMaster.feature_id)
            .outerjoin(SpecialFeatureOptionMaster, DealerOrderVehicleFeature.feature_option_id == SpecialFeatureOptionMaster.option_id)
            .where(DealerOrderVehicleFeature.order_vehicle_id.in_(list(ov_ids_set)))
        )
        feat_items: dict[uuid.UUID, list[str]] = {}
        for r in (await db.execute(feat_stmt)).all():
            val = None
            if r.input_type == 'DROPDOWN' and r.option_value:
                val = f"{r.feature_name}: {r.option_value}"
            elif r.input_type == 'BOOLEAN' and r.bool_value:
                val = r.feature_name
            elif r.input_type == 'TEXT' and r.text_value:
                val = f"{r.feature_name}: {r.text_value}"
            if val:
                feat_items.setdefault(r.order_vehicle_id, []).append(val)
        for ov_id, items in feat_items.items():
            features_summary_map[ov_id] = " • ".join(items[:5])

    # Model master lookup for chassis fallback
    all_models = dict((await db.execute(select(VehicleModelMaster.model_id, VehicleModelMaster.model_number))).all())
    all_dealers = dict((await db.execute(select(CustomerMaster.customer_id, CustomerMaster.name))).all())
    all_clients = dict((await db.execute(select(ClientMaster.client_id, ClientMaster.name))).all())

    results = []
    for c in chassis_list:
        c_no_clean = c.chassis_no.strip() if c.chassis_no else ''
        dovc = chassis_to_dovc.get(c_no_clean)

        # Match to confirmed order vehicle ID (direct or via confirmed DOVC)
        ov_id = None
        if c.dealer_order_vehicle_id and c.dealer_order_vehicle_id in ov_map:
            ov_id = c.dealer_order_vehicle_id
        elif dovc and dovc.order_vehicle_id in ov_map:
            ov_id = dovc.order_vehicle_id

        # Production schedule criteria: Must have an active CONFIRMED order
        if not ov_id:
            # Skip chassis that do not have a confirmed order
            continue

        ov_info = ov_map.get(ov_id, {})
        if ov_info.get('order_status') != 'CONFIRMED':
            continue

        # Schedule Filter Check
        if isinstance(schedule_filter, str) and schedule_filter.strip():
            sf = schedule_filter.strip().upper()
            if sf == 'TODAY':
                if c.scheduled_induction_date != today:
                    continue
            elif sf == 'TOMORROW':
                if c.scheduled_induction_date != tomorrow:
                    continue
            elif sf == 'UPCOMING':
                if not (c.scheduled_induction_date and c.scheduled_induction_date > today):
                    continue
            elif sf == 'UNSCHEDULED':
                if c.scheduled_induction_date is not None:
                    continue
            elif sf != 'ALL':
                # Custom ISO date filter
                try:
                    target_d = date.fromisoformat(schedule_filter.strip())
                    if c.scheduled_induction_date != target_d:
                        continue
                except ValueError:
                    pass

        ov_info = ov_map.get(ov_id, {})
        ov = ov_info.get('ov')

        dealer_id_val = ov_info.get('dealer_id') or c.dealer_id
        dealer_name_val = ov_info.get('dealer_name') or all_dealers.get(dealer_id_val)
        client_id_val = ov_info.get('client_id') or c.client_id
        client_name_val = ov_info.get('client_name') or all_clients.get(client_id_val)

        days_in_yard = (today - c.arrival_date).days if c.arrival_date else 0

        item = schemas.InductionReadyChassisOut(
            chassis_id=c.chassis_id,
            chassis_no=c.chassis_no,
            order_id=ov.order_id if ov else None,
            order_no=ov_info.get('order_no'),
            order_vehicle_id=ov_id,
            dealer_id=dealer_id_val,
            dealer_name=dealer_name_val,
            client_id=client_id_val,
            client_name=client_name_val,
            model_id=ov.model_id if ov else c.model_id,
            model_name=ov_info.get('model_name') or all_models.get(c.model_id),
            body_type_id=ov.body_type_id if ov else None,
            body_type_name=ov_info.get('body_type_name'),
            segment_id=ov.segment_id if ov else None,
            segment_name=ov_info.get('segment_name'),
            fuel_type=c.fuel_type,
            light_type=c.light_type,
            yard_location=c.yard_location,
            procurement_status=c.procurement_status,
            arrival_date=c.arrival_date,
            days_in_yard=days_in_yard,
            da_lite_status=c.da_lite_status or 'PENDING',
            da_lite_photo_url=c.da_lite_photo_url,
            current_status=c.current_status,
            tracking_status=c.tracking_status or 'CHASSIS_IN',
            expected_delivery_date=dovc.expected_delivery_date if dovc else None,
            order_status=ov_info.get('order_status'),
            order_qty=ov.qty if ov else 1,
            scheduled_induction_date=c.scheduled_induction_date,
            scheduled_at=c.scheduled_at,
            scheduled_by=c.scheduled_by,
            features_summary=features_summary_map.get(ov_id) if ov_id else None,
            remarks=c.remarks,
        )

        # Apply search filter if present
        if search:
            q = search.strip().lower()
            haystack = f"{item.chassis_no} {item.order_no or ''} {item.dealer_name or ''} {item.client_name or ''} {item.model_name or ''} {item.body_type_name or ''} {item.yard_location or ''}".lower()
            if q not in haystack:
                continue

        results.append(item)

    return results


@router.get("/induction-ready/summary", response_model=schemas.InductionReadySummaryOut)
async def get_induction_ready_summary(
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission_any(['SALES', 'DEALER_PORTAL_ADMIN', 'YARD', 'PRODUCTION'], 'view')),
):
    """
    Computes top KPIs for the Induction Ready dashboard including scheduling counts.
    """
    from datetime import date, timedelta
    today = date.today()
    tomorrow = today + timedelta(days=1)

    items = await list_induction_ready_chassis(db=db, current_user=current_user)
    total = len(items)
    dealers = {i.dealer_id for i in items if i.dealer_id}
    avg_ageing = sum(i.days_in_yard or 0 for i in items) / total if total > 0 else 0.0
    da_lite_ok = sum(1 for i in items if i.da_lite_status == 'OK')

    today_count = sum(1 for i in items if i.scheduled_induction_date == today)
    tomorrow_count = sum(1 for i in items if i.scheduled_induction_date == tomorrow)
    upcoming_count = sum(1 for i in items if i.scheduled_induction_date and i.scheduled_induction_date > today)
    unscheduled_count = sum(1 for i in items if i.scheduled_induction_date is None)

    return schemas.InductionReadySummaryOut(
        total_induction_ready=total,
        total_dealers=len(dealers),
        avg_yard_ageing_days=round(avg_ageing, 1),
        da_lite_ok_count=da_lite_ok,
        scheduled_today_count=today_count,
        scheduled_tomorrow_count=tomorrow_count,
        scheduled_upcoming_count=upcoming_count,
        unscheduled_count=unscheduled_count,
    )


@router.get("/induction-ready/schedule-plan", response_model=schemas.SchedulePlanSummaryOut)
async def get_induction_schedule_plan(
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission_any(['SALES', 'DEALER_PORTAL_ADMIN', 'YARD', 'PRODUCTION'], 'view')),
):
    """
    Returns the daily planned breakdown and scheduling summary for upcoming induction handovers.
    """
    from datetime import date, timedelta
    today = date.today()
    tomorrow = today + timedelta(days=1)

    items = await list_induction_ready_chassis(db=db, current_user=current_user)
    total_ready = len(items)

    today_count = 0
    tomorrow_count = 0
    upcoming_count = 0
    unscheduled_count = 0

    # Group items by scheduled date
    date_map: dict[date, list[uuid.UUID]] = {}
    for item in items:
        s_date = item.scheduled_induction_date
        if s_date is None:
            unscheduled_count += 1
        else:
            date_map.setdefault(s_date, []).append(item.chassis_id)
            if s_date == today:
                today_count += 1
            elif s_date == tomorrow:
                tomorrow_count += 1
            if s_date >= today:
                if s_date > today:
                    upcoming_count += 1

    # Prepare daily buckets for at least next 7 days or any dates in date_map
    all_dates = set(date_map.keys())
    for i in range(7):
        all_dates.add(today + timedelta(days=i))

    daily_breakdown = []
    for d in sorted(all_dates):
        if d < today and not date_map.get(d):
            continue
        c_ids = date_map.get(d, [])
        if d == today:
            label = "Today"
        elif d == tomorrow:
            label = "Tomorrow"
        else:
            label = d.strftime("%a, %d %b")

        daily_breakdown.append(
            schemas.InductionDateBucket(
                date=d,
                date_label=label,
                count=len(c_ids),
                chassis_ids=c_ids,
            )
        )

    return schemas.SchedulePlanSummaryOut(
        today_count=today_count,
        tomorrow_count=tomorrow_count,
        upcoming_count=upcoming_count,
        unscheduled_count=unscheduled_count,
        total_ready_count=total_ready,
        daily_breakdown=daily_breakdown,
    )


@router.post("/induction-ready/schedule")
async def schedule_chassis_induction(
    payload: schemas.ScheduleInductionRequest,
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission_any(['SALES', 'DEALER_PORTAL_ADMIN', 'YARD', 'PRODUCTION'], 'edit')),
):
    """
    Assigns or clears a planned induction date for selected chassis.
    """
    from datetime import datetime, timezone
    from app.models.yard_models import ChassisMaster

    if not payload.chassis_ids:
        raise HTTPException(status_code=400, detail="No chassis IDs provided")

    now = datetime.now(timezone.utc)
    updated_count = 0

    for c_id in payload.chassis_ids:
        chassis = await db.get(ChassisMaster, c_id)
        if not chassis:
            continue

        if chassis.current_status == 'HANDED_TO_PRODUCTION':
            continue

        # Criteria check: Must have an active CONFIRMED order
        from app.models.dealer_models import DealerOrder, DealerOrderVehicle, DealerOrderVehicleChassis
        is_confirmed = False
        if chassis.dealer_order_vehicle_id:
            ord_status = (await db.execute(
                select(DealerOrder.status)
                .join(DealerOrderVehicle, DealerOrder.order_id == DealerOrderVehicle.order_id)
                .where(DealerOrderVehicle.order_vehicle_id == chassis.dealer_order_vehicle_id)
            )).scalar_one_or_none()
            if ord_status == 'CONFIRMED':
                is_confirmed = True
        if not is_confirmed and chassis.chassis_no:
            dovc_ord = (await db.execute(
                select(DealerOrder.status)
                .join(DealerOrderVehicle, DealerOrderVehicleChassis.order_vehicle_id == DealerOrderVehicle.order_vehicle_id)
                .join(DealerOrder, DealerOrderVehicle.order_id == DealerOrder.order_id)
                .where(DealerOrderVehicleChassis.chassis_no == chassis.chassis_no.strip())
            )).scalars().all()
            if 'CONFIRMED' in dovc_ord:
                is_confirmed = True

        if not is_confirmed:
            continue

        chassis.scheduled_induction_date = payload.scheduled_induction_date
        chassis.scheduled_at = now
        chassis.scheduled_by = current_user.user_id
        if payload.notes:
            chassis.remarks = f"{chassis.remarks or ''}\n[Schedule Plan]: {payload.notes}".strip()
        updated_count += 1

    await db.commit()

    date_str = payload.scheduled_induction_date.strftime("%d %b %Y") if payload.scheduled_induction_date else "Unscheduled"
    return {
        "success": True,
        "updated_count": updated_count,
        "message": f"Successfully planned {updated_count} chassis for {date_str}."
    }


@router.post("/induction-ready/auto-distribute")
async def auto_distribute_induction_schedule(
    payload: schemas.BatchAutoDistributeRequest,
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission_any(['SALES', 'DEALER_PORTAL_ADMIN', 'YARD', 'PRODUCTION'], 'edit')),
):
    """
    Distributes a batch of chassis across specified dates (e.g. 20 Today, 30 Tomorrow, 10 Day After).
    If chassis_ids is not provided, allocates from available unscheduled ready chassis.
    """
    from datetime import datetime, timezone
    from app.models.yard_models import ChassisMaster

    if not payload.distributions:
        raise HTTPException(status_code=400, detail="No date distributions provided")

    total_requested = sum(d.count for d in payload.distributions if d.count > 0)
    if total_requested <= 0:
        raise HTTPException(status_code=400, detail="Total distributed count must be greater than 0")

    candidate_ids = []
    if payload.chassis_ids:
        candidate_ids = list(payload.chassis_ids)
    else:
        ready_items = await list_induction_ready_chassis(dealer_id=payload.dealer_id, schedule_filter='UNSCHEDULED', db=db, current_user=current_user)
        candidate_ids = [i.chassis_id for i in ready_items]

    if not candidate_ids:
        raise HTTPException(status_code=400, detail="No eligible chassis available for distribution")

    now = datetime.now(timezone.utc)
    curr_idx = 0
    total_scheduled = 0

    for dist in payload.distributions:
        if dist.count <= 0 or curr_idx >= len(candidate_ids):
            continue

        target_slice = candidate_ids[curr_idx : curr_idx + dist.count]
        curr_idx += len(target_slice)

        for c_id in target_slice:
            chassis = await db.get(ChassisMaster, c_id)
            if not chassis or chassis.current_status == 'HANDED_TO_PRODUCTION':
                continue
            chassis.scheduled_induction_date = dist.date
            chassis.scheduled_at = now
            chassis.scheduled_by = current_user.user_id
            total_scheduled += 1

    await db.commit()

    return {
        "success": True,
        "scheduled_count": total_scheduled,
        "message": f"Successfully planned {total_scheduled} chassis across {len(payload.distributions)} scheduled dates."
    }


@router.get("/induction-ready/stations")
async def list_induction_stations(
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission_any(['SALES', 'DEALER_PORTAL_ADMIN', 'YARD', 'PRODUCTION'], 'view')),
):
    """
    Returns active stations from StationMaster for production station assignment.
    """
    from app.models.rm_models import StationMaster
    stations = (await db.execute(
        select(StationMaster)
        .where(StationMaster.is_active == True)
        .order_by(StationMaster.sequence_no)
    )).scalars().all()
    return [
        {
            "station_id": str(s.station_id),
            "station_code": s.station_code,
            "station_name": s.station_name,
            "sequence_no": s.sequence_no or 0,
        }
        for s in stations
    ]


@router.post("/induction-ready/induct", response_model=schemas.InductToProductionResponse)
async def induct_chassis_to_production(
    payload: schemas.InductToProductionRequest,
    db: AsyncSession = Depends(get_db),
    current_user: LoginUsers = Depends(require_permission_any(['SALES', 'DEALER_PORTAL_ADMIN', 'YARD', 'PRODUCTION'], 'approve')),
):
    """
    Inducts selected chassis into production:
    - Sets current_status = 'HANDED_TO_PRODUCTION'
    - Sets handed_to_production_at & handed_to_production_by
    - Note: tracking_status remains 'CHASSIS_IN' until scanned by the station operator
    - Logs event in ChassisStatusLog
    - Initializes/updates ProductionReportManualEntry with Line/Contractor group & remarks
    """
    from datetime import datetime, timezone
    from app.models.yard_models import ChassisMaster, ChassisStatusLog, ChassisMovementLog
    from app.models.production_report_models import ProductionReportManualEntry

    target_chassis_ids = list(payload.chassis_ids or [])

    # If scheduled_date is provided and no chassis_ids, gather all chassis planned for that date
    if not target_chassis_ids and payload.scheduled_date:
        items = await list_induction_ready_chassis(
            schedule_filter=payload.scheduled_date.isoformat(),
            db=db,
            current_user=current_user
        )
        target_chassis_ids = [i.chassis_id for i in items]

    if not target_chassis_ids:
        raise HTTPException(status_code=400, detail="No chassis IDs or scheduled batch found for induction")

    now = datetime.now(timezone.utc)
    inducted_count = 0

    for c_id in target_chassis_ids:
        chassis = await db.get(ChassisMaster, c_id)
        if not chassis:
            continue

        if chassis.current_status == 'HANDED_TO_PRODUCTION':
            continue

        # Strict Dual-Criteria Validation:
        # Criteria 1: Must be in yard (arrival_date is set)
        if not chassis.arrival_date:
            raise HTTPException(
                status_code=400,
                detail=f"Chassis {chassis.chassis_no} has not arrived in the yard yet. Both order and yard arrival criteria must be satisfied."
            )

        # Criteria 2: Must have an active CONFIRMED order
        from app.models.dealer_models import DealerOrder, DealerOrderVehicle, DealerOrderVehicleChassis
        confirmed_order_exists = False
        target_ov_id = chassis.dealer_order_vehicle_id
        if target_ov_id:
            ord_res = (await db.execute(
                select(DealerOrder.status)
                .join(DealerOrderVehicle, DealerOrder.order_id == DealerOrderVehicle.order_id)
                .where(DealerOrderVehicle.order_vehicle_id == target_ov_id)
            )).scalar_one_or_none()
            if ord_res == 'CONFIRMED':
                confirmed_order_exists = True

        if not confirmed_order_exists and chassis.chassis_no:
            dovc_ord = (await db.execute(
                select(DealerOrder.status, DealerOrderVehicleChassis.order_vehicle_id)
                .join(DealerOrderVehicle, DealerOrderVehicleChassis.order_vehicle_id == DealerOrderVehicle.order_vehicle_id)
                .join(DealerOrder, DealerOrderVehicle.order_id == DealerOrder.order_id)
                .where(DealerOrderVehicleChassis.chassis_no == chassis.chassis_no.strip())
                .order_by(case((DealerOrder.status == 'CONFIRMED', 1), else_=2))
            )).first()
            if dovc_ord and dovc_ord[0] == 'CONFIRMED':
                confirmed_order_exists = True
                if not chassis.dealer_order_vehicle_id:
                    chassis.dealer_order_vehicle_id = dovc_ord[1]

        if not confirmed_order_exists:
            raise HTTPException(
                status_code=400,
                detail=f"Chassis {chassis.chassis_no} does not have a confirmed order attached (status must be CONFIRMED). Only confirmed orders can be inducted into production."
            )

        chassis.current_status = 'HANDED_TO_PRODUCTION'
        chassis.procurement_status = 'PROCUREMENT_PENDING'
        chassis.handed_to_production_at = now
        chassis.handed_to_production_by = current_user.user_id

        if payload.initial_station_id:
            chassis.current_station_id = payload.initial_station_id

        # Close open movement leg if any
        open_leg = (await db.execute(
            select(ChassisMovementLog)
            .where(ChassisMovementLog.chassis_id == c_id, ChassisMovementLog.out_time.is_(None))
            .order_by(ChassisMovementLog.created_at.desc())
            .limit(1)
        )).scalar_one_or_none()
        if open_leg:
            open_leg.out_time = datetime.now().time()

        # Audit log in ChassisStatusLog
        db.add(ChassisStatusLog(
            chassis_id=c_id,
            station_id=payload.initial_station_id or chassis.current_station_id,
            scan_mode='MANUAL_ENTRY',
            from_status=chassis.tracking_status or 'CHASSIS_IN',
            to_status=chassis.tracking_status or 'CHASSIS_IN',
            changed_by=current_user.user_id,
            scan_location='PRODUCTION',
            remarks=payload.remarks or 'Inducted to production via Sales Induction Ready'
        ))

        # Upsert ProductionReportManualEntry for production sheet
        manual_entry = (await db.execute(
            select(ProductionReportManualEntry).where(ProductionReportManualEntry.chassis_id == c_id)
        )).scalar_one_or_none()

        if not manual_entry:
            manual_entry = ProductionReportManualEntry(
                chassis_id=c_id,
                contractor_group=payload.contractor_group,
                ac_make=payload.ac_make,
                interior_work=payload.interior_work,
                remark=payload.remarks,
            )
            db.add(manual_entry)
        else:
            if payload.contractor_group:
                manual_entry.contractor_group = payload.contractor_group
            if payload.ac_make:
                manual_entry.ac_make = payload.ac_make
            if payload.interior_work:
                manual_entry.interior_work = payload.interior_work
            if payload.remarks:
                manual_entry.remark = payload.remarks

        inducted_count += 1

    await db.commit()

    return schemas.InductToProductionResponse(
        success=True,
        inducted_count=inducted_count,
        message=f"Successfully sent {inducted_count} chassis into production."
    )


