<?php

declare(strict_types=1);

namespace App\Business\MasterData;

use App\Business\Sales\CustomerCreditControlService;
use App\Business\Sales\Enums\SalesOrderStatus;
use App\Business\Settlement\Enums\SettlementDocumentStatus;
use App\Business\Settlement\Enums\SettlementRecordStatus;
use App\Filament\Resources\DberpCustomerReceivables\DberpCustomerReceivableResource;
use App\Filament\Resources\DberpCustomerSettlementQueries\DberpCustomerSettlementQueryResource;
use App\Filament\Resources\DberpReceiptOrders\DberpReceiptOrderResource;
use App\Filament\Resources\DberpSalesOrders\DberpSalesOrderResource;
use App\Models\DberpCustomer;
use App\Models\DberpReceiptOrder;
use App\Models\DberpReceivableRecord;
use App\Models\DberpSalesOrder;
use App\Support\DberpNumberFormatter;
use Illuminate\Database\Eloquent\Builder;

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

    public const int RECENT_BUSINESS_DAYS = 90;

    public function __construct(
        private readonly CustomerCreditControlService $customerCreditControlService,
    ) {}

    public function loadOverviewRelations(DberpCustomer $customer): DberpCustomer
    {
        return $customer->loadMissing([
            'salesperson.department',
            'salesperson.position',
            'latestSalespersonProfile.employee.department',
            'latestSalespersonProfile.employee.position',
            'latestSalespersonProfile.department',
            'latestSalespersonProfile.position',
        ]);
    }

    /**
     * @return list<array{label: string, value: string}>
     */
    public function headerSummary(DberpCustomer $customer): array
    {
        $customer = $this->loadOverviewRelations($customer);
        $credit = $this->customerCreditControlService->evaluateCustomer($customer, '0.0000');

        return [
            [
                'label' => (string) __('dberp.fields.customer_status'),
                'value' => $customer->is_enabled
                    ? (string) __('dberp.messages.customer_status_enabled')
                    : (string) __('dberp.messages.customer_status_disabled'),
            ],
            [
                'label' => (string) __('dberp.fields.credit_limit_amount'),
                'value' => $credit['credit_limit_amount'] !== ''
                    ? (string) $credit['credit_limit_amount']
                    : (string) __('dberp.messages.not_available'),
            ],
            [
                'label' => (string) __('dberp.fields.credit_usage_amount'),
                'value' => $credit['credit_usage_ratio'] !== null
                    ? (string) __('dberp.messages.customer_credit_usage_ratio', [
                        'percent' => number_format((float) $credit['credit_usage_ratio'] * 100, 2),
                    ])
                    : (string) $credit['occupied_amount'],
            ],
            [
                'label' => (string) __('dberp.fields.open_receivable_amount'),
                'value' => (string) $credit['occupied_amount'],
            ],
            [
                'label' => (string) __('dberp.fields.overdue_receivable_amount'),
                'value' => (string) $credit['overdue_amount'],
            ],
            [
                'label' => (string) __('dberp.fields.recent_sales_amount'),
                'value' => DberpNumberFormatter::money($this->recentSalesAmountSum($customer)),
            ],
            [
                'label' => (string) __('dberp.fields.recent_received_amount'),
                'value' => DberpNumberFormatter::money($this->recentReceivedAmountSum($customer)),
            ],
            [
                'label' => (string) __('dberp.fields.salesperson_employee'),
                'value' => $this->formatSalespersonLabel($customer),
            ],
        ];
    }

    /**
     * @return list<array{
     *     code: string,
     *     business_time: string,
     *     due_date: string,
     *     status: string,
     *     amount: string,
     *     url: ?string,
     * }>
     */
    public function recentSalesOrderSummaries(DberpCustomer $customer): array
    {
        if (! DberpSalesOrderResource::canViewAny()) {
            return [];
        }

        $canLink = DberpSalesOrderResource::canViewAny();

        return DberpSalesOrder::query()
            ->where('customer_id', $customer->getKey())
            ->whereNot('status', SalesOrderStatus::Voided->value)
            ->orderByDesc('business_time')
            ->orderByDesc('id')
            ->limit(self::RELATED_LIST_LIMIT)
            ->get()
            ->map(fn (DberpSalesOrder $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->due_date?->toDateString() ?? (string) __('dberp.messages.not_available'),
                'status' => SalesOrderStatus::tryFrom((string) $order->status)?->label()
                    ?? (string) __('dberp.messages.not_available'),
                'amount' => DberpNumberFormatter::money($order->final_amount),
                'url' => $canLink
                    ? DberpSalesOrderResource::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 openReceivableSummaries(DberpCustomer $customer): array
    {
        return $this->mapReceivableRows(
            $this->openReceivableQuery((int) $customer->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 overdueReceivableSummaries(DberpCustomer $customer): array
    {
        return $this->mapReceivableRows(
            $this->overdueReceivableQuery((int) $customer->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 recentReceiptSummaries(DberpCustomer $customer): array
    {
        if (! DberpReceiptOrderResource::canViewAny()) {
            return [];
        }

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

        return DberpReceiptOrder::query()
            ->where('customer_id', $customer->getKey())
            ->where('status', SettlementDocumentStatus::Effective->value)
            ->where('business_time', '>=', $since)
            ->orderByDesc('business_time')
            ->orderByDesc('id')
            ->limit(self::RELATED_LIST_LIMIT)
            ->get()
            ->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'),
                'due_date' => (string) __('dberp.messages.not_available'),
                'status' => SettlementDocumentStatus::tryFrom((string) $receipt->status)?->label()
                    ?? (string) __('dberp.messages.not_available'),
                'amount' => DberpNumberFormatter::money($receipt->amount),
                'url' => $canLink
                    ? DberpReceiptOrderResource::getUrl('edit', ['record' => $receipt->getKey()])
                    : null,
            ])
            ->values()
            ->all();
    }

    public function settlementQueryUrl(DberpCustomer $customer): ?string
    {
        if (! DberpCustomerSettlementQueryResource::canViewAny()) {
            return null;
        }

        return DberpCustomerSettlementQueryResource::getUrl('index', [
            'tableFilters' => [
                'customer_id' => ['value' => (string) $customer->getKey()],
            ],
        ]);
    }

    /**
     * @return array{
     *     header: list<array{label: string, value: string}>,
     *     settlement_query_url: ?string,
     *     sales_orders: list<array<string, mixed>>,
     *     open_receivables: list<array<string, mixed>>,
     *     overdue_receivables: list<array<string, mixed>>,
     *     receipts: list<array<string, mixed>>,
     * }
     */
    public function viewDataForCustomer(?DberpCustomer $customer): array
    {
        if (! $customer instanceof DberpCustomer) {
            return [
                'header' => [],
                'settlement_query_url' => null,
                'sales_orders' => [],
                'open_receivables' => [],
                'overdue_receivables' => [],
                'receipts' => [],
            ];
        }

        return [
            'header' => $this->headerSummary($customer),
            'settlement_query_url' => $this->settlementQueryUrl($customer),
            'sales_orders' => $this->recentSalesOrderSummaries($customer),
            'open_receivables' => $this->openReceivableSummaries($customer),
            'overdue_receivables' => $this->overdueReceivableSummaries($customer),
            'receipts' => $this->recentReceiptSummaries($customer),
        ];
    }

    public function countOpenReceivables(DberpCustomer $customer): int
    {
        return $this->openReceivableQuery((int) $customer->getKey())->count();
    }

    public function openReceivableAmountSum(DberpCustomer $customer): string
    {
        return DberpNumberFormatter::money(
            $this->openReceivableQuery((int) $customer->getKey())->sum('remaining_amount'),
        );
    }

    private function recentSalesAmountSum(DberpCustomer $customer): float
    {
        $since = now()->subDays(self::RECENT_BUSINESS_DAYS);

        return (float) DberpSalesOrder::query()
            ->where('customer_id', $customer->getKey())
            ->whereNot('status', SalesOrderStatus::Voided->value)
            ->where('business_time', '>=', $since)
            ->sum('final_amount');
    }

    private function recentReceivedAmountSum(DberpCustomer $customer): float
    {
        $since = now()->subDays(self::RECENT_BUSINESS_DAYS);

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

    private function formatSalespersonLabel(DberpCustomer $customer): string
    {
        $profile = $customer->latestSalespersonProfile;
        if ($profile !== null) {
            return $profile->labelForSelectWithOrganization();
        }

        if ($customer->salesperson !== null) {
            return $customer->salesperson->labelForSelectWithOrganization();
        }

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

    private function openReceivableQuery(int $customerId): Builder
    {
        return DberpReceivableRecord::query()
            ->where('customer_id', $customerId)
            ->where('remaining_amount', '>', 0)
            ->whereNot('record_status', 'voided');
    }

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

        return $this->openReceivableQuery($customerId)->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, DberpReceivableRecord>  $records
     * @return list<array{
     *     code: string,
     *     business_time: string,
     *     due_date: string,
     *     status: string,
     *     amount: string,
     *     url: ?string,
     * }>
     */
    private function mapReceivableRows(iterable $records): array
    {
        $canLink = DberpCustomerReceivableResource::canViewAny();
        $rows = [];

        foreach ($records as $record) {
            if (! $record instanceof DberpReceivableRecord) {
                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
                    ? DberpCustomerReceivableResource::getUrl('index', [
                        'tableFilters' => [
                            'customer_id' => ['value' => (string) $record->customer_id],
                        ],
                    ])
                    : null,
            ];
        }

        return $rows;
    }
}
