<?php

namespace App\Services\Settlement;

use App\Services\AuditLogger;
use App\Models\SettlementBatch;
use App\Services\LotterySettings;
use Illuminate\Support\Facades\DB;
use App\Lottery\SettlementBatchStatus;

/**
 * draw tick 在自动结算后，按系统设置自动审核并派彩入账。
 */
final class SettlementTickFinalizer
{
    public function __construct(
        private readonly SettlementBatchWorkflowService $workflow,
    ) {}

    /** @return array{approved: int, paid: int, payout_failed: int} */
    public function finalizePendingBatches(): array
    {
        $approved = 0;
        $paid = 0;
        $payoutFailed = 0;

        $autoApprove = (bool) LotterySettings::get('settlement.auto_approve_on_tick', true);
        $pending = $autoApprove
            ? SettlementBatch::query()
                ->where('status', SettlementBatchStatus::PendingReview->value)
                ->orderBy('id')
                ->limit((int) config('lottery.draw_tick_finalize_limit', 5))
                ->get()
            : collect();

        // 除本轮待审核批次外，也恢复处理上轮已批准但尚未派彩的批次。
        // 这样进程即使在 approve 提交后、payout 开始前退出，下一 tick 仍能续跑。
        $approvedDrawIds = DB::table('settlement_batches as approved')
            ->where('approved.status', SettlementBatchStatus::Approved->value)
            ->whereNotExists(function ($query): void {
                $query->selectRaw('1')
                    ->from('settlement_batches as sibling')
                    ->whereColumn('sibling.draw_id', 'approved.draw_id')
                    ->whereIn('sibling.status', [
                        SettlementBatchStatus::Running->value,
                        SettlementBatchStatus::PendingReview->value,
                    ]);
            })
            ->groupBy('approved.draw_id')
            ->orderByRaw('MAX(approved.auto_payout_attempts)')
            ->orderByRaw('MIN(approved.id)')
            ->limit((int) config('lottery.draw_tick_finalize_limit', 5))
            ->pluck('approved.draw_id');
        $candidateDrawIds = $pending->pluck('draw_id')
            ->merge($approvedDrawIds)
            ->map(fn ($id): int => (int) $id)
            ->unique()
            ->values();

        foreach ($pending as $batch) {
            try {
                $this->workflow->approveBySystem($batch, 'auto approve on draw tick');
                $approved++;
            } catch (\Throwable $e) {
                report($e);

                continue;
            }
        }

        if (! (bool) LotterySettings::get('settlement.auto_payout_on_tick', true)) {
            return ['approved' => $approved, 'paid' => 0, 'payout_failed' => 0];
        }

        foreach ($candidateDrawIds as $drawId) {
            $hasUnapprovedBatch = SettlementBatch::query()
                ->where('draw_id', $drawId)
                ->whereIn('status', [
                    SettlementBatchStatus::Running->value,
                    SettlementBatchStatus::PendingReview->value,
                ])
                ->exists();
            if ($hasUnapprovedBatch) {
                continue;
            }

            $approvedBatches = SettlementBatch::query()
                ->where('draw_id', $drawId)
                ->where('status', SettlementBatchStatus::Approved->value)
                ->orderBy('id')
                ->get();

            foreach ($approvedBatches as $batch) {
                try {
                    $this->workflow->payout($batch);
                    $paid++;
                    AuditLogger::recordForSystem(
                        moduleCode: 'settlement',
                        actionCode: 'auto_payout',
                        targetType: 'settlement_batch',
                        targetId: (string) $batch->id,
                        afterJson: ['draw_id' => (int) $batch->draw_id],
                    );
                } catch (\Throwable $e) {
                    report($e);
                    $this->recordAutoPayoutFailure($batch, $e);
                    $payoutFailed++;
                }
            }
        }

        return ['approved' => $approved, 'paid' => $paid, 'payout_failed' => $payoutFailed];
    }

    private function recordAutoPayoutFailure(SettlementBatch $batch, \Throwable $e): void
    {
        $message = mb_substr($e->getMessage(), 0, 200);

        DB::transaction(function () use ($batch, $message): void {
            $locked = SettlementBatch::query()->whereKey($batch->id)->lockForUpdate()->first();
            if ($locked === null || $locked->status !== SettlementBatchStatus::Approved->value) {
                return;
            }

            $locked->forceFill([
                'auto_payout_attempts' => (int) $locked->auto_payout_attempts + 1,
                'review_remark' => 'auto_payout_failed: '.$message,
            ])->save();

            AuditLogger::recordForSystem(
                moduleCode: 'settlement',
                actionCode: 'auto_payout_failed',
                targetType: 'settlement_batch',
                targetId: (string) $locked->id,
                afterJson: [
                    'draw_id' => (int) $locked->draw_id,
                    'error' => $message,
                ],
            );
        });
    }
}
