import json

from fastapi import APIRouter, Depends, Query, HTTPException, BackgroundTasks
from pydantic import BaseModel
from sqlalchemy.orm import Session
from sqlalchemy import func
from datetime import date, datetime, timedelta
from typing import Optional, List

from database import get_db
from models.order import Order, OrderItem, OrderWaiter, PrintLog
from models.user import User
from models.table import Table
from models.printer import Printer
from models.shift import WaiterShift
from schemas.order import OrderOut
from schemas.table import TableOut
from routers.deps import require_manager
from services.printer_service import print_waiter_report, print_printer_report, print_order_receipt

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("/shift")
def shift_summary(
    from_dt: Optional[str] = Query(default=None, alias="from"),
    to_dt: Optional[str] = Query(default=None, alias="to"),
    report_date: Optional[date] = Query(default=None, alias="date"),
    waiter_id: Optional[int] = None,
    db: Session = Depends(get_db),
    user: User = Depends(require_manager),
):
    """Payments collected per waiter — based on paid_by on order items."""
    if from_dt and to_dt:
        start = datetime.fromisoformat(from_dt)
        end = datetime.fromisoformat(to_dt)
    else:
        target = report_date or date.today()
        start = datetime.combine(target, datetime.min.time())
        end = start + timedelta(days=1)

    q = db.query(OrderItem).filter(
        OrderItem.status == "paid",
        OrderItem.paid_at >= start,
        OrderItem.paid_at < end,
    )
    if waiter_id:
        q = q.filter(OrderItem.paid_by == waiter_id)
    items = q.all()

    waiters_db = {u.id: u for u in db.query(User).all()}
    tables_db = {t.id: (t.label or f"T{t.number}") for t in db.query(Table).all()}

    # Build per-waiter summary keyed by waiter_id
    summary: dict[int, dict] = {}
    for item in items:
        wid = item.paid_by
        if wid not in summary:
            w = waiters_db.get(wid)
            wname = (w.full_name or w.username) if w else f"#{wid}"
            summary[wid] = {
                "waiter_id": wid,
                "waiter_name": wname,
                "items": 0,
                "total": 0.0,
                "order_data": {},
            }
        summary[wid]["items"] += item.quantity
        val = item.unit_price * item.quantity
        summary[wid]["total"] += val

        oid = item.order_id
        if oid not in summary[wid]["order_data"]:
            order = db.query(Order).filter(Order.id == oid).first()
            summary[wid]["order_data"][oid] = {
                "id": oid,
                "time_open": order.opened_at.strftime("%H:%M") if order else "",
                "time_close": order.closed_at.strftime("%H:%M") if order and order.closed_at else "",
                "table": tables_db.get(order.table_id, f"#{oid}") if order else f"#{oid}",
                "total": 0.0,
                "items": [],
            }
        summary[wid]["order_data"][oid]["total"] += val
        product_name = item.product.name if item.product else f"#{item.product_id}"
        summary[wid]["order_data"][oid]["items"].append(
            {"name": product_name, "quantity": item.quantity}
        )

    result = []
    for entry in summary.values():
        entry["orders"] = len(entry["order_data"])
        entry["order_data"] = list(entry["order_data"].values())
        result.append(entry)

    return {"from": start.isoformat(), "to": end.isoformat(), "waiters": result}


@router.get("/shift/orders")
def shift_orders_summary(
    from_dt: Optional[str] = Query(default=None, alias="from"),
    to_dt: Optional[str] = Query(default=None, alias="to"),
    report_date: Optional[date] = Query(default=None, alias="date"),
    waiter_id: Optional[int] = None,
    db: Session = Depends(get_db),
    user: User = Depends(require_manager),
):
    """Items sent (added) per waiter — regardless of payment status."""
    if from_dt and to_dt:
        start = datetime.fromisoformat(from_dt)
        end = datetime.fromisoformat(to_dt)
    else:
        target = report_date or date.today()
        start = datetime.combine(target, datetime.min.time())
        end = start + timedelta(days=1)

    q = db.query(OrderItem).filter(
        OrderItem.status.in_(["active", "paid"]),
        OrderItem.added_at >= start,
        OrderItem.added_at < end,
    )
    if waiter_id:
        q = q.filter(OrderItem.added_by == waiter_id)
    items = q.all()

    waiters_db = {u.id: u for u in db.query(User).all()}
    tables_db = {t.id: (t.label or f"T{t.number}") for t in db.query(Table).all()}

    summary: dict[int, dict] = {}
    for item in items:
        wid = item.added_by
        if wid not in summary:
            w = waiters_db.get(wid)
            wname = (w.full_name or w.username) if w else f"#{wid}"
            summary[wid] = {
                "waiter_id": wid,
                "waiter_name": wname,
                "items": 0,
                "total": 0.0,
                "order_data": {},
            }
        summary[wid]["items"] += item.quantity
        val = item.unit_price * item.quantity
        summary[wid]["total"] += val

        oid = item.order_id
        if oid not in summary[wid]["order_data"]:
            order = db.query(Order).filter(Order.id == oid).first()
            summary[wid]["order_data"][oid] = {
                "id": oid,
                "time_open": order.opened_at.strftime("%H:%M") if order else "",
                "time_close": order.closed_at.strftime("%H:%M") if order and order.closed_at else "",
                "table": tables_db.get(order.table_id, f"#{oid}") if order else f"#{oid}",
                "total": 0.0,
                "items": [],
            }
        summary[wid]["order_data"][oid]["total"] += val
        product_name = item.product.name if item.product else f"#{item.product_id}"
        summary[wid]["order_data"][oid]["items"].append(
            {"name": product_name, "quantity": item.quantity}
        )

    result = []
    for entry in summary.values():
        entry["orders"] = len(entry["order_data"])
        entry["order_data"] = list(entry["order_data"].values())
        result.append(entry)

    return {"from": start.isoformat(), "to": end.isoformat(), "waiters": result}


@router.get("/orders/history", response_model=List[OrderOut])
def order_history(
    from_date: Optional[str] = Query(default=None, alias="from"),
    to_date: Optional[str] = Query(default=None, alias="to"),
    waiter_id: Optional[int] = None,
    order_status: Optional[str] = Query(default=None, alias="status"),
    table_id: Optional[int] = None,
    page: int = 1,
    page_size: int = 50,
    db: Session = Depends(get_db),
    user: User = Depends(require_manager),
):
    q = db.query(Order)
    if from_date:
        q = q.filter(Order.opened_at >= datetime.fromisoformat(from_date))
    if to_date:
        q = q.filter(Order.opened_at <= datetime.fromisoformat(to_date))
    if waiter_id:
        q = q.join(OrderWaiter).filter(OrderWaiter.waiter_id == waiter_id)
    if order_status:
        q = q.filter(Order.status == order_status)
    if table_id:
        q = q.filter(Order.table_id == table_id)
    return q.order_by(Order.opened_at.desc()).offset((page - 1) * page_size).limit(page_size).all()


@router.get("/tables/summary")
def tables_summary(db: Session = Depends(get_db), user: User = Depends(require_manager)):
    tables = db.query(Table).filter(Table.is_active == True).all()
    result = []
    for table in tables:
        active_order = db.query(Order).filter(
            Order.table_id == table.id,
            Order.status.in_(["open", "partially_paid"]),
        ).first()
        result.append({
            "table": TableOut.model_validate(table),
            "status": active_order.status if active_order else "free",
            "order_id": active_order.id if active_order else None,
        })
    return result


@router.get("/printers")
def printer_totals(
    from_date: Optional[str] = Query(default=None, alias="from"),
    to_date: Optional[str] = Query(default=None, alias="to"),
    db: Session = Depends(get_db),
    user: User = Depends(require_manager),
):
    """Returns totals per printer based on print_log entries in the date range."""
    q = db.query(PrintLog).filter(PrintLog.success == True)
    if from_date:
        q = q.filter(PrintLog.printed_at >= datetime.fromisoformat(from_date))
    if to_date:
        q = q.filter(PrintLog.printed_at <= datetime.fromisoformat(to_date))
    logs = q.all()

    printers_db = {p.id: p for p in db.query(Printer).all()}
    tables_db = {t.id: (t.label or f"T{t.number}") for t in db.query(Table).all()}

    # summary[pid] — aggregated totals
    summary: dict[int, dict] = {}
    # order_map[pid][order_id] — per-order detail with items
    order_map: dict[int, dict] = {}

    for log in logs:
        pid = log.printer_id
        if pid not in summary:
            printer = printers_db.get(pid)
            summary[pid] = {
                "printer_id": pid,
                "printer_name": printer.name if printer else f"Printer #{pid}",
                "print_jobs": 0,
                "orders": set(),
                "items": 0,
                "total": 0.0,
            }
            order_map[pid] = {}

        summary[pid]["print_jobs"] += 1
        summary[pid]["orders"].add(log.order_id)

        oid = log.order_id
        if oid not in order_map[pid]:
            order = db.query(Order).filter(Order.id == oid).first()
            order_map[pid][oid] = {
                "order_id": oid,
                "time": log.printed_at.strftime("%H:%M"),
                "table": tables_db.get(order.table_id, f"#{oid}") if order else f"#{oid}",
                "total": 0.0,
                "items": [],
            }

        try:
            item_ids = json.loads(log.item_ids)
        except Exception:
            item_ids = []
        for item_id in item_ids:
            item = db.query(OrderItem).filter(OrderItem.id == item_id).first()
            if item and item.status in ("active", "paid"):
                summary[pid]["items"] += item.quantity
                val = item.unit_price * item.quantity
                summary[pid]["total"] += val
                order_map[pid][oid]["total"] += val
                product_name = item.product.name if item.product else f"#{item.product_id}"
                order_map[pid][oid]["items"].append({"name": product_name, "quantity": item.quantity})

    result = []
    for pid, entry in summary.items():
        entry["orders"] = len(entry["orders"])
        entry["order_data"] = list(order_map.get(pid, {}).values())
        result.append(entry)
    return {"printers": result}


class PrintWaiterReportBody(BaseModel):
    waiter_name: str
    printer_id: int
    mode: str  # "simple" | "extensive"
    from_dt: str
    to_dt: str


class PrintPrinterReportBody(BaseModel):
    printer_target_id: int
    printer_id: int
    mode: str  # "simple" | "extensive"
    from_dt: str
    to_dt: str


class PrintOrderBody(BaseModel):
    printer_id: int


@router.post("/print/waiter")
def print_waiter(
    body: PrintWaiterReportBody,
    background_tasks: BackgroundTasks,
    db: Session = Depends(get_db),
    user: User = Depends(require_manager),
):
    printer = db.query(Printer).filter(Printer.id == body.printer_id, Printer.is_active == True).first()
    if not printer:
        raise HTTPException(status_code=404, detail="Printer not found or inactive")

    from_dt = datetime.fromisoformat(body.from_dt)
    to_dt = datetime.fromisoformat(body.to_dt)

    # Gather orders for this waiter in time range
    waiter = db.query(User).filter(User.username == body.waiter_name).first()
    q = db.query(Order).filter(
        Order.opened_at >= from_dt,
        Order.opened_at <= to_dt,
    )
    if waiter:
        q = q.filter(Order.opened_by == waiter.id)
    else:
        q = q.filter(False)
    orders = q.all()

    # Enrich with table names
    tables = {t.id: (t.label or f"T{t.number}") for t in db.query(Table).all()}
    order_data = []
    for o in orders:
        active_items = [i for i in o.items if i.status in ("active", "paid")]
        total = sum(i.unit_price * i.quantity for i in active_items)
        order_data.append({
            "id": o.id,
            "time_open": o.opened_at.strftime("%H:%M"),
            "time_close": o.closed_at.strftime("%H:%M") if o.closed_at else "",
            "table": tables.get(o.table_id, f"#{o.table_id}"),
            "total": total,
            "items": [
                {"name": (i.product.name if i.product else f"#{i.product_id}"), "quantity": i.quantity}
                for i in active_items
            ],
        })

    items_count = sum(
        i.quantity for o in orders for i in o.items if i.status in ("active", "paid")
    )
    grand_total = sum(d["total"] for d in order_data)

    report = {
        "waiter_name": body.waiter_name,
        "orders": len(orders),
        "items": items_count,
        "total": grand_total,
        "order_data": order_data if body.mode == "extensive" else [],
        "from_dt": from_dt.strftime("%d/%m/%Y %H:%M"),
        "to_dt": to_dt.strftime("%d/%m/%Y %H:%M"),
    }

    background_tasks.add_task(print_waiter_report, printer.ip_address, printer.port, report, body.mode)
    return {"status": "printing"}


@router.post("/print/printer")
def print_printer_totals(
    body: PrintPrinterReportBody,
    background_tasks: BackgroundTasks,
    db: Session = Depends(get_db),
    user: User = Depends(require_manager),
):
    printer = db.query(Printer).filter(Printer.id == body.printer_id, Printer.is_active == True).first()
    if not printer:
        raise HTTPException(status_code=404, detail="Printer not found or inactive")

    target_printer = db.query(Printer).filter(Printer.id == body.printer_target_id).first()
    target_name = target_printer.name if target_printer else f"Printer #{body.printer_target_id}"

    from_dt = datetime.fromisoformat(body.from_dt)
    to_dt = datetime.fromisoformat(body.to_dt)

    logs = db.query(PrintLog).filter(
        PrintLog.printer_id == body.printer_target_id,
        PrintLog.success == True,
        PrintLog.printed_at >= from_dt,
        PrintLog.printed_at <= to_dt,
    ).all()

    tables = {t.id: (t.label or f"T{t.number}") for t in db.query(Table).all()}

    # Build per-order entries keyed by order_id; each log may add more items
    order_map: dict = {}
    items_count = 0
    grand_total = 0.0
    for log in logs:
        oid = log.order_id
        if oid not in order_map:
            order = db.query(Order).filter(Order.id == oid).first()
            if order:
                order_map[oid] = {
                    "id": oid,
                    "time": log.printed_at.strftime("%H:%M"),
                    "table": tables.get(order.table_id, f"#{order.table_id}"),
                    "total": 0.0,
                    "items": [],
                }
        try:
            item_ids = json.loads(log.item_ids)
        except Exception:
            item_ids = []
        for item_id in item_ids:
            item = db.query(OrderItem).filter(OrderItem.id == item_id).first()
            if item and item.status in ("active", "paid"):
                items_count += item.quantity
                val = item.unit_price * item.quantity
                grand_total += val
                if oid in order_map:
                    order_map[oid]["total"] += val
                    product_name = item.product.name if item.product else f"#{item.product_id}"
                    order_map[oid]["items"].append({"name": product_name, "quantity": item.quantity})

    order_data = list(order_map.values())

    report = {
        "printer_name": target_name,
        "print_jobs": len(logs),
        "orders": len(order_map),
        "items": items_count,
        "total": grand_total,
        "order_data": order_data if body.mode == "extensive" else [],
        "from_dt": from_dt.strftime("%d/%m/%Y %H:%M"),
        "to_dt": to_dt.strftime("%d/%m/%Y %H:%M"),
    }

    background_tasks.add_task(print_printer_report, printer.ip_address, printer.port, report, body.mode)
    return {"status": "printing"}


# ---------------------------------------------------------------------------
# Shift history report
# ---------------------------------------------------------------------------

@router.get("/shifts")
def shifts_report(
    waiter_id: Optional[int] = None,
    business_day_id: Optional[int] = None,
    from_dt: Optional[str] = Query(default=None, alias="from"),
    to_dt: Optional[str] = Query(default=None, alias="to"),
    active_only: bool = False,
    db: Session = Depends(get_db),
    user: User = Depends(require_manager),
):
    from routers.shifts import compute_shift_total

    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 from_dt:
        q = q.filter(WaiterShift.started_at >= datetime.fromisoformat(from_dt))
    if to_dt:
        q = q.filter(WaiterShift.started_at <= datetime.fromisoformat(to_dt))
    if active_only:
        q = q.filter(WaiterShift.ended_at == None)

    shifts = q.order_by(WaiterShift.started_at.desc()).all()
    waiters_db = {u.id: u for u in db.query(User).all()}

    result = []
    for shift in shifts:
        w = waiters_db.get(shift.waiter_id)
        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)
        result.append({
            "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,
        })

    return {"shifts": result}


# ---------------------------------------------------------------------------
# Product performance analytics
# ---------------------------------------------------------------------------

@router.get("/products/performance")
def product_performance(
    from_dt: Optional[str] = Query(default=None, alias="from"),
    to_dt: Optional[str] = Query(default=None, alias="to"),
    business_day_id: Optional[int] = None,
    category_id: Optional[int] = None,
    db: Session = Depends(get_db),
    user: User = Depends(require_manager),
):
    from models.product import Product

    q = db.query(OrderItem).filter(OrderItem.status.in_(["active", "paid"]))
    if from_dt:
        q = q.filter(OrderItem.added_at >= datetime.fromisoformat(from_dt))
    if to_dt:
        q = q.filter(OrderItem.added_at <= datetime.fromisoformat(to_dt))
    if business_day_id:
        q = q.join(Order).filter(Order.business_day_id == business_day_id)

    items = q.all()
    products_db = {p.id: p for p in db.query(Product).all()}

    summary: dict = {}
    for item in items:
        pid = item.product_id
        product = products_db.get(pid)
        if category_id and (not product or product.category_id != category_id):
            continue
        if pid not in summary:
            summary[pid] = {
                "product_id": pid,
                "product_name": product.name if product else f"#{pid}",
                "category_id": product.category_id if product else None,
                "qty_sold": 0,
                "revenue": 0.0,
                "order_ids": set(),
            }
        summary[pid]["qty_sold"] += item.quantity
        summary[pid]["revenue"] += item.unit_price * item.quantity
        summary[pid]["order_ids"].add(item.order_id)

    result = []
    for entry in summary.values():
        entry["order_count"] = len(entry.pop("order_ids"))
        entry["revenue"] = round(entry["revenue"], 2)
        result.append(entry)

    result.sort(key=lambda x: x["qty_sold"], reverse=True)
    return {"products": result}


# ---------------------------------------------------------------------------
# Table performance analytics
# ---------------------------------------------------------------------------

@router.get("/tables/performance")
def table_performance(
    from_dt: Optional[str] = Query(default=None, alias="from"),
    to_dt: Optional[str] = Query(default=None, alias="to"),
    business_day_id: Optional[int] = None,
    db: Session = Depends(get_db),
    user: User = Depends(require_manager),
):
    q = db.query(Order).filter(Order.status.in_(["closed", "paid"]))
    if from_dt:
        q = q.filter(Order.opened_at >= datetime.fromisoformat(from_dt))
    if to_dt:
        q = q.filter(Order.opened_at <= datetime.fromisoformat(to_dt))
    if business_day_id:
        q = q.filter(Order.business_day_id == business_day_id)
    orders = q.all()

    tables_db = {t.id: t for t in db.query(Table).all()}

    summary: dict = {}
    for order in orders:
        tid = order.table_id
        if tid not in summary:
            t = tables_db.get(tid)
            summary[tid] = {
                "table_id": tid,
                "table_name": (t.label or f"T{t.number}") if t else f"#{tid}",
                "order_count": 0,
                "revenue": 0.0,
                "durations": [],
            }
        summary[tid]["order_count"] += 1
        summary[tid]["revenue"] += sum(
            i.unit_price * i.quantity for i in order.items if i.status in ("active", "paid")
        )
        if order.closed_at and order.opened_at:
            summary[tid]["durations"].append(
                (order.closed_at - order.opened_at).total_seconds() / 60
            )

    result = []
    for entry in summary.values():
        durations = entry.pop("durations")
        entry["avg_duration_minutes"] = round(sum(durations) / len(durations), 1) if durations else None
        entry["revenue"] = round(entry["revenue"], 2)
        result.append(entry)

    result.sort(key=lambda x: x["revenue"], reverse=True)
    return {"tables": result}


# ---------------------------------------------------------------------------
# Traffic analysis (hour-of-day / day-of-week)
# ---------------------------------------------------------------------------

@router.get("/traffic")
def traffic_analysis(
    from_dt: Optional[str] = Query(default=None, alias="from"),
    to_dt: Optional[str] = Query(default=None, alias="to"),
    business_day_id: Optional[int] = None,
    db: Session = Depends(get_db),
    user: User = Depends(require_manager),
):
    q = db.query(Order)
    if from_dt:
        q = q.filter(Order.opened_at >= datetime.fromisoformat(from_dt))
    if to_dt:
        q = q.filter(Order.opened_at <= datetime.fromisoformat(to_dt))
    if business_day_id:
        q = q.filter(Order.business_day_id == business_day_id)
    orders = q.all()

    by_hour = {h: {"hour": h, "orders": 0, "revenue": 0.0} for h in range(24)}
    day_labels = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
    by_weekday = {d: {"day": d, "label": day_labels[d], "orders": 0, "revenue": 0.0} for d in range(7)}

    for order in orders:
        revenue = sum(
            i.unit_price * i.quantity for i in order.items if i.status in ("active", "paid")
        )
        h = order.opened_at.hour
        d = order.opened_at.weekday()
        by_hour[h]["orders"] += 1
        by_hour[h]["revenue"] += revenue
        by_weekday[d]["orders"] += 1
        by_weekday[d]["revenue"] += revenue

    for h in by_hour:
        by_hour[h]["revenue"] = round(by_hour[h]["revenue"], 2)
    for d in by_weekday:
        by_weekday[d]["revenue"] = round(by_weekday[d]["revenue"], 2)

    return {
        "by_hour": list(by_hour.values()),
        "by_weekday": list(by_weekday.values()),
    }
