<?php

declare(strict_types=1);

namespace App\Business\Sales;

use App\Business\Inventory\Enums\InventoryDocumentStatus;
use App\Business\Sales\Enums\SalesOrderStatus;
use App\Business\Settlement\Enums\ReceivableStatus;
use App\Business\Settlement\Enums\SettlementDocumentStatus;
use App\Business\Settlement\Enums\SettlementRecordStatus;
use App\Filament\Resources\DberpCustomerReceivables\DberpCustomerReceivableResource;
use App\Filament\Resources\DberpReceiptOrders\DberpReceiptOrderResource;
use App\Filament\Resources\DberpSalesOutbounds\DberpSalesOutboundResource;
use App\Filament\Resources\DberpSalesReturns\DberpSalesReturnResource;
use App\Models\DberpReceiptOrder;
use App\Models\DberpReceivableRecord;
use App\Models\DberpSalesOrder;
use App\Models\DberpSalesOrderItem;
use App\Models\DberpSalesOutboundOrder;
use App\Models\DberpSalesReturnOrder;
use App\Support\DberpNumberFormatter;

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

    public function loadOverviewRelations(DberpSalesOrder $order): DberpSalesOrder
    {
        return $order->loadMissing([
            'customer',
            'outbounds' => 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),
            'receipts' => fn ($query) => $query
                ->orderByDesc('business_time')
                ->orderByDesc('id')
                ->limit(self::RELATED_DOCUMENT_LIMIT),
            'receivableRecord',
        ]);
    }

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

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

        $receivablePending = $this->shouldShowReceivableAsPending($order);

        return [
            [
                'label' => (string) __('dberp.fields.status'),
                'value' => $statusLabel,
            ],
            [
                'label' => (string) __('dberp.fields.customer'),
                'value' => (string) ($order->customer?->name ?? __('dberp.messages.not_available')),
            ],
            [
                'label' => (string) __('dberp.fields.final_amount'),
                'value' => DberpNumberFormatter::money($order->final_amount),
            ],
            [
                'label' => (string) __('dberp.fields.due_date'),
                'value' => $order->due_date?->toDateString() ?? (string) __('dberp.messages.not_available'),
            ],
            [
                'label' => (string) __('dberp.fields.receivable_amount'),
                'value' => $receivablePending
                    ? (string) __('dberp.hints.sales_order_overview_receivable_pending')
                    : $this->formatMoneyOrDash($order->receivable_amount),
            ],
            [
                'label' => (string) __('dberp.fields.received_amount'),
                'value' => $receivablePending
                    ? (string) __('dberp.hints.sales_order_overview_receivable_pending')
                    : $this->formatMoneyOrDash($order->received_amount),
            ],
            [
                'label' => (string) __('dberp.fields.unreceived_amount'),
                'value' => $receivablePending
                    ? (string) __('dberp.hints.sales_order_overview_receivable_pending')
                    : $this->formatMoneyOrDash($order->unreceived_amount),
            ],
            [
                'label' => (string) __('dberp.fields.receivable_status'),
                'value' => $receivablePending
                    ? (string) __('dberp.hints.sales_order_overview_receivable_pending')
                    : $this->formatReceivableStatus($order->receivable_status),
            ],
        ];
    }

    public function pendingShipQuantity(DberpSalesOrderItem $item): string
    {
        return bcsub(
            (string) ($item->quantity ?? '0'),
            (string) ($item->shipped_quantity ?? '0'),
            4,
        );
    }

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

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

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

        return $order->returns
            ->map(fn (DberpSalesReturnOrder $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
                    ? DberpSalesReturnResource::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 relatedReceiptSummaries(DberpSalesOrder $order): array
    {
        $order = $this->loadOverviewRelations($order);
        $canLink = DberpReceiptOrderResource::canViewAny();

        return $order->receipts
            ->map(fn (DberpReceiptOrder $receipt): array => [
                'code' => (string) $receipt->code,
                'business_time' => $receipt->business_time?->format('Y-m-d H:i') ?? (string) __('dberp.messages.not_available'),
                'status' => SettlementDocumentStatus::tryFrom((string) $receipt->status)?->label()
                    ?? (string) __('dberp.messages.not_available'),
                'quantity' => (string) __('dberp.messages.not_available'),
                'amount' => DberpNumberFormatter::money($receipt->amount),
                'url' => $canLink
                    ? DberpReceiptOrderResource::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 relatedReceivableSummaries(DberpSalesOrder $order): array
    {
        $order = $this->loadOverviewRelations($order);
        $record = $order->receivableRecord;

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

        $canLink = DberpCustomerReceivableResource::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
                ? DberpCustomerReceivableResource::getUrl('index', [
                    'tableFilters' => [
                        'customer_id' => ['value' => (string) $record->customer_id],
                    ],
                ])
                : null,
        ]];
    }

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

        return [
            'header' => $this->headerSummary($order),
            'outbounds' => $this->relatedOutboundSummaries($order),
            'returns' => $this->relatedReturnSummaries($order),
            'receipts' => $this->relatedReceiptSummaries($order),
            'receivables' => $this->relatedReceivableSummaries($order),
        ];
    }

    public function countRelatedOutbounds(DberpSalesOrder $order): int
    {
        return $order->outbounds()->count();
    }

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

        if ($order->receivable_amount === null && $order->received_amount === null && $order->unreceived_amount === null) {
            return true;
        }

        return false;
    }

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

        return DberpNumberFormatter::money($amount);
    }

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

        $label = ReceivableStatus::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,
        ];
    }
}
