<?php

declare(strict_types=1);

namespace App\Services;

use App\Enums\Status;
use App\Models\Order;
use App\Models\Payment;
use App\Models\Product;
use App\Models\User;
use App\Models\WalletTransaction;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use InvalidArgumentException;
use RuntimeException;

class OrderService
{
    /**
     * @param  array{
     *     address: array{title?: string|null, fullName: string, phone: string, address: string, province_id?: int|null, county_id?: int|null},
     *     items: list<array{product_id: int, quantity: int}>,
     *     payable: int,
     *     payment_method: string,
     *     notes?: string|null,
     *     gateway?: string|null,
     * }  $payload
     * @return array{orders: Collection<int, Order>, payment: Payment|null, payable: int, gateway_url: string|null, gateway: array<string, mixed>|null}
     */
    public function place(User $user, array $payload): array
    {
        $lines = $this->buildLines($payload['items']);
        $payable = (int) $lines->sum('total_price');

        if ($payable !== (int) $payload['payable']) {
            throw new InvalidArgumentException('مبلغ قابل پرداخت با قیمت محصولات هم‌خوانی ندارد.');
        }

        if ($payable <= 0) {
            throw new InvalidArgumentException('مبلغ سفارش نامعتبر است.');
        }

        return DB::transaction(function () use ($user, $payload, $lines, $payable) {
            $groupId = (string) Str::uuid();
            $orderDate = now();
            $address = $payload['address'];
            $orders = collect();

            foreach ($lines as $index => $line) {
                /** @var Product $product */
                $product = $line['product'];

                $order = Order::create([
                    'user_id' => $user->id,
                    'producer_id' => $product->producer_id,
                    'product_id' => $product->id,
                    'order_number' => $this->generateOrderNumber($index),
                    'type' => 'order',
                    'quantity' => $line['quantity'],
                    'unit' => $product->unit,
                    'unit_price' => $line['unit_price'],
                    'total_price' => $line['total_price'],
                    'order_date' => $orderDate,
                    'status' => 'pending',
                    'province_id' => $address['province_id'] ?? $product->province_id,
                    'county_id' => $address['county_id'] ?? $product->county_id,
                    'delivery_address' => $address['address'],
                    'notes' => $payload['notes'] ?? null,
                    'metadata' => [
                        'group_id' => $groupId,
                        'payment_method' => $payload['payment_method'],
                        'address' => [
                            'title' => $address['title'] ?? null,
                            'full_name' => $address['fullName'],
                            'phone' => $address['phone'],
                            'address' => $address['address'],
                        ],
                        'item' => [
                            'product_id' => $product->id,
                            'product_name' => $product->name,
                            'quantity' => $line['quantity'],
                            'unit_price' => $line['unit_price'],
                            'total_price' => $line['total_price'],
                        ],
                    ],
                    'created_by' => $user->id,
                    'updated_by' => $user->id,
                ]);

                $orders->push($order);
            }

            /** @var Order $primary */
            $primary = $orders->first();

            if ($payload['payment_method'] === 'wallet') {
                $payment = $this->payWithWallet($user, $orders, $primary, $payable);

                return [
                    'orders' => $orders->values(),
                    'payment' => $payment,
                    'payable' => $payable,
                    'gateway_url' => null,
                    'gateway' => null,
                ];
            }

            $gatewayResult = $this->payWithGateway(
                $user,
                $primary,
                $payable,
                $payload['gateway'] ?? config('payment.default', 'zibal'),
            );

            return [
                'orders' => $orders->values(),
                'payment' => $gatewayResult['payment'],
                'payable' => $payable,
                'gateway_url' => $gatewayResult['gateway_url'],
                'gateway' => $gatewayResult['gateway'],
            ];
        });
    }

    public function markGroupPaid(Order $order): void
    {
        $groupId = $order->metadata['group_id'] ?? null;

        $query = Order::query()->where('id', $order->id);

        if ($groupId) {
            $query = Order::query()->where('metadata->group_id', $groupId);
        }

        $orders = $query->get();

        foreach ($orders as $item) {
            if ($item->status === 'pending') {
                $item->update(['status' => 'confirmed']);
                $this->decrementStock($item);
            }
        }

        $this->createProjectsFromOrders($orders);
        $this->syncPaymentProjectId($orders->first()?->fresh());
    }

    /**
     * @param  Collection<int, Order>  $orders
     */
    private function createProjectsFromOrders(Collection $orders): void
    {
        $service = new ProjectFromOrderService;

        foreach ($orders as $order) {
            $service->createFromOrder($order->fresh());
        }
    }

    private function syncPaymentProjectId(?Order $order): void
    {
        if (! $order?->project_id) {
            return;
        }

        Payment::query()
            ->where('model_type', Order::class)
            ->where('model_id', $order->id)
            ->update(['project_id' => $order->project_id]);
    }

    /**
     * @param  list<array{product_id: int, quantity: int}>  $items
     * @return Collection<int, array{product: Product, quantity: int, unit_price: int, total_price: int}>
     */
    private function buildLines(array $items): Collection
    {
        $lines = collect();

        foreach ($items as $item) {
            $product = Product::query()
                ->whereKey($item['product_id'])
                ->where('status', 'active')
                ->where('is_active', true)
                ->first();

            if (! $product) {
                throw new InvalidArgumentException('محصول انتخاب‌شده موجود یا فعال نیست.');
            }

            $quantity = (int) $item['quantity'];

            if ($product->count !== null && $product->count < $quantity) {
                throw new InvalidArgumentException("موجودی محصول «{$product->name}» کافی نیست.");
            }

            $unitPrice = (int) ($product->total_price ?? $product->price ?? 0);

            if ($unitPrice <= 0) {
                throw new InvalidArgumentException("قیمت محصول «{$product->name}» نامعتبر است.");
            }

            $lines->push([
                'product' => $product,
                'quantity' => $quantity,
                'unit_price' => $unitPrice,
                'total_price' => $unitPrice * $quantity,
            ]);
        }

        return $lines;
    }

    private function generateOrderNumber(int $index): string
    {
        do {
            $suffix = $index > 0 ? '-' . ($index + 1) : '';
            $number = 'ORD-' . now()->format('YmdHis') . '-' . Str::upper(Str::random(4)) . $suffix;
        } while (Order::withTrashed()->where('order_number', $number)->exists());

        return $number;
    }

    /**
     * @param  Collection<int, Order>  $orders
     */
    private function payWithWallet(User $user, Collection $orders, Order $primary, int $payable): Payment
    {
        if ((int) $user->wallet < $payable) {
            throw new RuntimeException('موجودی کیف پول کافی نیست.');
        }

        $user->decrement('wallet', $payable);

        $payment = Payment::create([
            'user_id' => $user->id,
            'price' => $payable,
            'gateway' => 'wallet',
            'status' => Status::PAID->value,
            'model_type' => Order::class,
            'model_id' => $primary->id,
            'project_id' => $primary->project_id,
            'created_by' => $user->id,
        ]);

        WalletTransaction::create([
            'user_id' => $user->id,
            'type' => WalletTransaction::TYPE_PURCHASE,
            'amount' => $payable,
            'balance_after' => $user->fresh()->wallet,
            'description' => 'پرداخت سفارش ' . $primary->order_number,
            'status' => WalletTransaction::STATUS_COMPLETED,
            'payment_id' => $payment->id,
            'meta' => [
                'order_id' => $primary->id,
                'order_ids' => $orders->pluck('id')->values()->all(),
                'group_id' => $primary->metadata['group_id'] ?? null,
            ],
        ]);

        foreach ($orders as $order) {
            $order->update(['status' => 'confirmed']);
            $this->decrementStock($order);
        }

        $this->createProjectsFromOrders($orders);

        $primary->refresh();

        if ($primary->project_id) {
            $payment->update(['project_id' => $primary->project_id]);
        }

        return $payment;
    }

    /**
     * @return array{payment: Payment, gateway_url: string, gateway: array{action: string, method: string, inputs: array<string, mixed>}}
     */
    private function payWithGateway(User $user, Order $primary, int $payable, string $gateway): array
    {
        return (new PaymentService)->startGateway(
            $user,
            $payable,
            Order::class,
            $primary->id,
            $gateway,
            $primary->order_number,
            $primary->project_id,
        );
    }

    private function decrementStock(Order $order): void
    {
        if (! $order->product_id) {
            return;
        }

        $product = Product::query()->lockForUpdate()->find($order->product_id);

        if (! $product || $product->count === null) {
            return;
        }

        $product->decrement('count', (int) $order->quantity);
        $product->increment('order_count');
    }
}
