<?php

declare(strict_types=1);

namespace App\Business\MasterData;

use App\Business\Purchase\Enums\PurchaseInquiryQuoteStatus;
use App\Business\Purchase\Enums\PurchaseOrderStatus;
use App\Business\Settlement\Enums\SettlementDocumentStatus;
use App\Business\Settlement\Enums\SettlementRecordStatus;
use App\Filament\Resources\DberpPaymentOrders\DberpPaymentOrderResource;
use App\Filament\Resources\DberpPurchaseOrders\DberpPurchaseOrderResource;
use App\Filament\Resources\DberpSupplierPayables\DberpSupplierPayableResource;
use App\Filament\Resources\DberpSupplierSettlementQueries\DberpSupplierSettlementQueryResource;
use App\Models\DberpPayableRecord;
use App\Models\DberpPaymentOrder;
use App\Models\DberpPurchaseInquiryQuote;
use App\Models\DberpPurchaseOrder;
use App\Models\DberpSupplier;
use App\Support\DberpNumberFormatter;
use Illuminate\Database\Eloquent\Builder;

/**
 * 供应商主数据编辑页只读业务全景聚合，不修改供应商或结算数据。
 */
final class SupplierBusinessOverviewService
{
    public const int RELATED_LIST_LIMIT = 10;

    public const int RECENT_BUSINESS_DAYS = 90;

    /**
     * @return list<array{label: string, value: string}>
     */
    public function headerSummary(DberpSupplier $supplier): array
    {
        $openPayable = $this->openPayableAmountSum($supplier);
        $overduePayable = $this->overduePayableAmountSum($supplier);
        $quoteSummary = $this->recentInquiryQuoteHeaderSummary($supplier);

        return [
            [
                'label' => (string) __('dberp.fields.supplier_status'),
                'value' => $supplier->is_enabled
                    ? (string) __('dberp.messages.supplier_status_enabled')
                    : (string) __('dberp.messages.supplier_status_disabled'),
            ],
            [
                'label' => (string) __('dberp.fields.open_payable_amount'),
                'value' => $openPayable,
            ],
            [
                'label' => (string) __('dberp.fields.overdue_payable_amount'),
                'value' => $overduePayable,
            ],
            [
                'label' => (string) __('dberp.fields.recent_purchase_amount'),
                'value' => DberpNumberFormatter::money($this->recentPurchaseAmountSum($supplier)),
            ],
            [
                'label' => (string) __('dberp.fields.recent_paid_amount'),
                'value' => DberpNumberFormatter::money($this->recentPaidAmountSum($supplier)),
            ],
            [
                'label' => (string) __('dberp.fields.recent_inquiry_quotes'),
                'value' => $quoteSummary,
            ],
        ];
    }

    /**
     * @return list<array{
     *     code: string,
     *     business_time: string,
     *     due_date: string,
     *     status: string,
     *     amount: string,
     *     url: ?string,
     * }>
     */
    public function recentPurchaseOrderSummaries(DberpSupplier $supplier): array
    {
        if (! DberpPurchaseOrderResource::canViewAny()) {
            return [];
        }

        $canLink = DberpPurchaseOrderResource::canViewAny();

        return DberpPurchaseOrder::query()
            ->where('supplier_id', $supplier->getKey())
            ->whereNot('status', PurchaseOrderStatus::Voided->value)
            ->orderByDesc('business_time')
            ->orderByDesc('id')
            ->limit(self::RELATED_LIST_LIMIT)
            ->get()
            ->map(fn (DberpPurchaseOrder $order): array => [
                'code' => (string) $order->code,
                'business_time' => $order->business_time?->format('Y-m-d H:i') ?? (string) __('dberp.messages.not_available'),
                'due_date' => $order->expected_arrival_date?->toDateString() ?? (string) __('dberp.messages.not_available'),
                'status' => PurchaseOrderStatus::tryFrom((string) $order->status)?->label()
                    ?? (string) __('dberp.messages.not_available'),
                'amount' => DberpNumberFormatter::money($order->total_amount),
                'url' => $canLink
                    ? DberpPurchaseOrderResource::getUrl('edit', ['record' => $order->getKey()])
                    : null,
            ])
            ->values()
            ->all();
    }

    /**
     * @return list<array{
     *     code: string,
     *     business_time: string,
     *     due_date: string,
     *     status: string,
     *     amount: string,
     *     url: ?string,
     * }>
     */
    public function openPayableSummaries(DberpSupplier $supplier): array
    {
        return $this->mapPayableRows(
            $this->openPayableQuery((int) $supplier->getKey())
                ->orderByDesc('due_date')
                ->orderByDesc('id')
                ->limit(self::RELATED_LIST_LIMIT)
                ->get(),
        );
    }

    /**
     * @return list<array{
     *     code: string,
     *     business_time: string,
     *     due_date: string,
     *     status: string,
     *     amount: string,
     *     url: ?string,
     * }>
     */
    public function overduePayableSummaries(DberpSupplier $supplier): array
    {
        return $this->mapPayableRows(
            $this->overduePayableQuery((int) $supplier->getKey())
                ->orderBy('due_date')
                ->orderBy('id')
                ->limit(self::RELATED_LIST_LIMIT)
                ->get(),
        );
    }

    /**
     * @return list<array{
     *     code: string,
     *     business_time: string,
     *     due_date: string,
     *     status: string,
     *     amount: string,
     *     url: ?string,
     * }>
     */
    public function recentPaymentSummaries(DberpSupplier $supplier): array
    {
        if (! DberpPaymentOrderResource::canViewAny()) {
            return [];
        }

        $canLink = DberpPaymentOrderResource::canViewAny();
        $since = now()->subDays(self::RECENT_BUSINESS_DAYS);

        return DberpPaymentOrder::query()
            ->where('supplier_id', $supplier->getKey())
            ->where('status', SettlementDocumentStatus::Effective->value)
            ->where('business_time', '>=', $since)
            ->orderByDesc('business_time')
            ->orderByDesc('id')
            ->limit(self::RELATED_LIST_LIMIT)
            ->get()
            ->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'),
                'due_date' => (string) __('dberp.messages.not_available'),
                'status' => SettlementDocumentStatus::tryFrom((string) $payment->status)?->label()
                    ?? (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,
     *     due_date: string,
     *     status: string,
     *     amount: string,
     *     url: ?string,
     * }>
     */
    public function recentInquiryQuoteSummaries(DberpSupplier $supplier): array
    {
        return DberpPurchaseInquiryQuote::query()
            ->where('supplier_id', $supplier->getKey())
            ->orderByDesc('quote_date')
            ->orderByDesc('id')
            ->limit(self::RELATED_LIST_LIMIT)
            ->get()
            ->map(fn (DberpPurchaseInquiryQuote $quote): array => [
                'code' => (string) $quote->code,
                'business_time' => $quote->quote_date?->toDateString() ?? (string) __('dberp.messages.not_available'),
                'due_date' => $quote->valid_until?->toDateString() ?? (string) __('dberp.messages.not_available'),
                'status' => PurchaseInquiryQuoteStatus::tryFrom((string) $quote->status)?->label()
                    ?? (string) __('dberp.messages.not_available'),
                'amount' => DberpNumberFormatter::price($quote->unit_price),
                'url' => null,
            ])
            ->values()
            ->all();
    }

    public function settlementQueryUrl(DberpSupplier $supplier): ?string
    {
        if (! DberpSupplierSettlementQueryResource::canViewAny()) {
            return null;
        }

        return DberpSupplierSettlementQueryResource::getUrl('index', [
            'tableFilters' => [
                'supplier_id' => ['value' => (string) $supplier->getKey()],
            ],
        ]);
    }

    /**
     * @return array{
     *     header: list<array{label: string, value: string}>,
     *     settlement_query_url: ?string,
     *     purchase_orders: list<array<string, mixed>>,
     *     open_payables: list<array<string, mixed>>,
     *     overdue_payables: list<array<string, mixed>>,
     *     payments: list<array<string, mixed>>,
     *     inquiry_quotes: list<array<string, mixed>>,
     * }
     */
    public function viewDataForSupplier(?DberpSupplier $supplier): array
    {
        if (! $supplier instanceof DberpSupplier) {
            return [
                'header' => [],
                'settlement_query_url' => null,
                'purchase_orders' => [],
                'open_payables' => [],
                'overdue_payables' => [],
                'payments' => [],
                'inquiry_quotes' => [],
            ];
        }

        return [
            'header' => $this->headerSummary($supplier),
            'settlement_query_url' => $this->settlementQueryUrl($supplier),
            'purchase_orders' => $this->recentPurchaseOrderSummaries($supplier),
            'open_payables' => $this->openPayableSummaries($supplier),
            'overdue_payables' => $this->overduePayableSummaries($supplier),
            'payments' => $this->recentPaymentSummaries($supplier),
            'inquiry_quotes' => $this->recentInquiryQuoteSummaries($supplier),
        ];
    }

    public function countOpenPayables(DberpSupplier $supplier): int
    {
        return $this->openPayableQuery((int) $supplier->getKey())->count();
    }

    public function openPayableAmountSum(DberpSupplier $supplier): string
    {
        return DberpNumberFormatter::money(
            $this->openPayableQuery((int) $supplier->getKey())->sum('remaining_amount'),
        );
    }

    public function overduePayableAmountSum(DberpSupplier $supplier): string
    {
        return DberpNumberFormatter::money(
            $this->overduePayableQuery((int) $supplier->getKey())->sum('remaining_amount'),
        );
    }

    private function recentPurchaseAmountSum(DberpSupplier $supplier): float
    {
        $since = now()->subDays(self::RECENT_BUSINESS_DAYS);

        return (float) DberpPurchaseOrder::query()
            ->where('supplier_id', $supplier->getKey())
            ->whereNot('status', PurchaseOrderStatus::Voided->value)
            ->where('business_time', '>=', $since)
            ->sum('total_amount');
    }

    private function recentPaidAmountSum(DberpSupplier $supplier): float
    {
        $since = now()->subDays(self::RECENT_BUSINESS_DAYS);

        return (float) DberpPaymentOrder::query()
            ->where('supplier_id', $supplier->getKey())
            ->where('status', SettlementDocumentStatus::Effective->value)
            ->where('business_time', '>=', $since)
            ->sum('amount');
    }

    private function recentInquiryQuoteHeaderSummary(DberpSupplier $supplier): string
    {
        $count = (int) DberpPurchaseInquiryQuote::query()
            ->where('supplier_id', $supplier->getKey())
            ->where('quote_date', '>=', now()->subDays(self::RECENT_BUSINESS_DAYS)->toDateString())
            ->count();

        if ($count === 0) {
            return (string) __('dberp.messages.partner_overview_no_recent_inquiry_quotes');
        }

        $latest = DberpPurchaseInquiryQuote::query()
            ->where('supplier_id', $supplier->getKey())
            ->orderByDesc('quote_date')
            ->orderByDesc('id')
            ->first();

        if (! $latest instanceof DberpPurchaseInquiryQuote) {
            return (string) __('dberp.messages.partner_overview_inquiry_quote_count', ['count' => $count]);
        }

        return (string) __('dberp.messages.partner_overview_inquiry_quote_summary', [
            'count' => $count,
            'code' => (string) $latest->code,
            'price' => DberpNumberFormatter::price($latest->unit_price),
            'date' => $latest->quote_date?->toDateString() ?? (string) __('dberp.messages.not_available'),
        ]);
    }

    private function openPayableQuery(int $supplierId): Builder
    {
        return DberpPayableRecord::query()
            ->where('supplier_id', $supplierId)
            ->where('remaining_amount', '>', 0)
            ->whereNot('record_status', 'voided');
    }

    private function overduePayableQuery(int $supplierId): Builder
    {
        $today = now()->toDateString();

        return $this->openPayableQuery($supplierId)->where(function (Builder $query) use ($today): void {
            $query->whereDate('due_date', '<', $today)
                ->orWhere(function (Builder $subQuery) use ($today): void {
                    $subQuery->whereNull('due_date')->whereDate('business_date', '<', $today);
                });
        });
    }

    /**
     * @param  iterable<int, DberpPayableRecord>  $records
     * @return list<array{
     *     code: string,
     *     business_time: string,
     *     due_date: string,
     *     status: string,
     *     amount: string,
     *     url: ?string,
     * }>
     */
    private function mapPayableRows(iterable $records): array
    {
        $canLink = DberpSupplierPayableResource::canViewAny();
        $rows = [];

        foreach ($records as $record) {
            if (! $record instanceof DberpPayableRecord) {
                continue;
            }

            $rows[] = [
                'code' => (string) $record->code,
                'business_time' => $record->business_date?->format('Y-m-d H:i') ?? (string) __('dberp.messages.not_available'),
                'due_date' => $record->due_date?->toDateString() ?? (string) __('dberp.messages.not_available'),
                'status' => SettlementRecordStatus::tryFrom((string) $record->record_status)?->label()
                    ?? (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 $rows;
    }
}
