<?php

declare(strict_types=1);

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

namespace App\Business\Sales;

use App\Business\Sales\Enums\CreditCheckResult;
use App\Business\Sales\Enums\CustomerPriceRuleStatus;
use App\Business\Sales\Enums\SalesQuoteStatus;
use App\Models\DberpCustomer;
use App\Models\DberpCustomerPriceRule;
use App\Models\DberpReceivableRecord;
use App\Models\DberpSalesOrder;
use App\Models\DberpSalesQuote;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;

final class SalesControlReminderService
{
    /**
     * @var array<string, int>|null
     */
    private ?array $cachedOverviewMetrics = null;

    /**
     * @return array<string, int>
     */
    public function overviewMetrics(): array
    {
        if ($this->cachedOverviewMetrics !== null) {
            return $this->cachedOverviewMetrics;
        }

        $cacheSeconds = max(0, (int) config('dberp.workbench.metrics_cache_seconds', 120));

        if ($cacheSeconds > 0) {
            /** @var array<string, int> $metrics */
            $metrics = Cache::remember(
                'dberp.workbench.sales_control_metrics',
                now()->addSeconds($cacheSeconds),
                fn (): array => $this->computeOverviewMetrics(),
            );

            return $this->cachedOverviewMetrics = $metrics;
        }

        return $this->cachedOverviewMetrics = $this->computeOverviewMetrics();
    }

    /**
     * @return array<string, int>
     */
    private function computeOverviewMetrics(): array
    {
        return [
            'pending_effective_quote_count' => $this->pendingEffectiveQuoteQuery()->count(),
            'expiring_quote_count' => $this->expiringQuoteQuery()->count(),
            'expired_quote_count' => $this->expiredQuoteQuery()->count(),
            'long_unconverted_quote_count' => $this->longUnconvertedQuoteQuery()->count(),
            'blocked_credit_customer_count' => $this->creditRiskRows()->filter(fn (array $row): bool => $row['is_blocked'])->count(),
            'near_limit_customer_count' => $this->creditRiskRows()->filter(fn (array $row): bool => $row['is_near_limit'])->count(),
            'released_override_order_count' => $this->releasedOverrideOrderQuery()->count(),
            'expiring_price_rule_count' => $this->expiringPriceRuleQuery()->count(),
            'expired_price_rule_count' => $this->expiredPriceRuleQuery()->count(),
        ];
    }

    /**
     * @return list<array<string, string|int>>
     */
    public function reminderItems(): array
    {
        $metrics = $this->overviewMetrics();

        return [
            [
                'key' => 'pending_effective_quote',
                'title' => (string) __('dberp.widgets.sales_workbench.reminder_pending_effective_quote'),
                'count' => $metrics['pending_effective_quote_count'],
                'amount' => '',
            ],
            [
                'key' => 'expiring_quote',
                'title' => (string) __('dberp.widgets.sales_workbench.reminder_expiring_quote'),
                'count' => $metrics['expiring_quote_count'],
                'amount' => '',
            ],
            [
                'key' => 'expired_quote',
                'title' => (string) __('dberp.widgets.sales_workbench.reminder_expired_quote'),
                'count' => $metrics['expired_quote_count'],
                'amount' => '',
            ],
            [
                'key' => 'long_unconverted_quote',
                'title' => (string) __('dberp.widgets.sales_workbench.reminder_long_unconverted_quote'),
                'count' => $metrics['long_unconverted_quote_count'],
                'amount' => '',
            ],
            [
                'key' => 'blocked_credit_customer',
                'title' => (string) __('dberp.widgets.sales_workbench.reminder_blocked_credit_customer'),
                'count' => $metrics['blocked_credit_customer_count'],
                'amount' => '',
            ],
            [
                'key' => 'near_limit_customer',
                'title' => (string) __('dberp.widgets.sales_workbench.reminder_near_limit_customer'),
                'count' => $metrics['near_limit_customer_count'],
                'amount' => '',
            ],
            [
                'key' => 'released_override_order',
                'title' => (string) __('dberp.widgets.sales_workbench.reminder_released_override_order'),
                'count' => $metrics['released_override_order_count'],
                'amount' => '',
            ],
            [
                'key' => 'expiring_price_rule',
                'title' => (string) __('dberp.widgets.sales_workbench.reminder_expiring_price_rule'),
                'count' => $metrics['expiring_price_rule_count'],
                'amount' => '',
            ],
            [
                'key' => 'expired_price_rule',
                'title' => (string) __('dberp.widgets.sales_workbench.reminder_expired_price_rule'),
                'count' => $metrics['expired_price_rule_count'],
                'amount' => '',
            ],
        ];
    }

    private function pendingEffectiveQuoteQuery()
    {
        return $this->governedQuoteQuery()
            ->whereDate('valid_from', '>', now()->toDateString());
    }

    private function expiringQuoteQuery()
    {
        $today = now()->toDateString();
        $until = now()->addDays($this->quoteExpirySoonDays())->toDateString();

        return $this->effectiveUnconvertedQuoteQuery()
            ->whereBetween('valid_until', [$today, $until]);
    }

    private function expiredQuoteQuery()
    {
        return $this->governedQuoteQuery()
            ->whereDate('valid_until', '<', now()->toDateString());
    }

    private function longUnconvertedQuoteQuery()
    {
        return $this->effectiveUnconvertedQuoteQuery()
            ->whereDate('quote_date', '<=', now()->subDays($this->longUnconvertedDays())->toDateString());
    }

    private function governedQuoteQuery()
    {
        return DberpSalesQuote::query()
            ->whereNotIn('status', [
                SalesQuoteStatus::Draft->value,
                SalesQuoteStatus::Converted->value,
                SalesQuoteStatus::Voided->value,
            ]);
    }

    private function effectiveUnconvertedQuoteQuery()
    {
        $today = now()->toDateString();

        return $this->governedQuoteQuery()
            ->whereNull('converted_sales_order_id')
            ->whereDate('valid_from', '<=', $today)
            ->whereDate('valid_until', '>=', $today);
    }

    private function releasedOverrideOrderQuery()
    {
        return DberpSalesOrder::query()
            ->where('credit_check_result', CreditCheckResult::ReleasedByOverride->value);
    }

    private function expiringPriceRuleQuery()
    {
        $today = now()->toDateString();
        $until = now()->addDays($this->priceRuleExpirySoonDays())->toDateString();

        return DberpCustomerPriceRule::query()
            ->where('status', CustomerPriceRuleStatus::Active->value)
            ->whereBetween('valid_until', [$today, $until]);
    }

    private function expiredPriceRuleQuery()
    {
        return DberpCustomerPriceRule::query()
            ->where('status', CustomerPriceRuleStatus::Active->value)
            ->whereDate('valid_until', '<', now()->toDateString());
    }

    /**
     * @return Collection<int, array<string, mixed>>
     */
    private function creditRiskRows(): Collection
    {
        $today = now()->toDateString();
        $threshold = $this->nearLimitRatio();

        return DberpCustomer::query()
            ->where('is_enabled', true)
            ->orderBy('sort_order')
            ->orderBy('id')
            ->get(['id', 'name', 'credit_limit_amount'])
            ->map(function (DberpCustomer $customer) use ($today, $threshold): array {
                $occupiedAmount = (float) DberpReceivableRecord::query()
                    ->where('customer_id', $customer->getKey())
                    ->where('remaining_amount', '>', 0)
                    ->whereNot('record_status', 'voided')
                    ->sum('remaining_amount');

                $overdueCount = (int) DberpReceivableRecord::query()
                    ->where('customer_id', $customer->getKey())
                    ->where('remaining_amount', '>', 0)
                    ->whereNot('record_status', 'voided')
                    ->where(function ($query) use ($today): void {
                        $query->whereDate('due_date', '<', $today)
                            ->orWhere(function ($subQuery) use ($today): void {
                                $subQuery->whereNull('due_date')->whereDate('business_date', '<', $today);
                            });
                    })
                    ->count();

                $creditLimit = $customer->credit_limit_amount === null ? null : (float) $customer->credit_limit_amount;
                $usageRatio = ($creditLimit !== null && $creditLimit > 0.0) ? ($occupiedAmount / $creditLimit) : 0.0;

                return [
                    'customer_id' => (int) $customer->getKey(),
                    'credit_limit_amount' => $creditLimit,
                    'occupied_amount' => $occupiedAmount,
                    'overdue_count' => $overdueCount,
                    'is_blocked' => $overdueCount > 0 || ($creditLimit !== null && $occupiedAmount > $creditLimit + 0.00005),
                    'is_near_limit' => $overdueCount === 0
                        && $creditLimit !== null
                        && $creditLimit > 0.0
                        && $usageRatio >= $threshold
                        && $usageRatio < 1,
                ];
            });
    }

    private function quoteExpirySoonDays(): int
    {
        return (int) config('dberp.sales.reminders.quote_expiry_soon_days', 7);
    }

    private function longUnconvertedDays(): int
    {
        return (int) config('dberp.sales.reminders.long_unconverted_days', 15);
    }

    private function priceRuleExpirySoonDays(): int
    {
        return (int) config('dberp.sales.reminders.price_rule_expiry_soon_days', 7);
    }

    private function nearLimitRatio(): float
    {
        return (float) config('dberp.sales.reminders.credit_near_limit_ratio', 0.8);
    }
}
