<?php

namespace App\Services;

use App\Models\AuditLog;
use App\Models\Order;
use App\Models\Product;
use App\Models\ProductAddon;
use App\Models\Register;
use App\Models\VariantOption;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Symfony\Component\HttpKernel\Exception\HttpException;

class OrderService
{
    public function createPaidOrder(array $payload, int $userId, int $outletId): Order
    {
        return DB::transaction(function () use ($payload, $userId, $outletId): Order {
            $clientOrderId = Arr::get($payload, 'client_order_id');

            if ($clientOrderId) {
                $existing = Order::query()->where('client_order_id', $clientOrderId)->first();
                if ($existing) {
                    return $existing->load(['items', 'payments']);
                }
            }

            $registerId = (int) ($payload['register_id'] ?? 0);
            if ($registerId === 0) {
                $registerId = (int) Register::query()
                    ->where('outlet_id', $outletId)
                    ->orderBy('id')
                    ->value('id');
            }
            abort_if($registerId === 0, 422, 'Register outlet belum tersedia.');

            $order = new Order();
            $order->fill([
                'client_order_id' => $clientOrderId ?: (string) Str::uuid(),
                'outlet_id' => $outletId,
                'register_id' => $registerId,
                'user_id' => $userId,
                'status' => 'PAID',
                'kitchen_status' => 'NEW',
                'order_type' => $payload['order_type'],
                'table_number' => $payload['table_number'] ?? null,
                'discount' => (int) ($payload['discount'] ?? 0),
                'paid_at' => now(),
                'synced_at' => now(),
                'client_created_at' => isset($payload['client_created_at']) ? Carbon::parse($payload['client_created_at']) : now(),
            ]);
            $order->save();

            $subtotal = 0;
            foreach ($payload['items'] as $item) {
                $product = Product::query()->with(['addons', 'variants.options'])->findOrFail($item['product_id']);

                $base = (int) $product->base_price;
                $variantSnapshot = null;
                $variantDelta = 0;

                if (! empty($item['variant_option_id'])) {
                    $option = VariantOption::query()
                        ->with('variant')
                        ->findOrFail($item['variant_option_id']);
                    $variantDelta = (int) $option->price_delta;
                    $variantSnapshot = [
                        'group' => $option->variant?->name,
                        'option' => $option->name,
                        'price_delta' => $variantDelta,
                    ];
                }

                $addonSnapshot = [];
                $addonsTotal = 0;
                if (! empty($item['addon_ids'])) {
                    $addons = ProductAddon::query()
                        ->whereIn('id', $item['addon_ids'])
                        ->get(['id', 'name', 'price']);

                    foreach ($addons as $addon) {
                        $addonsTotal += (int) $addon->price;
                        $addonSnapshot[] = [
                            'id' => $addon->id,
                            'name' => $addon->name,
                            'price' => (int) $addon->price,
                        ];
                    }
                }

                $qty = (int) $item['qty'];
                $lineTotal = ($base + $variantDelta + $addonsTotal) * $qty;
                $subtotal += $lineTotal;

                $order->items()->create([
                    'product_id' => $product->id,
                    'name_snapshot' => $product->name,
                    'base_price' => $base,
                    'qty' => $qty,
                    'note' => $item['note'] ?? null,
                    'variant_snapshot' => $variantSnapshot,
                    'addons_snapshot' => $addonSnapshot,
                    'line_total' => $lineTotal,
                ]);
            }

            $tax = $this->calculatePercent($subtotal - $order->discount, (int) $order->outlet->tax_percent);
            $service = $this->calculatePercent($subtotal - $order->discount, (int) $order->outlet->service_percent);
            $total = max(0, $subtotal - $order->discount + $tax + $service);

            $paymentTotal = (int) collect($payload['payments'])->sum(fn ($payment) => (int) $payment['amount']);
            if ($paymentTotal < $total) {
                throw new HttpException(422, 'Total pembayaran kurang dari grand total.');
            }

            foreach ($payload['payments'] as $payment) {
                $order->payments()->create([
                    'kind' => 'PAYMENT',
                    'method' => $payment['method'],
                    'amount' => (int) $payment['amount'],
                    'reference' => $payment['reference'] ?? null,
                ]);
            }

            $order->update([
                'subtotal' => $subtotal,
                'tax' => $tax,
                'service' => $service,
                'total' => $total,
                'receipt_number' => $this->nextReceiptNumber($outletId),
            ]);

            return $order->load(['items', 'payments']);
        });
    }

    public function voidOrder(Order $order, int $userId, string $reason): Order
    {
        DB::transaction(function () use ($order, $userId, $reason): void {
            $order->update(['status' => 'VOIDED']);
            AuditLog::query()->create([
                'user_id' => $userId,
                'action' => 'VOID_ORDER',
                'entity_type' => Order::class,
                'entity_id' => $order->id,
                'metadata' => ['reason' => $reason],
            ]);
        });

        return $order->fresh();
    }

    public function refundOrder(Order $order, int $userId, int $amount, string $reason, string $method = 'CASH', ?string $reference = null): Order
    {
        return DB::transaction(function () use ($order, $userId, $amount, $reason, $method, $reference): Order {
            if ($order->status !== 'PAID') {
                throw new HttpException(422, 'Hanya order PAID yang bisa direfund.');
            }

            $remaining = max(0, (int) $order->total - (int) $order->refund_total);
            if ($amount > $remaining) {
                throw new HttpException(422, 'Nominal refund melebihi sisa yang bisa direfund.');
            }

            $order->payments()->create([
                'kind' => 'REFUND',
                'method' => $method,
                'amount' => $amount,
                'reference' => $reference,
            ]);

            $newRefundTotal = (int) $order->refund_total + $amount;
            $newStatus = $newRefundTotal >= (int) $order->total ? 'REFUNDED' : $order->status;
            $order->update([
                'refund_total' => $newRefundTotal,
                'refunded_at' => now(),
                'refund_reason' => $reason,
                'status' => $newStatus,
            ]);

            AuditLog::query()->create([
                'user_id' => $userId,
                'action' => 'REFUND_ORDER',
                'entity_type' => Order::class,
                'entity_id' => $order->id,
                'metadata' => ['amount' => $amount, 'reason' => $reason, 'method' => $method],
            ]);

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

    public function updateKitchenStatus(Order $order, int $userId, string $status): Order
    {
        $valid = ['NEW', 'PREPARING', 'READY', 'SERVED'];
        if (! in_array($status, $valid, true)) {
            throw new HttpException(422, 'Kitchen status tidak valid.');
        }

        $order->update(['kitchen_status' => $status]);
        AuditLog::query()->create([
            'user_id' => $userId,
            'action' => 'UPDATE_KITCHEN_STATUS',
            'entity_type' => Order::class,
            'entity_id' => $order->id,
            'metadata' => ['kitchen_status' => $status],
        ]);

        return $order->fresh();
    }

    private function calculatePercent(int $base, int $percent): int
    {
        return (int) floor(max(0, $base) * $percent / 100);
    }

    private function nextReceiptNumber(int $outletId): string
    {
        $date = now()->format('Ymd');
        $prefix = "INV-{$date}-";

        $last = Order::query()
            ->where('outlet_id', $outletId)
            ->whereDate('created_at', now()->toDateString())
            ->whereNotNull('receipt_number')
            ->orderByDesc('id')
            ->value('receipt_number');

        $sequence = 1;
        if ($last && str_starts_with($last, $prefix)) {
            $sequence = (int) substr($last, -4) + 1;
        }

        return $prefix.str_pad((string) $sequence, 4, '0', STR_PAD_LEFT);
    }
}
