<?php

declare(strict_types=1);

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

namespace App\Business\Settlement;

use App\Business\Settlement\Enums\SettlementOverdueStatus;
use App\Business\Settlement\Enums\SettlementRecordStatus;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Builder;

/**
 * 应收/应付未结与到期条件。与 SettlementReminderService / 列表 attention_status 同一业务规则。
 */
final class SettlementDueConstraint
{
    public static function dueSoonDays(): int
    {
        return max(1, (int) config('dberp.settlement.reminders.due_soon_days', 7));
    }

    public static function applyOpen(Builder $query): void
    {
        $query
            ->where('remaining_amount', '>', 0)
            ->whereNot('record_status', SettlementRecordStatus::Voided->value);
    }

    public static function apply(Builder $query, SettlementOverdueStatus $status, string $asOfDate): void
    {
        self::applyOpen($query);

        if ($status === SettlementOverdueStatus::Open) {
            return;
        }

        if ($status === SettlementOverdueStatus::Overdue) {
            self::applyOverdue($query, $asOfDate);

            return;
        }

        if ($status === SettlementOverdueStatus::DueToday) {
            self::applyDueToday($query, $asOfDate);

            return;
        }

        self::applyDueSoon($query, $asOfDate);
    }

    public static function applyOverdue(Builder $query, string $asOfDate): void
    {
        $query->where(function (Builder $builder) use ($asOfDate): void {
            $builder
                ->whereDate('due_date', '<', $asOfDate)
                ->orWhere(function (Builder $fallback) use ($asOfDate): void {
                    $fallback->whereNull('due_date')->whereDate('business_date', '<', $asOfDate);
                });
        });
    }

    public static function applyDueToday(Builder $query, string $asOfDate): void
    {
        $query->where(function (Builder $builder) use ($asOfDate): void {
            $builder
                ->whereDate('due_date', $asOfDate)
                ->orWhere(function (Builder $fallback) use ($asOfDate): void {
                    $fallback->whereNull('due_date')->whereDate('business_date', $asOfDate);
                });
        });
    }

    public static function applyDueSoon(Builder $query, string $asOfDate): void
    {
        $until = self::addDays($asOfDate, self::dueSoonDays());

        $query->where(function (Builder $builder) use ($asOfDate, $until): void {
            $builder
                ->whereBetween('due_date', [$asOfDate, $until])
                ->orWhere(function (Builder $fallback) use ($asOfDate, $until): void {
                    $fallback->whereNull('due_date')->whereBetween('business_date', [$asOfDate, $until]);
                });
        });
    }

    public static function isOpen(mixed $remainingAmount, mixed $recordStatus): bool
    {
        return (float) $remainingAmount > 0
            && (string) $recordStatus !== SettlementRecordStatus::Voided->value;
    }

    public static function isOverdue(mixed $dueDate, mixed $businessDate, string $asOfDate): bool
    {
        $due = self::dateString($dueDate) ?? self::dateString($businessDate);

        return $due !== null && $due < $asOfDate;
    }

    public static function overdueStatus(
        mixed $remainingAmount,
        mixed $recordStatus,
        mixed $dueDate,
        mixed $businessDate,
        string $asOfDate,
    ): ?string {
        if (! self::isOpen($remainingAmount, $recordStatus)) {
            return null;
        }

        $due = self::dateString($dueDate) ?? self::dateString($businessDate);

        if ($due === null) {
            return SettlementOverdueStatus::Open->value;
        }

        if ($due < $asOfDate) {
            return SettlementOverdueStatus::Overdue->value;
        }

        if ($due === $asOfDate) {
            return SettlementOverdueStatus::DueToday->value;
        }

        $soonUntil = self::addDays($asOfDate, self::dueSoonDays());
        if ($due <= $soonUntil) {
            return SettlementOverdueStatus::DueSoon->value;
        }

        return SettlementOverdueStatus::Open->value;
    }

    private static function addDays(string $date, int $days): string
    {
        $parsed = \DateTimeImmutable::createFromFormat('!Y-m-d', $date);

        if (! $parsed instanceof \DateTimeImmutable) {
            return $date;
        }

        return $parsed->modify('+'.$days.' days')->format('Y-m-d');
    }

    private static function dateString(mixed $value): ?string
    {
        if ($value === null) {
            return null;
        }

        if ($value instanceof DateTimeInterface) {
            return $value->format('Y-m-d');
        }

        $normalized = trim((string) $value);

        if ($normalized === '') {
            return null;
        }

        return substr($normalized, 0, 10);
    }
}
