<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\ActivityLog;
use App\Models\Order;
use App\Models\OrderItem;
use App\Models\Product;
use App\Models\RestaurantTable;
use App\Models\Setting;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Throwable;

class StaffOrderController extends Controller
{
    /**
     * The waiter's own currently-open orders (still unpaid) for their assigned
     * tables, placed today — this is how the app finds "the same order" again to
     * add more items to it, instead of always starting a brand new order per
     * table visit. Scoped to today the same way Kitchen's and Cashier's own
     * queues are, so a stray unpaid order from a previous day never lingers here.
     */
    public function active(Request $request): JsonResponse
    {
        $staff = $request->user();

        $orders = Order::with(['items', 'restaurantTable:id,name', 'placedByStaff:id,name'])
            ->where('source', 'mobile')
            ->where('payment_status', 'unpaid')
            ->whereHas('restaurantTable', fn ($query) => $query->where('assigned_staff_id', $staff->id))
            ->whereDate('created_at', now()->toDateString())
            ->orderByDesc('created_at')
            ->get();

        return response()->json(['orders' => $orders->map(fn (Order $order) => $this->orderData($order))]);
    }

    public function store(Request $request): JsonResponse
    {
        $data = $request->validate([
            'table_id' => ['required', 'integer', Rule::exists('restaurant_tables', 'id')->where('client_id', tenant()->clientId())],
            'items' => ['required', 'array', 'min:1'],
            'items.*.product_id' => ['required', 'integer', 'distinct', Rule::exists('products', 'id')->where('client_id', tenant()->clientId())],
            'items.*.qty' => ['required', 'integer', 'min:1', 'max:20'],
        ]);

        $staff = $request->user();
        $table = $this->assertOwnsTable($data['table_id'], $staff);

        try {
            $order = DB::transaction(function () use ($data, $staff, $table) {
                $products = $this->resolveOrderableProducts($data['items']);

                $subtotal = $this->itemsSubtotal($data['items'], $products);
                $packingCharge = (float) Setting::get('default_packing_charge', 0);
                $gst = round($subtotal * ((float) Setting::get('gst_percent', 5) / 100), 2);
                [$orderNumber, $token] = Order::nextIdentifiers();

                $order = Order::create([
                    'order_number' => $orderNumber, 'token_number' => $token,
                    'restaurant_table_id' => $table->id, 'placed_by_staff_id' => $staff->id,
                    'source' => 'mobile', 'status' => 'preparing',
                    'payment_method' => 'cash', 'payment_status' => 'unpaid',
                    'subtotal' => $subtotal, 'packing_charge' => $packingCharge,
                    'discount' => 0, 'gst' => $gst, 'grand_total' => $subtotal + $packingCharge + $gst,
                ]);

                $this->createItems($order, $data['items'], $products);
                $order->recomputeStatus();

                ActivityLog::log('order.created', "Mobile order {$order->order_number} placed by {$staff->name} for {$table->name}");

                return $order;
            });

            $order->load(['items', 'restaurantTable:id,name', 'placedByStaff:id,name']);

            return response()->json(['message' => 'Order placed successfully.', 'order' => $this->orderData($order)], 201);
        } catch (ValidationException $exception) {
            throw $exception;
        } catch (Throwable $exception) {
            Log::error('Staff mobile order failed', [
                'staff_id' => $staff->id,
                'table_id' => $data['table_id'],
                'exception' => $exception,
            ]);

            return response()->json([
                'message' => 'Order could not be saved.',
                'error' => $exception->getMessage(),
                'exception' => $exception::class,
                'file' => $exception->getFile(),
                'line' => $exception->getLine(),
            ], 500);
        }
    }

    /**
     * Adds a second (or later) round of items to an order the waiter already
     * placed — only while it's still unpaid. The new items start out 'pending' on
     * the Kitchen board even if earlier items in this same order were already
     * handed over, and the order's overall status reopens to reflect that.
     */
    public function addItems(Request $request, Order $order): JsonResponse
    {
        abort_unless($order->client_id === tenant()->clientId(), 404);

        $data = $request->validate([
            'items' => ['required', 'array', 'min:1'],
            'items.*.product_id' => ['required', 'integer', 'distinct', Rule::exists('products', 'id')->where('client_id', tenant()->clientId())],
            'items.*.qty' => ['required', 'integer', 'min:1', 'max:20'],
        ]);

        $staff = $request->user();
        abort_unless($order->source === 'mobile', 422, 'Only a mobile-placed order can be edited here.');
        $this->assertOwnsTable($order->restaurant_table_id, $staff);

        if ($order->payment_status !== 'unpaid') {
            throw ValidationException::withMessages(['items' => ['This order has already been paid and can no longer be edited.']]);
        }

        try {
            $order = DB::transaction(function () use ($data, $order) {
                $products = $this->resolveOrderableProducts($data['items']);

                $this->createItems($order, $data['items'], $products);

                $newSubtotal = round((float) $order->items()->sum('line_total'), 2);
                $gst = round($newSubtotal * ((float) Setting::get('gst_percent', 5) / 100), 2);

                $order->update([
                    'subtotal' => $newSubtotal,
                    'gst' => $gst,
                    'grand_total' => $newSubtotal + (float) $order->packing_charge + $gst,
                ]);

                $order->recomputeStatus();

                ActivityLog::log('order.items_added', "Items added to order {$order->order_number}");

                return $order;
            });

            $order->load(['items', 'restaurantTable:id,name', 'placedByStaff:id,name']);

            return response()->json(['message' => 'Items added to order.', 'order' => $this->orderData($order)]);
        } catch (ValidationException $exception) {
            throw $exception;
        } catch (Throwable $exception) {
            Log::error('Adding items to mobile order failed', ['order_id' => $order->id, 'exception' => $exception]);

            return response()->json(['message' => 'Items could not be added.', 'error' => $exception->getMessage()], 500);
        }
    }

    private function assertOwnsTable(int $tableId, $staff): RestaurantTable
    {
        $table = RestaurantTable::whereKey($tableId)
            ->where('assigned_staff_id', $staff->id)
            ->where('is_active', true)
            ->first();

        if (! $table) {
            throw ValidationException::withMessages([
                'table_id' => ['You can only place or edit orders for an active table assigned to you.'],
            ]);
        }

        return $table;
    }

    /** @return \Illuminate\Support\Collection<int, Product> keyed by product id */
    private function resolveOrderableProducts(array $items)
    {
        $requested = collect($items)->keyBy('product_id');
        $products = Product::whereIn('id', $requested->keys())
            ->where('is_available', true)
            ->with('category:id,requires_kitchen')
            ->get()
            ->keyBy('id');

        if ($products->count() !== $requested->count()) {
            throw ValidationException::withMessages(['items' => ['One or more selected products are unavailable.']]);
        }

        return $products;
    }

    private function itemsSubtotal(array $items, $products): float
    {
        $subtotal = 0.0;
        foreach ($items as $item) {
            $subtotal += (float) $products->get($item['product_id'])->price * $item['qty'];
        }

        return round($subtotal, 2);
    }

    /**
     * A category can opt out of Kitchen entirely (e.g. Ice Cream, Soft Drinks —
     * nothing to prepare) via Admin > Categories > "Send to Kitchen". Those items
     * are created already handed_over so they never appear on the KOT board and
     * stay with the Cashier only. Waiter App flow only — Kiosk ordering doesn't
     * go through this controller at all.
     */
    private function createItems(Order $order, array $items, $products): void
    {
        foreach ($items as $item) {
            $product = $products->get($item['product_id']);
            $requiresKitchen = $product->category?->requires_kitchen ?? true;

            $order->items()->create([
                'product_id' => $product->id, 'name' => $product->name, 'price' => $product->price,
                'qty' => $item['qty'], 'line_total' => round((float) $product->price * $item['qty'], 2),
                'kot_status' => $requiresKitchen ? OrderItem::KOT_PENDING : OrderItem::KOT_HANDED_OVER,
            ]);
        }
    }

    private function orderData(Order $order): array
    {
        return [
            'id' => $order->id, 'order_number' => $order->order_number, 'token_number' => $order->token_number,
            'status' => $order->status, 'payment_status' => $order->payment_status,
            'subtotal' => (float) $order->subtotal, 'packing_charge' => (float) $order->packing_charge,
            'gst' => (float) $order->gst, 'grand_total' => (float) $order->grand_total,
            'table' => $order->restaurantTable, 'placed_by' => $order->placedByStaff,
            'items' => $order->items->map(fn (OrderItem $item) => [
                'product_id' => $item->product_id, 'name' => $item->name, 'price' => (float) $item->price,
                'qty' => $item->qty, 'line_total' => (float) $item->line_total, 'kot_status' => $item->kot_status,
            ]),
            'created_at' => $order->created_at->toISOString(),
        ];
    }
}
