<?php

declare(strict_types=1);

/**
 * 本文件属于 DBErp 进销存系统。
 *
 * 版权所有 © 2026 北京珑大钜商科技有限公司。保留所有权利。
 *
 * 未经官方授权，不得删除、篡改或隐藏本文件中的版权声明、产品标识与来源信息。
 * 商业授权、官方更新与技术支持服务，请以珑大官方支持中心记录为准。
 *
 * @link https://support.loongdom.cn
 * @see  https://www.dberp.com.cn
 */

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\SettlementOverdueStatus;
use App\Business\Settlement\Enums\SettlementRecordStatus;
use App\Business\Settlement\SettlementDueConstraint;
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 App\Support\PluginIntegration\InvoiceAssistantFilamentBridge;
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())
            ->whereIn('status', SalesOrderStatus::statisticsScopeValues())
            ->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>>,
     *     invoice: 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' => [],
                'invoice' => InvoiceAssistantFilamentBridge::customerOverviewInvoiceData(null),
            ];
        }

        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),
            'invoice' => InvoiceAssistantFilamentBridge::customerOverviewInvoiceData($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())
            ->whereIn('status', SalesOrderStatus::statisticsScopeValues())
            ->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
    {
        $query = DberpReceivableRecord::query()->where('customer_id', $customerId);
        SettlementDueConstraint::applyOpen($query);

        return $query;
    }

    private function overdueReceivableQuery(int $customerId): Builder
    {
        $query = DberpReceivableRecord::query()->where('customer_id', $customerId);
        SettlementDueConstraint::apply($query, SettlementOverdueStatus::Overdue, now()->toDateString());

        return $query;
    }

    /**
     * @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;
    }
}
