<?php

namespace App\Http\Controllers\Admin;

use App\CentralLogics\Helpers;
use App\Exceptions\OrderFinancialException;
use App\Http\Controllers\Controller;
use App\Models\AccountingAccount;
use App\Models\CustomerAddress;
use App\Models\Order;
use App\Models\OrderReceipt;
use App\Services\OrderFinancialService;
use Brian2694\Toastr\Facades\Toastr;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Schema;
use Illuminate\Validation\Rule;

class CustomerAdvanceController extends Controller
{
    public function __construct(private readonly OrderFinancialService $financialService)
    {
    }

    public function index(Request $request)
    {
        $this->authorizeOrderModule();

        $invoiceNumber = trim((string) $request->query('invoice_number'));
        $order = null;
        $searchError = null;

        if ($invoiceNumber !== '') {
            $orderId = $this->resolveOrderId($invoiceNumber);
            $order = $orderId ? $this->findOrder($orderId) : null;
            if (! $order) {
                $searchError = 'No order was found for this invoice number.';
            }
        }

        $accounts = Schema::hasTable('accounting_accounts')
            ? AccountingAccount::query()
                ->where('is_active', true)
                ->where('type', 'asset')
                ->orderBy('code')
                ->get()
            : collect();

        $summary = $order?->financialSummary();
        $customer = $order ? $this->customerDetails($order) : null;
        $courierLocked = $order && Schema::hasTable('order_courier_shipments')
            ? ($order->courierShipment?->hasSuccessfulBooking() ?? false)
            : false;
        $advanceReceiveBlocked = $courierLocked || ($order && ! $order->canReceiveAdvance());

        return view('admin-views.order.customer-advance', compact(
            'invoiceNumber',
            'order',
            'summary',
            'customer',
            'accounts',
            'courierLocked',
            'advanceReceiveBlocked',
            'searchError'
        ));
    }

    public function store(Request $request)
    {
        $this->authorizeOrderModule();
        $validated = $this->validateAdvance($request, true);
        $order = Order::withoutGlobalScopes()->findOrFail((int) $validated['order_id']);

        try {
            $receipt = $this->financialService->recordAdvance($order, $validated, auth('admin')->id());
            Toastr::success('Customer advance '.$receipt->voucher_no.' has been posted.');
        } catch (OrderFinancialException $exception) {
            Toastr::error($exception->getMessage());
        } catch (\Throwable $throwable) {
            report($throwable);
            Toastr::error('Customer advance could not be posted. Please try again.');
        }

        return redirect()->route('admin.order.customer-advance.index', ['invoice_number' => $order->id]);
    }

    public function update(OrderReceipt $receipt, Request $request)
    {
        $this->authorizeOrderModule();
        $validated = $this->validateAdvance($request);

        try {
            $this->financialService->updateAdvance($receipt, $validated, auth('admin')->id());
            Toastr::success('Customer advance has been updated and all balances were recalculated.');
        } catch (OrderFinancialException $exception) {
            Toastr::error($exception->getMessage());
        } catch (\Throwable $throwable) {
            report($throwable);
            Toastr::error('Customer advance could not be updated. Please try again.');
        }

        return redirect()->route('admin.order.customer-advance.index', ['invoice_number' => $receipt->order_id]);
    }

    public function void(OrderReceipt $receipt, Request $request)
    {
        $this->authorizeOrderModule();
        $validated = $request->validate([
            'reason' => ['required', 'string', 'max:500'],
        ]);

        try {
            $this->financialService->voidReceipt($receipt, auth('admin')->id(), $validated['reason']);
            Toastr::success('Customer advance has been cancelled and all balances were recalculated.');
        } catch (OrderFinancialException $exception) {
            Toastr::error($exception->getMessage());
        } catch (\Throwable $throwable) {
            report($throwable);
            Toastr::error('Customer advance could not be cancelled. Please try again.');
        }

        return redirect()->route('admin.order.customer-advance.index', ['invoice_number' => $receipt->order_id]);
    }

    private function validateAdvance(Request $request, bool $creating = false): array
    {
        $rules = [
            'amount' => ['required', 'numeric', 'gt:0'],
            'payment_method' => ['required', 'string', 'max:100'],
            'account_id' => [
                'required',
                'integer',
                Rule::exists('accounting_accounts', 'id')->where(
                    fn ($query) => $query->where('is_active', true)->where('type', 'asset')
                ),
            ],
            'received_at' => ['required', 'date'],
            'transaction_ref' => ['nullable', 'string', 'max:120'],
            'notes' => ['nullable', 'string', 'max:1000'],
        ];

        if ($creating) {
            $rules = [
                'order_id' => ['required', 'integer', 'exists:orders,id'],
                'idempotency_key' => ['required', 'uuid'],
            ] + $rules;
        }

        return $request->validate($rules);
    }

    private function findOrder(int $orderId): ?Order
    {
        $relations = [
            'customer',
            'guest',
            'payments',
            'receipts.account',
            'receipts.ledgerEntry',
            'receipts.createdByAdmin',
            'customerLedgerEntries',
        ];
        if (Schema::hasTable('order_courier_shipments')) {
            $relations[] = 'courierShipment';
        }

        return Order::withoutGlobalScopes()->with($relations)->find($orderId);
    }

    private function resolveOrderId(string $invoiceNumber): ?int
    {
        if (! preg_match('/(\d+)$/', $invoiceNumber, $matches)) {
            return null;
        }

        return (int) $matches[1];
    }

    private function customerDetails(Order $order): array
    {
        $address = $this->deliveryAddress($order);
        $customer = $order->is_guest ? $order->guest : $order->customer;
        $fallbackName = trim(implode(' ', array_filter([
            $customer?->f_name,
            $customer?->l_name,
        ])));

        return [
            'name' => $address['contact_person_name'] ?? ($fallbackName ?: 'Guest customer'),
            'mobile' => $address['contact_person_number'] ?? ($customer?->phone ?: 'N/A'),
            'district' => $address['district'] ?? $address['district_name'] ?? 'N/A',
            'thana' => $address['thana'] ?? $address['thana_name'] ?? $address['upazila'] ?? 'N/A',
        ];
    }

    private function deliveryAddress(Order $order): array
    {
        $rawAddress = $order->delivery_address;
        $snapshot = is_array($rawAddress)
            ? $rawAddress
            : (is_string($rawAddress) ? (json_decode($rawAddress, true) ?: []) : []);

        if (! $order->delivery_address_id) {
            return $snapshot;
        }

        $stored = CustomerAddress::withoutGlobalScopes()->find($order->delivery_address_id)?->toArray() ?? [];

        return array_replace($stored, array_filter($snapshot, fn ($value) => $value !== null && $value !== ''));
    }

    private function authorizeOrderModule(): void
    {
        abort_unless(Helpers::module_permission_check('order'), 403);
    }
}
