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

from database import get_db
from models.shift import WaiterShift, ShiftBreak
from models.business_day import BusinessDay
from models.order import OrderItem
from models.settings import PosSettings
from models.user import User
from schemas.shift import StartShiftRequest, EndShiftRequest
from routers.deps import get_current_user, require_manager

router = APIRouter()


def _dt(dt):
    """Serialize a naive-UTC datetime to ISO string with Z so JS parses it as UTC."""
    if dt is None:
        return None
    return (dt.isoformat() + "Z") if dt.tzinfo is None else dt.isoformat()


def _get_setting(db: Session, key: str, default: str = "true") -> str:
    s = db.query(PosSettings).filter(PosSettings.key == key).first()
    return s.value if s else default


def compute_shift_total(shift_id: int, db: Session) -> float:
    items = db.query(OrderItem).filter(
        OrderItem.paid_in_shift_id == shift_id,
        OrderItem.status == "paid",
    ).all()
    return round(sum(i.unit_price * i.quantity for i in items), 2)


def _enrich_shift(shift: WaiterShift, db: Session) -> dict:
    w = shift.waiter
    wname = (w.full_name or w.username) if w else f"#{shift.waiter_id}"
    total = compute_shift_total(shift.id, db) if shift.ended_at is None else (shift.total_collected or 0.0)
    return {
        "id": shift.id,
        "waiter_id": shift.waiter_id,
        "waiter_name": wname,
        "business_day_id": shift.business_day_id,
        "started_at": _dt(shift.started_at),
        "ended_at": _dt(shift.ended_at),
        "starting_cash": shift.starting_cash,
        "total_collected": total,
        "net_to_deliver": round(total + (shift.starting_cash or 0.0), 2),
        "is_active": shift.ended_at is None,
        "notes": shift.notes,
        "breaks": [
            {"id": b.id, "shift_id": b.shift_id, "started_at": _dt(b.started_at), "ended_at": _dt(b.ended_at)}
            for b in shift.breaks
        ],
    }


@router.get("/my")
def my_shift(db: Session = Depends(get_db), user: User = Depends(get_current_user)):
    shift = db.query(WaiterShift).filter(
        WaiterShift.waiter_id == user.id,
        WaiterShift.ended_at == None,
    ).first()
    return _enrich_shift(shift, db) if shift else None


@router.post("/start", status_code=status.HTTP_201_CREATED)
def start_shift(
    body: StartShiftRequest,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    target_id = body.waiter_id

    if target_id and target_id != user.id:
        if user.role not in ("manager", "sysadmin"):
            raise HTTPException(status_code=403, detail="Only managers can start shifts for other waiters")
        target = db.query(User).filter(User.id == target_id, User.is_active == True).first()
        if not target:
            raise HTTPException(status_code=404, detail="Waiter not found")
    else:
        target_id = user.id
        if user.role == "waiter" and _get_setting(db, "shifts.waiter_self_start") != "true":
            raise HTTPException(status_code=403, detail="Shift start requires manager confirmation")

    active_day = db.query(BusinessDay).filter(BusinessDay.status == "open").first()
    if not active_day:
        raise HTTPException(status_code=400, detail="No open business day — manager must open the restaurant first")

    existing = db.query(WaiterShift).filter(
        WaiterShift.waiter_id == target_id,
        WaiterShift.ended_at == None,
    ).first()
    if existing:
        raise HTTPException(status_code=400, detail="Waiter already has an active shift")

    shift = WaiterShift(
        waiter_id=target_id,
        business_day_id=active_day.id,
        starting_cash=body.starting_cash,
    )
    db.add(shift)
    db.commit()
    db.refresh(shift)
    return _enrich_shift(shift, db)


@router.post("/end")
def end_shift(
    body: EndShiftRequest,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    if user.role == "waiter" and _get_setting(db, "shifts.waiter_self_end") != "true":
        raise HTTPException(status_code=403, detail="Shift end requires manager confirmation")

    shift = db.query(WaiterShift).filter(
        WaiterShift.waiter_id == user.id,
        WaiterShift.ended_at == None,
    ).first()
    if not shift:
        raise HTTPException(status_code=404, detail="No active shift found")

    now = datetime.now(timezone.utc)
    shift.total_collected = compute_shift_total(shift.id, db)
    shift.ended_at = now
    if body.notes:
        shift.notes = body.notes

    open_break = db.query(ShiftBreak).filter(
        ShiftBreak.shift_id == shift.id, ShiftBreak.ended_at == None
    ).first()
    if open_break:
        open_break.ended_at = now

    db.commit()
    db.refresh(shift)
    return _enrich_shift(shift, db)


@router.post("/manager/start", status_code=status.HTTP_201_CREATED)
def manager_start_shift(
    body: StartShiftRequest,
    db: Session = Depends(get_db),
    user: User = Depends(require_manager),
):
    if not body.waiter_id:
        raise HTTPException(status_code=400, detail="waiter_id is required")

    target = db.query(User).filter(User.id == body.waiter_id, User.is_active == True).first()
    if not target:
        raise HTTPException(status_code=404, detail="Waiter not found")

    active_day = db.query(BusinessDay).filter(BusinessDay.status == "open").first()
    if not active_day:
        raise HTTPException(status_code=400, detail="No open business day")

    existing = db.query(WaiterShift).filter(
        WaiterShift.waiter_id == body.waiter_id,
        WaiterShift.ended_at == None,
    ).first()
    if existing:
        raise HTTPException(status_code=400, detail="Waiter already has an active shift")

    shift = WaiterShift(
        waiter_id=body.waiter_id,
        business_day_id=active_day.id,
        starting_cash=body.starting_cash,
    )
    db.add(shift)
    db.commit()
    db.refresh(shift)
    return _enrich_shift(shift, db)


@router.post("/manager/end/{shift_id}")
def manager_end_shift(
    shift_id: int,
    body: EndShiftRequest,
    db: Session = Depends(get_db),
    user: User = Depends(require_manager),
):
    shift = db.query(WaiterShift).filter(
        WaiterShift.id == shift_id,
        WaiterShift.ended_at == None,
    ).first()
    if not shift:
        raise HTTPException(status_code=404, detail="Active shift not found")

    now = datetime.now(timezone.utc)
    shift.total_collected = compute_shift_total(shift.id, db)
    shift.ended_at = now
    if body.notes:
        shift.notes = body.notes

    open_break = db.query(ShiftBreak).filter(
        ShiftBreak.shift_id == shift.id, ShiftBreak.ended_at == None
    ).first()
    if open_break:
        open_break.ended_at = now

    db.commit()
    db.refresh(shift)
    return _enrich_shift(shift, db)


@router.post("/{shift_id}/break/start")
def start_break(
    shift_id: int,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    shift = db.query(WaiterShift).filter(WaiterShift.id == shift_id).first()
    if not shift:
        raise HTTPException(status_code=404, detail="Shift not found")
    if shift.waiter_id != user.id and user.role not in ("manager", "sysadmin"):
        raise HTTPException(status_code=403, detail="Access denied")
    if shift.ended_at:
        raise HTTPException(status_code=400, detail="Shift already ended")

    open_break = db.query(ShiftBreak).filter(
        ShiftBreak.shift_id == shift_id, ShiftBreak.ended_at == None
    ).first()
    if open_break:
        raise HTTPException(status_code=400, detail="Break already in progress")

    b = ShiftBreak(shift_id=shift_id)
    db.add(b)
    db.commit()
    db.refresh(b)
    return {"id": b.id, "shift_id": b.shift_id, "started_at": _dt(b.started_at), "ended_at": _dt(b.ended_at)}


@router.post("/{shift_id}/break/end")
def end_break(
    shift_id: int,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    shift = db.query(WaiterShift).filter(WaiterShift.id == shift_id).first()
    if not shift:
        raise HTTPException(status_code=404, detail="Shift not found")
    if shift.waiter_id != user.id and user.role not in ("manager", "sysadmin"):
        raise HTTPException(status_code=403, detail="Access denied")

    open_break = db.query(ShiftBreak).filter(
        ShiftBreak.shift_id == shift_id, ShiftBreak.ended_at == None
    ).first()
    if not open_break:
        raise HTTPException(status_code=404, detail="No active break found")

    open_break.ended_at = datetime.now(timezone.utc)
    db.commit()
    db.refresh(open_break)
    return {"id": open_break.id, "shift_id": open_break.shift_id, "started_at": _dt(open_break.started_at), "ended_at": _dt(open_break.ended_at)}


@router.get("/")
def list_shifts(
    waiter_id: Optional[int] = None,
    business_day_id: Optional[int] = None,
    active_only: bool = False,
    db: Session = Depends(get_db),
    user: User = Depends(require_manager),
):
    q = db.query(WaiterShift)
    if waiter_id:
        q = q.filter(WaiterShift.waiter_id == waiter_id)
    if business_day_id:
        q = q.filter(WaiterShift.business_day_id == business_day_id)
    if active_only:
        q = q.filter(WaiterShift.ended_at == None)
    shifts = q.order_by(WaiterShift.started_at.desc()).all()
    return {"shifts": [_enrich_shift(s, db) for s in shifts]}


@router.get("/{shift_id}")
def get_shift(
    shift_id: int,
    db: Session = Depends(get_db),
    user: User = Depends(require_manager),
):
    shift = db.query(WaiterShift).filter(WaiterShift.id == shift_id).first()
    if not shift:
        raise HTTPException(status_code=404, detail="Shift not found")
    return _enrich_shift(shift, db)


@router.get("/{shift_id}/summary")
def get_shift_summary(
    shift_id: int,
    db: Session = Depends(get_db),
    user: User = Depends(require_manager),
):
    """Full shift summary: enriched shift data + paid items grouped by order."""
    from models.order import Order
    from sqlalchemy.orm import joinedload

    shift = db.query(WaiterShift).filter(WaiterShift.id == shift_id).first()
    if not shift:
        raise HTTPException(status_code=404, detail="Shift not found")

    items = db.query(OrderItem).options(
        joinedload(OrderItem.product),
        joinedload(OrderItem.order),
    ).filter(
        OrderItem.paid_in_shift_id == shift_id,
        OrderItem.status == "paid",
    ).all()

    orders_seen = {}
    for item in items:
        oid = item.order_id
        if oid not in orders_seen:
            o = item.order
            orders_seen[oid] = {
                "order_id": oid,
                "table_id": o.table_id if o else None,
                "opened_at": _dt(o.opened_at) if o else None,
                "items": [],
            }
        orders_seen[oid]["items"].append({
            "id": item.id,
            "product_name": item.product.name if item.product else f"#{item.product_id}",
            "quantity": item.quantity,
            "unit_price": float(item.unit_price),
            "subtotal": round(float(item.unit_price) * item.quantity, 2),
            "paid_at": _dt(item.paid_at),
        })

    # Compute hours worked
    started = shift.started_at
    ended = shift.ended_at
    duration_minutes = None
    if started and ended:
        duration_minutes = int((ended - started).total_seconds() / 60)
    elif started:
        from datetime import datetime, timezone as tz
        duration_minutes = int((datetime.now(tz.utc) - started.replace(tzinfo=tz.utc) if started.tzinfo is None else datetime.now(tz.utc) - started).total_seconds() / 60)

    enriched = _enrich_shift(shift, db)
    enriched["orders"] = list(orders_seen.values())
    enriched["duration_minutes"] = duration_minutes
    return enriched
