<?php

namespace App\Services;

use App\Models\Order;
use App\Models\OrderStatusHistory;
use App\Repositories\Eloquent\OrderRepository;
use App\Repositories\Eloquent\PaymentRepository;
use App\Repositories\Eloquent\ProductRepository;
use Illuminate\Support\Facades\DB;

class OrderService
{
    public function __construct(
        protected OrderRepository $orderRepository,
        protected PaymentRepository $paymentRepository,
        protected ProductRepository $productRepository,
        protected InventoryService $inventoryService,
        protected LoggingService $loggingService,
        protected ProductionBatchService $productionBatchService
    ) {}

    public function getAll()
    {
        $this->loggingService->logInfo('Fetching all orders');
        return $this->orderRepository->all();
    }

    public function getForBranch(int $branchId, int $limit = 50)
    {
        $this->loggingService->logInfo('Fetching orders for branch', ['branch_id' => $branchId]);
        return $this->orderRepository->getForBranch($branchId, $limit);
    }

    public function getActiveForBranch(int $branchId)
    {
        $this->loggingService->logInfo('Fetching active orders for branch', ['branch_id' => $branchId]);
        return $this->orderRepository->getActiveForBranch($branchId);
    }

    public function getActiveAll()
    {
        $this->loggingService->logInfo('Fetching all active orders across all branches');
        return $this->orderRepository->getActiveAll();
    }

    public function getByStatus(int $branchId, string $status)
    {
        $this->loggingService->logInfo('Fetching orders by status', ['branch_id' => $branchId, 'status' => $status]);
        return $this->orderRepository->getByStatus($branchId, $status);
    }

    public function getTodayForBranch(int $branchId)
    {
        $this->loggingService->logInfo('Fetching today orders for branch', ['branch_id' => $branchId]);
        return $this->orderRepository->getTodayForBranch($branchId);
    }

    public function find(int $id)
    {
        $this->loggingService->logInfo('Finding order', ['id' => $id]);
        return $this->orderRepository->findWithRelations($id);
    }

    public function create(array $data)
    {
        $this->loggingService->logInfo('Creating order', ['data' => $data]);

        return DB::transaction(function () use ($data) {
            $items = $data['items'] ?? [];
            unset($data['items']);

            $data['order_number'] = Order::generateOrderNumber($data['branch_id']);
            $data['status'] = $data['status'] ?? Order::STATUS_PENDING;

            $order = $this->orderRepository->create($data);

            foreach ($items as $item) {
                $product = $this->productRepository->find($item['product_id']);

                $order->items()->create([
                    'product_id' => $item['product_id'],
                    'product_name' => $product->name,
                    'quantity' => $item['quantity'],
                    'unit_price' => $item['unit_price'] ?? $product->base_price,
                    'total_price' => ($item['unit_price'] ?? $product->base_price) * $item['quantity'],
                    'options' => $item['options'] ?? null,
                    'inner_options' => $item['inner_options'] ?? null,
                    'special_instructions' => $item['special_instructions'] ?? null,
                    'cup_size_id' => $item['cup_size_id'] ?? null,
                    'flavors' => $item['flavors'] ?? null,
                    'total_weight_grams' => $item['total_weight_grams'] ?? null,
                ]);
            }

            $order->calculateTotals();

            // Record status history
            OrderStatusHistory::create([
                'order_id' => $order->id,
                'user_id' => $data['user_id'],
                'from_status' => null,
                'to_status' => $order->status,
                'notes' => 'Order created',
            ]);

            return $order->fresh(['items.product', 'user', 'branch']);
        });
    }

    public function update(int $id, array $data)
    {
        $this->loggingService->logInfo('Updating order', ['id' => $id, 'data' => $data]);

        return DB::transaction(function () use ($id, $data) {
            $items = $data['items'] ?? null;
            unset($data['items']);

            $order = $this->orderRepository->update($id, $data);

            if ($items !== null) {
                // Delete existing items and recreate
                $order->items()->delete();

                foreach ($items as $item) {
                    $product = $this->productRepository->find($item['product_id']);

                    $order->items()->create([
                        'product_id' => $item['product_id'],
                        'product_name' => $product->name,
                        'quantity' => $item['quantity'],
                        'unit_price' => $item['unit_price'] ?? $product->base_price,
                        'total_price' => ($item['unit_price'] ?? $product->base_price) * $item['quantity'],
                        'options' => $item['options'] ?? null,
                        'inner_options' => $item['inner_options'] ?? null,
                        'special_instructions' => $item['special_instructions'] ?? null,
                    ]);
                }

                $order->calculateTotals();
            }

            return $order->fresh(['items.product', 'user', 'branch']);
        });
    }

    public function updateStatus(int $id, string $status, ?int $userId = null, ?string $notes = null)
    {
        $this->loggingService->logInfo('Updating order status', ['id' => $id, 'status' => $status]);

        return DB::transaction(function () use ($id, $status, $userId, $notes) {
            $order = $this->orderRepository->find($id);
            if (!$order) {
                return null;
            }

            $fromStatus = $order->status;

            $updateData = ['status' => $status];

            if ($status === Order::STATUS_COMPLETED) {
                $updateData['completed_at'] = now();
                // Deduct inventory when order is completed
                $this->deductInventoryForOrder($order);
            }

            if ($status === Order::STATUS_CANCELLED) {
                $updateData['cancelled_at'] = now();
                $updateData['cancellation_reason'] = $notes;
            }

            $order = $this->orderRepository->update($id, $updateData);

            // Record status history
            OrderStatusHistory::create([
                'order_id' => $order->id,
                'user_id' => $userId,
                'from_status' => $fromStatus,
                'to_status' => $status,
                'notes' => $notes,
            ]);

            return $order->fresh(['items.product', 'user', 'branch', 'statusHistory']);
        });
    }

    public function cancel(int $id, ?int $userId = null, ?string $reason = null)
    {
        return $this->updateStatus($id, Order::STATUS_CANCELLED, $userId, $reason);
    }

    public function delete(int $id)
    {
        $this->loggingService->logInfo('Deleting order', ['id' => $id]);
        return $this->orderRepository->delete($id);
    }

    // Payment Methods
    public function addPayment(int $orderId, array $data)
    {
        $this->loggingService->logInfo('Adding payment to order', ['order_id' => $orderId, 'data' => $data]);

        $order = $this->orderRepository->find($orderId);
        if (!$order) {
            return null;
        }

        $data['order_id'] = $orderId;

        // Calculate change for cash payments
        if ($data['method'] === 'cash' && isset($data['tendered'])) {
            $data['change'] = max(0, $data['tendered'] - $data['amount']);
        }

        $payment = $this->paymentRepository->create($data);

        // Update cash register if payment method is cash
        if ($data['method'] === 'cash') {
            $this->updateCashRegisterForPayment($order, $data['tendered'] ?? $data['amount'], $data['change'] ?? 0);
        }

        return $payment->fresh('user');
    }

    /**
     * Update cash register with payment and change
     */
    private function updateCashRegisterForPayment(Order $order, float $received, float $change): void
    {
        $cashRegister = \App\Models\CashRegister::where('branch_id', $order->branch_id)
            ->where('status', 'open')
            ->latest()
            ->first();

        if (!$cashRegister) {
            // No open cash register - skip update (payment still recorded)
            $this->loggingService->logWarning('No open cash register found for payment', [
                'branch_id' => $order->branch_id,
                'order_id' => $order->id
            ]);
            return;
        }

        // Get CashRegisterService
        $cashRegisterService = app(\App\Services\CashRegisterService::class);

        // Record transaction: Cash received (tendered amount added to drawer)
        $cashRegisterService->recordTransaction(
            $cashRegister->id,
            \App\Models\CashRegisterTransaction::TYPE_BILL_PAYMENT,
            $received,
            auth()->id() ?? $order->user_id,
            'Order',
            $order->id,
            "Cash received for Order #{$order->order_number}"
        );

        // Record transaction: Change given (money removed from drawer)
        if ($change > 0) {
            $cashRegisterService->recordTransaction(
                $cashRegister->id,
                \App\Models\CashRegisterTransaction::TYPE_CHANGE_GIVEN,
                $change,
                auth()->id() ?? $order->user_id,
                'Order',
                $order->id,
                "Change given for Order #{$order->order_number}"
            );
        }
    }

    public function getPayments(int $orderId)
    {
        $this->loggingService->logInfo('Fetching payments for order', ['order_id' => $orderId]);
        return $this->paymentRepository->getForOrder($orderId);
    }

    public function refundPayment(int $paymentId, ?int $userId = null, ?string $notes = null)
    {
        $this->loggingService->logInfo('Refunding payment', ['payment_id' => $paymentId]);
        return $this->paymentRepository->update($paymentId, [
            'status' => 'refunded',
            'notes' => $notes,
        ]);
    }

    // Add discount to order
    public function applyDiscount(int $orderId, float $amount, ?string $reason = null)
    {
        $this->loggingService->logInfo('Applying discount', ['order_id' => $orderId, 'amount' => $amount]);

        $order = $this->orderRepository->find($orderId);
        if (!$order) {
            return null;
        }

        $order = $this->orderRepository->update($orderId, [
            'discount_amount' => $amount,
            'discount_reason' => $reason,
        ]);

        $order->calculateTotals();

        return $order->fresh();
    }

    protected function deductInventoryForOrder(Order $order): void
    {
        $this->loggingService->logInfo('Deducting inventory for order', ['order_id' => $order->id]);

        foreach ($order->items as $item) {
            // Check if this is a weight-based order (has cup size and flavors)
            if ($item->cup_size_id && $item->flavors && is_array($item->flavors)) {
                // Weight-based deduction for multi-flavor cups
                $this->deductWeightBasedInventory($order, $item);
            } else {
                // Legacy scoop-based deduction
                $this->deductScoopBasedInventory($order, $item);
            }
        }
    }

    /**
     * Deduct inventory using weight-based tracking (new system)
     */
    protected function deductWeightBasedInventory(Order $order, $item): void
    {
        foreach ($item->flavors as $flavor) {
            $productId = $flavor['product_id'];
            $weightGrams = $flavor['weight_grams'];

            // Find an active batch for this flavor's product at this branch
            $batch = $this->productionBatchService->findAvailableBatchForProduct(
                $order->branch_id,
                $productId
            );

            if ($batch && $batch->hasRemainingWeight()) {
                try {
                    // Deduct weight from batch and create batch deduction record
                    $this->productionBatchService->recordWeightDeduction(
                        $batch->id,
                        $order->id,
                        $item->id,
                        $weightGrams,
                        "Order #{$order->order_number} - {$flavor['name']}"
                    );

                    $this->loggingService->logInfo('Weight deduction recorded', [
                        'order_id' => $order->id,
                        'product_id' => $productId,
                        'flavor' => $flavor['name'],
                        'batch_id' => $batch->id,
                        'weight_grams' => $weightGrams,
                    ]);
                } catch (\Exception $e) {
                    $this->loggingService->logError('Failed to deduct weight from batch', [
                        'order_id' => $order->id,
                        'product_id' => $productId,
                        'flavor' => $flavor['name'],
                        'error' => $e->getMessage(),
                    ]);
                }
            } else {
                $this->loggingService->logWarning('No active batch with weight found for product', [
                    'order_id' => $order->id,
                    'product_id' => $productId,
                    'flavor' => $flavor['name'],
                    'branch_id' => $order->branch_id,
                ]);
            }
        }
    }

    /**
     * Deduct inventory using scoop-based tracking (legacy system)
     */
    protected function deductScoopBasedInventory(Order $order, $item): void
    {
        // Find an active batch for this product at this branch
        $batch = $this->productionBatchService->findAvailableBatchForProduct(
            $order->branch_id,
            $item->product_id
        );

        if ($batch) {
            try {
                // Calculate scoops based on quantity (1 item = 1 scoop by default)
                $scoopCount = $item->quantity;

                // Check if there's a size option that affects scoop count
                if ($item->options) {
                    $options = is_array($item->options) ? $item->options : json_decode($item->options, true);
                    foreach ($options as $option) {
                        // If large size, count as 2 scoops, medium as 1.5, etc.
                        if (isset($option['name'])) {
                            $sizeName = strtolower($option['name']);
                            if ($sizeName === 'large') {
                                $scoopCount = $item->quantity * 2;
                            } elseif ($sizeName === 'medium') {
                                $scoopCount = (int) ceil($item->quantity * 1.5);
                            }
                        }
                    }
                }

                // Record scoop sale and deduct from batch
                $this->productionBatchService->recordScoopSale(
                    $batch->id,
                    $order->id,
                    $item->id,
                    $item->product_id,
                    $order->branch_id,
                    $scoopCount,
                    $item->total_price
                );

                $this->loggingService->logInfo('Scoop sale recorded', [
                    'order_id' => $order->id,
                    'product_id' => $item->product_id,
                    'batch_id' => $batch->id,
                    'scoop_count' => $scoopCount,
                ]);
            } catch (\Exception $e) {
                $this->loggingService->logError('Failed to deduct scoops from batch', [
                    'order_id' => $order->id,
                    'product_id' => $item->product_id,
                    'error' => $e->getMessage(),
                ]);
            }
        } else {
            $this->loggingService->logInfo('No active batch found for product', [
                'order_id' => $order->id,
                'product_id' => $item->product_id,
                'branch_id' => $order->branch_id,
            ]);
        }
    }

    // Reporting
    public function getOrdersByDateRange(int $branchId, $startDate, $endDate)
    {
        $this->loggingService->logInfo('Fetching orders by date range', [
            'branch_id' => $branchId,
            'start_date' => $startDate,
            'end_date' => $endDate
        ]);
        return $this->orderRepository->getByDateRange($branchId, $startDate, $endDate);
    }

    public function getDailySummary(int $branchId, $date = null)
    {
        $date = $date ?? today();

        $orders = $this->orderRepository->getByDateRange($branchId, $date->copy()->startOfDay(), $date->copy()->endOfDay());

        $completed = $orders->where('status', Order::STATUS_COMPLETED);

        return [
            'date' => $date->toDateString(),
            'total_orders' => $orders->count(),
            'completed_orders' => $completed->count(),
            'cancelled_orders' => $orders->where('status', Order::STATUS_CANCELLED)->count(),
            'total_sales' => $completed->sum('total_amount'),
            'average_order_value' => $completed->count() > 0 ? $completed->avg('total_amount') : 0,
        ];
    }

    // Temporary Bill Methods
    public function createTempBill(array $data)
    {
        $this->loggingService->logInfo('Creating temp bill', ['data' => $data]);

        return DB::transaction(function () use ($data) {
            $items = $data['items'] ?? [];
            unset($data['items']);

            $data['order_number'] = Order::generateOrderNumber($data['branch_id']);
            $data['is_temp_bill'] = true;
            $data['status'] = Order::STATUS_TEMP_BILL;

            $order = $this->orderRepository->create($data);

            foreach ($items as $item) {
                $product = $this->productRepository->find($item['product_id']);

                $order->items()->create([
                    'product_id' => $item['product_id'],
                    'product_name' => $product->name,
                    'quantity' => $item['quantity'],
                    'unit_price' => $item['unit_price'] ?? $product->base_price,
                    'total_price' => ($item['unit_price'] ?? $product->base_price) * $item['quantity'],
                    'options' => $item['options'] ?? null,
                    'inner_options' => $item['inner_options'] ?? null,
                    'special_instructions' => $item['special_instructions'] ?? null,
                    'cup_size_id' => $item['cup_size_id'] ?? null,
                    'flavors' => $item['flavors'] ?? null,
                    'total_weight_grams' => $item['total_weight_grams'] ?? null,
                ]);
            }

            $order->calculateTotals();

            // Record status history
            OrderStatusHistory::create([
                'order_id' => $order->id,
                'user_id' => $data['user_id'],
                'from_status' => null,
                'to_status' => Order::STATUS_TEMP_BILL,
                'notes' => 'Temporary bill created',
            ]);

            return $order->fresh(['items.product', 'user', 'branch']);
        });
    }

    public function getTempBillsForBranch(int $branchId)
    {
        $this->loggingService->logInfo('Fetching temp bills for branch', ['branch_id' => $branchId]);
        return Order::tempBills()->forBranch($branchId)->with(['items.product', 'user'])->latest()->get();
    }

    public function updateTempBill(int $id, array $data, ?int $userId = null)
    {
        $this->loggingService->logInfo('Updating temp bill', ['id' => $id, 'data' => $data]);

        return DB::transaction(function () use ($id, $data, $userId) {
            $order = $this->orderRepository->find($id);

            if (!$order || !$order->isTempBill()) {
                throw new \Exception('Order is not a temporary bill or does not exist');
            }

            $items = $data['items'] ?? null;
            unset($data['items']);

            // Update order details
            if (!empty($data)) {
                $order = $this->orderRepository->update($id, $data);
            }

            // Update items if provided
            if ($items !== null) {
                // Delete existing items and recreate
                $order->items()->delete();

                foreach ($items as $item) {
                    $product = $this->productRepository->find($item['product_id']);

                    $order->items()->create([
                        'product_id' => $item['product_id'],
                        'product_name' => $product->name,
                        'quantity' => $item['quantity'],
                        'unit_price' => $item['unit_price'] ?? $product->base_price,
                        'total_price' => ($item['unit_price'] ?? $product->base_price) * $item['quantity'],
                        'options' => $item['options'] ?? null,
                        'inner_options' => $item['inner_options'] ?? null,
                        'special_instructions' => $item['special_instructions'] ?? null,
                    ]);
                }

                $order->calculateTotals();
            }

            // Record modification in status history
            OrderStatusHistory::create([
                'order_id' => $order->id,
                'user_id' => $userId,
                'from_status' => Order::STATUS_TEMP_BILL,
                'to_status' => Order::STATUS_TEMP_BILL,
                'notes' => 'Temporary bill updated',
            ]);

            return $order->fresh(['items.product', 'user', 'branch']);
        });
    }

    public function finalizeTempBill(int $id, array $paymentData, ?int $userId = null)
    {
        $this->loggingService->logInfo('Finalizing temp bill', ['id' => $id, 'payment_data' => $paymentData]);

        return DB::transaction(function () use ($id, $paymentData, $userId) {
            $order = $this->orderRepository->find($id);

            if (!$order || !$order->isTempBill()) {
                throw new \Exception('Order is not a temporary bill or does not exist');
            }

            // Generate bill number
            $billNumber = Order::generateBillNumber($order->branch_id);

            // Update order to finalized state
            $order = $this->orderRepository->update($id, [
                'bill_number' => $billNumber,
                'status' => Order::STATUS_PENDING,
                'finalized_at' => now(),
            ]);

            // Add payment
            $paymentData['order_id'] = $id;
            $paymentData['user_id'] = $userId ?? $order->user_id;

            // Calculate change for cash payments
            if ($paymentData['method'] === 'cash' && isset($paymentData['tendered'])) {
                $paymentData['change'] = max(0, $paymentData['tendered'] - $paymentData['amount']);
            }

            $this->paymentRepository->create($paymentData);

            // Record status history
            OrderStatusHistory::create([
                'order_id' => $order->id,
                'user_id' => $userId,
                'from_status' => Order::STATUS_TEMP_BILL,
                'to_status' => Order::STATUS_PENDING,
                'notes' => 'Temporary bill finalized and payment received',
            ]);

            // Deduct inventory immediately since payment is complete
            $this->deductInventoryForOrder($order);

            // Mark as completed
            $this->updateStatus($id, Order::STATUS_COMPLETED, $userId, 'Order completed from temp bill');

            return $order->fresh(['items.product', 'user', 'branch', 'payments']);
        });
    }

    public function getDuplicateBillData(int $id)
    {
        $this->loggingService->logInfo('Fetching duplicate bill data', ['id' => $id]);

        $order = $this->orderRepository->findWithRelations($id);

        if (!$order) {
            return null;
        }

        // Increment print count
        $order->incrementPrintCount();

        return $order;
    }
}
