from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import func
from sqlalchemy.orm import Session
from typing import Optional
from datetime import datetime, timezone

from database import get_db
from models.business_day import BusinessDay
from models.order import Order, OrderItem, OrderAuditLog
from models.shift import WaiterShift
from models.flag import TableFlagAssignment
from models.message import StaffMessage, StaffMessageAck
from schemas.business_day import BusinessDayOut, OpenBusinessDayRequest, CloseBusinessDayRequest
from routers.deps import get_current_user, require_manager
from models.user import User

router = APIRouter()


def _dt(dt):
    if dt is None:
        return None
    return (dt.isoformat() + "Z") if dt.tzinfo is None else dt.isoformat()


@router.get("/current", response_model=Optional[BusinessDayOut])
def get_current_business_day(
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    return db.query(BusinessDay).filter(BusinessDay.status == "open").first()


@router.post("/open", response_model=BusinessDayOut, status_code=status.HTTP_201_CREATED)
def open_business_day(
    body: OpenBusinessDayRequest,
    db: Session = Depends(get_db),
    user: User = Depends(require_manager),
):
    existing = db.query(BusinessDay).filter(BusinessDay.status == "open").first()
    if existing:
        raise HTTPException(status_code=400, detail="A business day is already open")
    day = BusinessDay(opened_by_id=user.id, notes=body.notes)
    db.add(day)
    db.commit()
    db.refresh(day)
    return day


@router.post("/close", response_model=BusinessDayOut)
def close_business_day(
    body: CloseBusinessDayRequest,
    db: Session = Depends(get_db),
    user: User = Depends(require_manager),
):
    day = db.query(BusinessDay).filter(BusinessDay.status == "open").first()
    if not day:
        raise HTTPException(status_code=404, detail="No open business day")

    open_orders = db.query(Order).filter(
        Order.business_day_id == day.id,
        Order.status.in_(["open", "partially_paid"]),
    ).all()

    if open_orders and not body.force:
        # Count orders that have at least one active (unpaid) item — covers both
        # "open" (fully unpaid) and "partially_paid" (partially unpaid) orders.
        with_pending = sum(
            1 for o in open_orders
            if db.query(OrderItem).filter(
                OrderItem.order_id == o.id,
                OrderItem.status == "active",
            ).first() is not None
        )
        raise HTTPException(
            status_code=409,
            detail={
                "message": f"{len(open_orders)} table(s) still open, {with_pending} with unpaid items.",
                "open_orders": len(open_orders),
                "partially_paid": with_pending,
            },
        )

    now = datetime.now(timezone.utc)

    # Close all non-terminal orders for this business day (open, partially_paid, paid)
    all_unclosed = db.query(Order).filter(
        Order.business_day_id == day.id,
        Order.status.in_(["open", "partially_paid", "paid"]),
    ).all()
    for order in all_unclosed:
        was_unpaid = order.status in ("open", "partially_paid")
        order.status = "closed"
        order.closed_at = now
        order.closed_by = user.id
        if was_unpaid:
            db.add(OrderAuditLog(
                order_id=order.id,
                event_type="ORDER_CLOSED",
                waiter_id=user.id,
                note="Force-closed at end of business day",
            ))

    active_shifts = db.query(WaiterShift).filter(
        WaiterShift.business_day_id == day.id,
        WaiterShift.ended_at == None,
    ).all()
    for shift in active_shifts:
        items = db.query(OrderItem).filter(
            OrderItem.paid_in_shift_id == shift.id,
            OrderItem.status == "paid",
        ).all()
        shift.total_collected = sum(i.unit_price * i.quantity for i in items)
        shift.ended_at = now

    # Clear all table flags and staff messages — fresh slate for the next day
    db.query(TableFlagAssignment).delete(synchronize_session=False)
    db.query(StaffMessageAck).delete(synchronize_session=False)
    db.query(StaffMessage).delete(synchronize_session=False)

    day.status = "closed"
    day.closed_at = now
    day.closed_by_id = user.id
    if body.notes:
        day.notes = body.notes

    db.commit()
    db.refresh(day)
    return day


@router.get("/history")
def business_day_history(
    db: Session = Depends(get_db),
    user: User = Depends(require_manager),
):
    days = db.query(BusinessDay).order_by(BusinessDay.opened_at.desc()).all()
    result = []
    for day in days:
        order_count = db.query(Order).filter(Order.business_day_id == day.id).count()
        revenue = (
            db.query(func.sum(OrderItem.unit_price * OrderItem.quantity))
            .join(Order)
            .filter(Order.business_day_id == day.id, OrderItem.status == "paid")
            .scalar() or 0.0
        )
        w_opener = day.opener
        w_closer = day.closer
        result.append({
            "id": day.id,
            "status": day.status,
            "opened_at": _dt(day.opened_at),
            "closed_at": _dt(day.closed_at),
            "opened_by_id": day.opened_by_id,
            "opened_by_name": (w_opener.full_name or w_opener.username) if w_opener else None,
            "closed_by_id": day.closed_by_id,
            "closed_by_name": (w_closer.full_name or w_closer.username) if w_closer else None,
            "notes": day.notes,
            "order_count": order_count,
            "revenue": round(revenue, 2),
        })
    return {"business_days": result}
