<?php

declare(strict_types=1);

namespace App\Business\Purchase;

use App\Business\Inventory\Enums\InventoryDocumentStatus;
use App\Business\Purchase\Enums\PurchaseExceptionStatus;
use App\Business\Purchase\Enums\PurchaseOrderStatus;
use App\Business\Settlement\Enums\PayableStatus;
use App\Business\Settlement\Enums\SettlementDocumentStatus;
use App\Business\Settlement\Enums\SettlementRecordStatus;
use App\Filament\Resources\DberpPaymentOrders\DberpPaymentOrderResource;
use App\Filament\Resources\DberpPurchaseExceptions\DberpPurchaseExceptionResource;
use App\Filament\Resources\DberpPurchaseReceipts\DberpPurchaseReceiptResource;
use App\Filament\Resources\DberpPurchaseReturns\DberpPurchaseReturnResource;
use App\Filament\Resources\DberpSupplierPayables\DberpSupplierPayableResource;
use App\Models\DberpPayableRecord;
use App\Models\DberpPaymentOrder;
use App\Models\DberpPurchaseException;
use App\Models\DberpPurchaseOrder;
use App\Models\DberpPurchaseOrderItem;
use App\Models\DberpPurchaseReceiptOrder;
use App\Models\DberpPurchaseReturnOrder;
use App\Support\DberpNumberFormatter;

/**
 * 采购订单编辑页只读信息聚合：页头摘要、关联单据摘要，不修改业务数据。
 */
final class PurchaseOrderOverviewService
{
    public const int RELATED_DOCUMENT_LIMIT = 20;

    public function loadOverviewRelations(DberpPurchaseOrder $order): DberpPurchaseOrder
    {
        return $order->loadMissing([
            'supplier',
            'receipts' => fn ($query) => $query
                ->orderByDesc('business_time')
                ->orderByDesc('id')
                ->limit(self::RELATED_DOCUMENT_LIMIT),
            'returns' => fn ($query) => $query
                ->orderByDesc('business_time')
                ->orderByDesc('id')
                ->limit(self::RELATED_DOCUMENT_LIMIT),
            'payments' => fn ($query) => $query
                ->orderByDesc('business_time')
                ->orderByDesc('id')
                ->limit(self::RELATED_DOCUMENT_LIMIT),
            'purchaseExceptions' => fn ($query) => $query
                ->orderByDesc('occurred_at')
                ->orderByDesc('id')
                ->limit(self::RELATED_DOCUMENT_LIMIT),
            'payableRecord',
        ]);
    }

    /**
     * @return list<array{label: string, value: string}>
     */
    public function headerSummary(DberpPurchaseOrder $order): array
    {
        $order = $this->loadOverviewRelations($order);

        $statusLabel = PurchaseOrderStatus::tryFrom((string) $order->status)?->label()
            ?? (string) __('dberp.messages.not_available');

        $payablePending = $this->shouldShowPayableAsPending($order);

        return [
            [
                'label' => (string) __('dberp.fields.status'),
                'value' => $statusLabel,
            ],
            [
                'label' => (string) __('dberp.fields.supplier'),
                'value' => (string) ($order->supplier?->name ?? __('dberp.messages.not_available')),
            ],
            [
                'label' => (string) __('dberp.fields.total_amount'),
                'value' => DberpNumberFormatter::money($order->total_amount),
            ],
            [
                'label' => (string) __('dberp.fields.arrival_progress_status'),
                'value' => $this->formatArrivalProgressSummary($order),
            ],
            [
                'label' => (string) __('dberp.fields.payable_amount'),
                'value' => $payablePending
                    ? (string) __('dberp.hints.purchase_order_overview_payable_pending')
                    : $this->formatMoneyOrDash($order->payable_amount),
            ],
            [
                'label' => (string) __('dberp.fields.paid_amount'),
                'value' => $payablePending
                    ? (string) __('dberp.hints.purchase_order_overview_payable_pending')
                    : $this->formatMoneyOrDash($order->paid_amount),
            ],
            [
                'label' => (string) __('dberp.fields.unpaid_amount'),
                'value' => $payablePending
                    ? (string) __('dberp.hints.purchase_order_overview_payable_pending')
                    : $this->formatMoneyOrDash($order->unpaid_amount),
            ],
            [
                'label' => (string) __('dberp.fields.payable_status'),
                'value' => $payablePending
                    ? (string) __('dberp.hints.purchase_order_overview_payable_pending')
                    : $this->formatPayableStatus($order->payable_status),
            ],
        ];
    }

    /**
     * 与采购明细保存逻辑一致：剩余待到货 = max(订购数量 − 已入库数量, 0)。
     */
    public function displayRemainingArrivalQuantity(DberpPurchaseOrderItem $item): string
    {
        if ($item->remaining_arrival_quantity !== null && $item->remaining_arrival_quantity !== '') {
            return (string) $item->remaining_arrival_quantity;
        }

        $quantity = (float) ($item->quantity ?? 0);
        $receivedQuantity = (float) ($item->received_quantity ?? 0);
        $remaining = max($quantity - $receivedQuantity, 0);

        return number_format($remaining, 4, '.', '');
    }

    /**
     * @return list<array{
     *     code: string,
     *     business_time: string,
     *     status: string,
     *     quantity: string,
     *     amount: string,
     *     url: ?string,
     * }>
     */
    public function relatedReceiptSummaries(DberpPurchaseOrder $order): array
    {
        $order = $this->loadOverviewRelations($order);
        $canLink = DberpPurchaseReceiptResource::canViewAny();

        return $order->receipts
            ->map(fn (DberpPurchaseReceiptOrder $receipt): array => $this->mapInventoryDocumentRow(
                code: (string) $receipt->code,
                businessTime: $receipt->business_time,
                status: (string) $receipt->status,
                quantity: DberpNumberFormatter::quantity($receipt->total_received_quantity ?? $receipt->total_arrival_quantity),
                amount: DberpNumberFormatter::money($receipt->total_amount),
                editUrl: $canLink
                    ? DberpPurchaseReceiptResource::getUrl('edit', ['record' => $receipt->getKey()])
                    : null,
            ))
            ->values()
            ->all();
    }

    /**
     * @return list<array{
     *     code: string,
     *     business_time: string,
     *     status: string,
     *     quantity: string,
     *     amount: string,
     *     url: ?string,
     * }>
     */
    public function relatedReturnSummaries(DberpPurchaseOrder $order): array
    {
        $order = $this->loadOverviewRelations($order);
        $canLink = DberpPurchaseReturnResource::canViewAny();

        return $order->returns
            ->map(fn (DberpPurchaseReturnOrder $return): array => $this->mapInventoryDocumentRow(
                code: (string) $return->code,
                businessTime: $return->business_time,
                status: (string) $return->status,
                quantity: DberpNumberFormatter::quantity($return->total_return_quantity),
                amount: DberpNumberFormatter::money($return->total_amount),
                editUrl: $canLink
                    ? DberpPurchaseReturnResource::getUrl('edit', ['record' => $return->getKey()])
                    : null,
            ))
            ->values()
            ->all();
    }

    /**
     * @return list<array{
     *     code: string,
     *     business_time: string,
     *     status: string,
     *     quantity: string,
     *     amount: string,
     *     url: ?string,
     * }>
     */
    public function relatedPaymentSummaries(DberpPurchaseOrder $order): array
    {
        $order = $this->loadOverviewRelations($order);
        $canLink = DberpPaymentOrderResource::canViewAny();

        return $order->payments
            ->map(fn (DberpPaymentOrder $payment): array => [
                'code' => (string) $payment->code,
                'business_time' => $payment->business_time?->format('Y-m-d H:i') ?? (string) __('dberp.messages.not_available'),
                'status' => SettlementDocumentStatus::tryFrom((string) $payment->status)?->label()
                    ?? (string) __('dberp.messages.not_available'),
                'quantity' => (string) __('dberp.messages.not_available'),
                'amount' => DberpNumberFormatter::money($payment->amount),
                'url' => $canLink
                    ? DberpPaymentOrderResource::getUrl('edit', ['record' => $payment->getKey()])
                    : null,
            ])
            ->values()
            ->all();
    }

    /**
     * @return list<array{
     *     code: string,
     *     business_time: string,
     *     status: string,
     *     quantity: string,
     *     amount: string,
     *     url: ?string,
     * }>
     */
    public function relatedPayableSummaries(DberpPurchaseOrder $order): array
    {
        $order = $this->loadOverviewRelations($order);
        $record = $order->payableRecord;

        if (! $record instanceof DberpPayableRecord) {
            return [];
        }

        $canLink = DberpSupplierPayableResource::canViewAny();

        return [[
            'code' => (string) $record->code,
            'business_time' => $record->business_date?->format('Y-m-d H:i') ?? (string) __('dberp.messages.not_available'),
            'status' => $record->record_status
                ? (SettlementRecordStatus::tryFrom((string) $record->record_status)?->label()
                    ?? (string) __('dberp.messages.not_available'))
                : (string) __('dberp.messages.not_available'),
            'quantity' => (string) __('dberp.messages.not_available'),
            'amount' => DberpNumberFormatter::money($record->remaining_amount),
            'url' => $canLink
                ? DberpSupplierPayableResource::getUrl('index', [
                    'tableFilters' => [
                        'supplier_id' => ['value' => (string) $record->supplier_id],
                    ],
                ])
                : null,
        ]];
    }

    /**
     * @return list<array{
     *     code: string,
     *     business_time: string,
     *     status: string,
     *     quantity: string,
     *     amount: string,
     *     url: ?string,
     * }>
     */
    public function relatedExceptionSummaries(DberpPurchaseOrder $order): array
    {
        $order = $this->loadOverviewRelations($order);
        $canLink = DberpPurchaseExceptionResource::canViewAny();

        return $order->purchaseExceptions
            ->map(fn (DberpPurchaseException $exception): array => [
                'code' => (string) $exception->code,
                'business_time' => $exception->occurred_at?->format('Y-m-d H:i') ?? (string) __('dberp.messages.not_available'),
                'status' => PurchaseExceptionStatus::tryFrom((string) $exception->status)?->label()
                    ?? (string) __('dberp.messages.not_available'),
                'quantity' => DberpNumberFormatter::quantity($exception->pending_quantity ?? $exception->difference_quantity),
                'amount' => (string) __('dberp.messages.not_available'),
                'url' => $canLink
                    ? DberpPurchaseExceptionResource::getUrl('edit', ['record' => $exception->getKey()])
                    : null,
            ])
            ->values()
            ->all();
    }

    /**
     * @return array{
     *     header: list<array{label: string, value: string}>,
     *     receipts: list<array<string, mixed>>,
     *     returns: list<array<string, mixed>>,
     *     payments: list<array<string, mixed>>,
     *     payables: list<array<string, mixed>>,
     *     exceptions: list<array<string, mixed>>,
     * }
     */
    public function viewDataForOrder(?DberpPurchaseOrder $order): array
    {
        if (! $order instanceof DberpPurchaseOrder) {
            return [
                'header' => [],
                'receipts' => [],
                'returns' => [],
                'payments' => [],
                'payables' => [],
                'exceptions' => [],
            ];
        }

        return [
            'header' => $this->headerSummary($order),
            'receipts' => $this->relatedReceiptSummaries($order),
            'returns' => $this->relatedReturnSummaries($order),
            'payments' => $this->relatedPaymentSummaries($order),
            'payables' => $this->relatedPayableSummaries($order),
            'exceptions' => $this->relatedExceptionSummaries($order),
        ];
    }

    public function countRelatedReceipts(DberpPurchaseOrder $order): int
    {
        return $order->receipts()->count();
    }

    private function shouldShowPayableAsPending(DberpPurchaseOrder $order): bool
    {
        if (in_array($order->status, [
            PurchaseOrderStatus::Draft->value,
            PurchaseOrderStatus::Voided->value,
        ], true)) {
            return true;
        }

        if ($order->payable_amount === null && $order->paid_amount === null && $order->unpaid_amount === null) {
            return true;
        }

        return false;
    }

    private function formatArrivalProgressSummary(DberpPurchaseOrder $order): string
    {
        if (in_array($order->status, [
            PurchaseOrderStatus::Draft->value,
            PurchaseOrderStatus::Voided->value,
        ], true)) {
            return (string) __('dberp.hints.purchase_order_overview_arrival_pending');
        }

        $progressLabel = PurchaseOrderStatus::tryFrom((string) $order->arrival_progress_status)?->label()
            ?? PurchaseOrderStatus::tryFrom((string) $order->status)?->label()
            ?? (string) __('dberp.messages.not_available');

        $receivedTotal = $order->items()->sum('received_quantity');

        return (string) __('dberp.messages.purchase_order_overview_arrival_progress', [
            'progress' => $progressLabel,
            'arrived' => DberpNumberFormatter::quantity($order->total_arrived_quantity),
            'received' => DberpNumberFormatter::quantity($receivedTotal),
            'pending' => DberpNumberFormatter::quantity($order->remaining_arrival_quantity),
        ]);
    }

    private function formatMoneyOrDash(mixed $amount): string
    {
        if ($amount === null || $amount === '') {
            return (string) __('dberp.messages.not_available');
        }

        return DberpNumberFormatter::money($amount);
    }

    private function formatPayableStatus(?string $status): string
    {
        if ($status === null || $status === '') {
            return (string) __('dberp.messages.not_available');
        }

        $label = PayableStatus::tryFrom($status)?->label();

        return $label ?? (string) __('dberp.messages.not_available');
    }

    /**
     * @return array{
     *     code: string,
     *     business_time: string,
     *     status: string,
     *     quantity: string,
     *     amount: string,
     *     url: ?string,
     * }
     */
    private function mapInventoryDocumentRow(
        string $code,
        mixed $businessTime,
        string $status,
        string $quantity,
        string $amount,
        ?string $editUrl,
    ): array {
        return [
            'code' => $code,
            'business_time' => $businessTime?->format('Y-m-d H:i') ?? (string) __('dberp.messages.not_available'),
            'status' => InventoryDocumentStatus::tryFrom($status)?->label()
                ?? (string) __('dberp.messages.not_available'),
            'quantity' => $quantity,
            'amount' => $amount,
            'url' => $editUrl,
        ];
    }
}
