<?php

declare(strict_types=1);

namespace App\Business\Sales;

use App\Business\Sales\Enums\CreditCheckResult;
use App\Models\DberpCustomer;
use App\Models\DberpReceivableRecord;
use App\Support\DberpNumberFormatter;
use App\Support\DberpPriceLevelDictionary;
use Carbon\CarbonInterface;
use Illuminate\Database\Eloquent\Builder;

final class CustomerCreditControlService
{
    public function currentOccupiedAmount(int $customerId): string
    {
        $amount = DberpReceivableRecord::query()
            ->where('customer_id', $customerId)
            ->where('remaining_amount', '>', 0)
            ->whereNot('record_status', 'voided')
            ->sum('remaining_amount');

        return DberpNumberFormatter::money($amount);
    }

    /**
     * @return array<string, mixed>
     */
    public function evaluateCustomer(DberpCustomer $customer, string $pendingAmount = '0.0000', CarbonInterface|string|null $asOfDate = null): array
    {
        $asOf = $asOfDate instanceof CarbonInterface ? $asOfDate->toDateString() : ($asOfDate ?: now()->toDateString());
        $occupied = (float) $this->currentOccupiedAmount((int) $customer->getKey());
        $pending = (float) $pendingAmount;
        $projected = $occupied + $pending;
        $creditLimit = $customer->credit_limit_amount !== null ? (float) $customer->credit_limit_amount : null;

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

        $overdueCount = (int) $overdueQuery->count();
        $overdueAmount = (float) (clone $overdueQuery)->sum('remaining_amount');
        $earliestDueDate = (clone $overdueQuery)->orderBy('due_date')->value('due_date');
        $isOverLimit = $creditLimit !== null && $projected > $creditLimit + 0.00005;
        $hasOverdue = $overdueCount > 0;

        $result = match (true) {
            $isOverLimit && $hasOverdue => CreditCheckResult::BlockedBoth,
            $isOverLimit => CreditCheckResult::BlockedOverLimit,
            $hasOverdue => CreditCheckResult::BlockedOverdue,
            default => CreditCheckResult::Pass,
        };

        return [
            'customer_id' => (int) $customer->getKey(),
            'customer_name' => $customer->name,
            'price_level_code' => $customer->price_level_code,
            'price_level_label' => DberpPriceLevelDictionary::displayLabel($customer->price_level_code),
            'default_payment_term_days' => $customer->default_payment_term_days,
            'credit_limit_amount' => $creditLimit === null ? '' : DberpNumberFormatter::money($creditLimit),
            'occupied_amount' => DberpNumberFormatter::money($occupied),
            'pending_amount' => DberpNumberFormatter::money($pending),
            'projected_amount' => DberpNumberFormatter::money($projected),
            'credit_usage_ratio' => $creditLimit !== null && $creditLimit > 0 ? round($projected / $creditLimit, 4) : null,
            'overdue_count' => $overdueCount,
            'overdue_amount' => DberpNumberFormatter::money($overdueAmount),
            'earliest_due_date' => $earliestDueDate,
            'credit_check_result' => $result->value,
            'credit_check_result_label' => $result->label(),
        ];
    }

    /**
     * @param  array<string, mixed>  $filters
     * @return array{rows: list<array<string, mixed>>, totals: array<string, string|int>}
     */
    public function overview(array $filters = []): array
    {
        $query = DberpCustomer::query()
            ->where('is_enabled', true)
            ->orderBy('sort_order')
            ->orderBy('id');

        if (! empty($filters['customer_id'])) {
            $query->whereKey((int) $filters['customer_id']);
        }

        /** @var list<DberpCustomer> $customers */
        $customers = $query->limit(50)->get()->all();
        $pendingAmount = (string) ($filters['pending_amount'] ?? '0.0000');
        $asOfDate = $filters['as_of_date'] ?? now()->toDateString();

        $rows = array_map(fn (DberpCustomer $customer): array => $this->evaluateCustomer($customer, $pendingAmount, $asOfDate), $customers);

        if (! empty($filters['credit_check_result'])) {
            $rows = array_values(array_filter($rows, fn (array $row): bool => $row['credit_check_result'] === $filters['credit_check_result']));
        }

        if (! empty($filters['attention_status'])) {
            $threshold = (float) config('dberp.sales.reminders.credit_near_limit_ratio', 0.8);

            $rows = array_values(array_filter($rows, function (array $row) use ($filters, $threshold): bool {
                return match ($filters['attention_status']) {
                    'blocked' => $row['credit_check_result'] !== CreditCheckResult::Pass->value,
                    'near_limit' => $row['credit_check_result'] === CreditCheckResult::Pass->value
                        && $row['credit_usage_ratio'] !== null
                        && $row['credit_usage_ratio'] >= $threshold
                        && $row['credit_usage_ratio'] < 1,
                    'overdue' => (int) $row['overdue_count'] > 0,
                    default => true,
                };
            }));
        }

        return [
            'rows' => $rows,
            'totals' => [
                'customer_count' => count($rows),
                'blocked_count' => count(array_filter($rows, fn (array $row): bool => $row['credit_check_result'] !== CreditCheckResult::Pass->value)),
                'overdue_count' => array_sum(array_map(fn (array $row): int => (int) $row['overdue_count'], $rows)),
            ],
        ];
    }

    /**
     * @param  array<string, mixed>  $result
     */
    public function buildBlockedMessage(array $result): string
    {
        return match ($result['credit_check_result']) {
            CreditCheckResult::BlockedBoth->value => __('dberp.errors.credit_blocked_both', [
                'projected_amount' => $result['projected_amount'],
                'credit_limit_amount' => $result['credit_limit_amount'] !== '' ? $result['credit_limit_amount'] : '-',
                'overdue_count' => $result['overdue_count'],
                'earliest_due_date' => $result['earliest_due_date'] ?: '-',
            ]),
            CreditCheckResult::BlockedOverLimit->value => __('dberp.errors.credit_blocked_over_limit', [
                'projected_amount' => $result['projected_amount'],
                'credit_limit_amount' => $result['credit_limit_amount'] !== '' ? $result['credit_limit_amount'] : '-',
            ]),
            CreditCheckResult::BlockedOverdue->value => __('dberp.errors.credit_blocked_overdue', [
                'overdue_count' => $result['overdue_count'],
                'earliest_due_date' => $result['earliest_due_date'] ?: '-',
            ]),
            default => __('dberp.errors.credit_blocked_generic'),
        };
    }
}
