<?php

namespace App\Services\Website;

use App\Models\Order;
use App\Services\Accounting\AccountingVoucherService;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class WebsiteOrderPostingService
{
    public function __construct(private readonly AccountingVoucherService $accountingVoucherService)
    {
    }

    public function postPaidOrder(Order $order, ?string $paymentMethod = null): ?Order
    {
        if (! Schema::hasTable('accounting_vouchers') || ! Schema::hasTable('accounting_voucher_lines')) {
            return $order;
        }

        return DB::transaction(function () use ($order, $paymentMethod) {
            $lockedOrder = Order::withoutGlobalScopes()
                ->with(['details', 'payments'])
                ->lockForUpdate()
                ->find($order->id);

            if (! $lockedOrder || $lockedOrder->payment_status !== 'paid' || $lockedOrder->order_type === 'parcel') {
                return $lockedOrder;
            }

            if ((float) $lockedOrder->order_amount <= 0) {
                return $lockedOrder;
            }

            if (Schema::hasColumn($lockedOrder->getTable(), 'sale_channel') && blank($lockedOrder->sale_channel)) {
                $lockedOrder->sale_channel = 'website';
            }

            if (Schema::hasColumn($lockedOrder->getTable(), 'accounting_voucher_id') && ! empty($lockedOrder->accounting_voucher_id)) {
                $lockedOrder->save();
                return $lockedOrder;
            }

            $payments = $this->paymentLines($lockedOrder, $paymentMethod);
            $voucher = $this->accountingVoucherService->postWebsiteSale(
                order: $lockedOrder,
                payments: $payments,
                cogs: $this->costOfGoodsSold($lockedOrder),
            );

            if (Schema::hasColumn($lockedOrder->getTable(), 'accounting_voucher_id')) {
                $lockedOrder->accounting_voucher_id = $voucher->id;
            }
            $lockedOrder->save();

            return $lockedOrder->fresh(['details', 'payments']);
        });
    }

    private function paymentLines(Order $order, ?string $paymentMethod): array
    {
        $orderAmount = round((float) $order->order_amount, 8);
        $payments = $order->payments
            ->where('payment_status', 'paid')
            ->map(fn ($payment) => [
                'payment_method' => $payment->payment_method ?: ($paymentMethod ?: $order->payment_method),
                'amount' => round((float) $payment->amount, 8),
            ])
            ->filter(fn ($payment) => $payment['amount'] > 0)
            ->values();

        $paidAmount = round((float) $payments->sum('amount'), 8);
        if ($paidAmount <= 0) {
            return [[
                'payment_method' => $paymentMethod ?: $order->payment_method ?: 'digital_payment',
                'amount' => $orderAmount,
            ]];
        }

        if ($paidAmount < $orderAmount) {
            $payments->push([
                'payment_method' => $paymentMethod ?: $order->payment_method ?: 'digital_payment',
                'amount' => round($orderAmount - $paidAmount, 8),
            ]);
        }

        if ($paidAmount > $orderAmount && $orderAmount > 0) {
            $remaining = $orderAmount;
            $payments = $payments
                ->map(function ($payment) use (&$remaining) {
                    $amount = min((float) $payment['amount'], max($remaining, 0));
                    $remaining = round($remaining - $amount, 8);
                    return array_merge($payment, ['amount' => round($amount, 8)]);
                })
                ->filter(fn ($payment) => $payment['amount'] > 0)
                ->values();
        }

        return $payments->all();
    }

    private function costOfGoodsSold(Order $order): float
    {
        if (! Schema::hasTable('purchase_items')) {
            return 0;
        }

        $total = 0.0;
        foreach ($order->details as $detail) {
            $itemId = (int) ($detail->item_id ?? $detail->product_id ?? 0);
            if ($itemId <= 0) {
                continue;
            }

            $quantity = (int) ($detail->quantity ?? $detail->qty ?? 0);
            if ($quantity <= 0) {
                continue;
            }

            $variationType = $detail->variant ?: data_get(json_decode($detail->variation ?: '[]', true), '0.type');
            $query = DB::table('purchase_items')->where('item_id', $itemId);
            if ($variationType && Schema::hasColumn('purchase_items', 'variation_type')) {
                $query->where('variation_type', $variationType);
            }

            $total += round(((float) ($query->avg('purchase_price') ?: 0)) * $quantity, 8);
        }

        return round($total, 8);
    }
}
