# order_data.py
from __future__ import annotations

import sys
import os

# Add backend to import path
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "backend")))

import time
from typing import Any, Dict, List, Optional
from app import crud, database


class OrderDataProvider:
    """
    Fetches pending orders from DB with TTL caching.
    Converts ORM objects to plain dicts inside the DB session to avoid DetachedInstanceError.
    """

    def __init__(self, speed = 2, ttl_ms: int = 400, max_orders: int = 3, max_products: int = 12):
        self.ttl_ms = int(ttl_ms)
        self.max_orders = int(max_orders)
        self.max_products = int(max_products)

        self._last_fetch_ms: int = 0
        self._cached_orders: List[Dict[str, Any]] = []
        self._last_error: Optional[str] = None

        self.order_start_times: Dict[int, float] = {}
        self.order_prep_times = []

        self.speed = speed

    @property
    def last_error(self) -> Optional[str]:
        return self._last_error

    def _now_ms(self) -> int:
        # time.monotonic() is stable for measuring intervals
        return int(time.monotonic() * 1000)

    def _fetch_orders_data(self) -> List[Dict[str, Any]]:
        """
        Returns a list of plain dicts:
        [
          {
            "id": ...,
            "status": ...,
            "table": ...,
            "total": ...,
            "products": [{"name":..., "qty":..., "waiter":...}, ...]
          },
          ...
        ]
        """
        data: List[Dict[str, Any]] = []

        with database.SessionLocal() as db:
            orders = crud.get_pending_orders(db)[: self.max_orders]

            for order in orders:
                table = getattr(order, "table", None)
                table_name = getattr(table, "name", "-") if table else "-"

                products_data: List[Dict[str, Any]] = []
                products = getattr(order, "products", []) or []

                for op in products[: self.max_products]:
                    product = getattr(op, "product", None)
                    prod_name = (
                        getattr(product, "name", "Unknown")
                        if product
                        else str(getattr(op, "product_id", "Unknown"))
                    )

                    qty = getattr(op, "quantity", 1)

                    waiter = getattr(op, "waiter", None)
                    if waiter:
                        waiter_name = getattr(waiter, "name", "-")
                    else:
                        waiter_id = getattr(op, "waiter_id", None)
                        waiter_name = f"ID:{waiter_id}" if waiter_id else "-"

                    cost = getattr(product, "cost", 0) if product else 0

                    products_data.append(
                        {"name": prod_name, "qty": qty, "waiter": waiter_name, "cost": cost, "status": getattr(op, "status", "unknown")}
                    )

                data.append(
                    {
                        "id": getattr(order, "id", "N/A"),
                        "status": getattr(order, "status", "unknown"),
                        "table": table_name,
                        "total": getattr(order, "total_price", 0),
                        "products": products_data,
                    }
                )

        return data
    
    def get_orders(self) -> List[Dict[str, Any]]:
        """
        Returns cached orders; refreshes from DB if TTL expired.
        Never raises: stores error in self._last_error and returns last cached data.
        """
        now = self._now_ms()

        if now - self._last_fetch_ms >= self.ttl_ms:
            try:
                self._cached_orders = self._fetch_orders_data()
                self._last_error = None
            except Exception as e:
                self._last_error = str(e)
            self._last_fetch_ms = now

        return self._cached_orders
    
    def get_first_pending_order(self) -> Optional[Dict[str, Any]]:
        """
        Returns the first pending order or None if no pending orders.
        """
        orders = self.get_orders()
        return orders[0] if orders else None
# simulation/map.py

    def get_order_and_product_for_waiter(self, waiter) -> Optional[Dict[str, Any]]:
        """
        Returns a dict with the first pending order and a product assigned to this waiter,
        or None if no such order/product exists.
        """
        orders = self.get_orders()

        for order in orders:
            products = order.get("products", []) or []
            for product in products:
                assigned_waiter = product.get("waiter", "-")
                if assigned_waiter.lower() == waiter.name.lower():
                    return {
                        "order_id": order.get("id"),
                        "product": product,
                    }

        return None

    def get_product_group(self, product_name: str) -> Optional[str]:
        """
        Returns the group name for the given product, or None if not found.
        """
        with database.SessionLocal() as db:
            group_name = crud.get_group_by_product_name(db, product_name)
            if group_name:
                return group_name
        return None
    
    def change_product_status(self, order_id: int, product_name: str, waiter_name: str, new_status: str) -> bool:
        """
        Changes the status of the specified product in the specified order.
        Returns True if successful, False otherwise.
        """
        try:
            with database.SessionLocal() as db:
                crud.update_order_product_status(db, order_id, product_name, waiter_name, new_status)
                ready = crud.is_order_ready(db, order_id)  # Update order status if needed
                if ready:
                    prep_time = time.time() - self.order_start_times.get(order_id, time.time())
                    prep_time = prep_time * self.speed
                    self.order_prep_times.append(prep_time)
            return True
        except Exception:
            return False
        
    def get_next_product_for_waiter(self, waiter_name: str) -> Optional[tuple[list, str, int]]:
        """
        Returns the next product(s), group name, and order ID assigned to the given waiter,
        or (None, None, None) if no such product exists.
        Si todos los camareros de orders[1] no tienen producto pendiente en order[0],
        ya empiezan estos con la siguiente order.
        """
        orders = self.get_orders()
        if not orders:
            return (None, None, None)

        # Primero, intentamos asignar productos de la primera order
        order = orders[0]
        order_id = order.get("id")
        if order_id not in self.order_start_times:
            self.order_start_times[order_id] = time.time()
        products = order.get("products", []) or []
        products_same_group = []
        product_assigned = False
        group_name = None
        for product in products:
            assigned_waiter = product.get("waiter", "-")
            if waiter_name.lower() == "-":
                print("Warning: waiter with name '-' found in order data.")
            product_status = product.get("status", None)
            if product_status is None:
                print(f"Warning: product with no status found in order {order_id}.")
            if assigned_waiter.lower() == waiter_name.lower() and product_status and product_status.lower() == "pending" and (group_name is None or group_name == self.get_product_group(product.get("name"))):
                product_name = product.get("name")
                group_name = self.get_product_group(product_name)
                product_assigned = True
                products_same_group.append(product)

        if product_assigned:
            return (products_same_group, group_name, order_id)

        # Si no hay producto pendiente para este camarero en la primera order,
        # comprobamos si TODOS los camareros de la segunda order tampoco tienen producto pendiente en la primera
        if len(orders) > 1:
            # Obtener todos los camareros de la segunda order
            next_order = orders[1]
            order_id = next_order.get("id")
            if order_id not in self.order_start_times:
                self.order_start_times[order_id] = time.time()
            next_products = next_order.get("products", []) or []
            waiters_in_next_order = set()
            for p in next_products:
                w = p.get("waiter", "-")
                if w != "-":
                    waiters_in_next_order.add(w.lower())

            # Para cada camarero de la segunda order, comprobar si tiene producto pendiente en la primera
            all_waiters_free = True
            for w in waiters_in_next_order:
                has_pending = False
                for product in products:
                    assigned_waiter = product.get("waiter", "-")
                    product_status = product.get("status", None)
                    if assigned_waiter.lower() == w and product_status and product_status.lower() == "pending":
                        has_pending = True
                        break
                if has_pending:
                    all_waiters_free = False
                    break

            # Si todos los camareros de la segunda order están libres, permitirles empezar con la siguiente order
            if all_waiters_free and waiter_name.lower() in waiters_in_next_order:
                # Buscar productos pendientes para este camarero en la segunda order
                next_products_same_group = []
                next_group_name = None
                next_order_id = next_order.get("id")
                for product in next_products:
                    assigned_waiter = product.get("waiter", "-")
                    product_status = product.get("status", None)
                    if assigned_waiter.lower() == waiter_name.lower() and product_status and product_status.lower() == "pending" and (next_group_name is None or next_group_name == self.get_product_group(product.get("name"))):
                        product_name = product.get("name")
                        next_group_name = self.get_product_group(product_name)
                        next_products_same_group.append(product)
                if next_products_same_group:
                    return (next_products_same_group, next_group_name, next_order_id)

        return (None, None, None)