<?php

namespace App\Services\Draw;

use Carbon\Carbon;
use App\Models\Draw;
use App\Lottery\DrawStatus;
use App\Models\DrawResultItem;
use App\Models\DrawResultBatch;
use Illuminate\Support\Facades\DB;
use App\Services\LotterySettings;
use App\Lottery\DrawResultSourceType;
use App\Lottery\DrawResultBatchStatus;

/**
 * 按配置执行 RNG，写入 {@see DrawResultBatch} / {@see DrawResultItem}。
 */
final class DrawRngRunner
{
    public function __construct(
        private readonly DrawPublishService $publisher,
    ) {}

    /** 已对单期加锁外层调用时使用 */
    public function executeLocked(Draw $draw): DrawResultBatch
    {
        $draw->forceFill([
            'status' => DrawStatus::Drawing->value,
        ])->save();

        $manualReview = LotterySettings::drawRequireManualReview();
        $seedHex = DrawRngSeedDerivation::generateSeedHex();
        $rngSeedHash = DrawRngSeedDerivation::hashSeedHex($seedHex);
        $rawSeedEncrypted = DrawRngSeedDerivation::encryptSeedHex($seedHex);
        $derivedRows = DrawRngSeedDerivation::deriveAllSlotRows($seedHex, (int) $draw->id);

        $nextVersion = max(1, (int) $draw->current_result_version + 1);

        $batch = DrawResultBatch::query()->create([
            'draw_id' => $draw->id,
            'result_version' => $nextVersion,
            'source_type' => DrawResultSourceType::Rng->value,
            'rng_seed_hash' => $rngSeedHash,
            'raw_seed_encrypted' => $rawSeedEncrypted,
            'status' => $manualReview ? DrawResultBatchStatus::PendingReview->value : DrawResultBatchStatus::Published->value,
            'created_by' => null,
            'confirmed_by' => null,
            'confirmed_at' => $manualReview ? null : now(),
        ]);

        foreach ($derivedRows as $row) {
            DrawResultItem::query()->create([
                'draw_id' => $draw->id,
                'result_batch_id' => $batch->id,
                'prize_type' => $row['prize_type'],
                'prize_index' => $row['prize_index'],
                'number_4d' => $row['number_4d'],
                'suffix_3d' => $row['suffix_3d'],
                'suffix_2d' => $row['suffix_2d'],
                'head_digit' => $row['head_digit'],
                'tail_digit' => $row['tail_digit'],
            ]);
        }

        if ($manualReview) {
            $draw->forceFill([
                'status' => DrawStatus::Review->value,
                'result_source' => DrawResultSourceType::Rng->value,
            ])->save();
        } else {
            $this->publisher->markPublishedInTransaction($draw->fresh(), $batch->fresh());
        }

        return $batch->fresh();
    }

    /**
     * @return array{rung: int, errors: array<int, string>}
     */
    public function runDue(?Carbon $now = null): array
    {
        $nowUtc = ($now ?? Carbon::now())->utc();
        $rung = 0;
        $errors = [];

        $ids = Draw::query()
            ->where('status', DrawStatus::Closed->value)
            ->whereNotNull('draw_time')
            ->where('draw_time', '<=', $nowUtc)
            ->where('settle_version', 0)
            ->where(function ($q): void {
                $q->where('is_reopened', true)
                    ->orWhereDoesntHave('resultBatches');
            })
            ->orderBy('draw_time')
            ->limit((int) config('lottery.draw_tick_rng_limit', 3))
            ->pluck('id');

        foreach ($ids as $drawId) {
            try {
                DB::transaction(function () use ($drawId, &$rung): void {
                    /** @var Draw|null $locked */
                    $locked = Draw::query()->whereKey($drawId)->lockForUpdate()->first();
                    if ($locked === null || $locked->status !== DrawStatus::Closed->value) {
                        return;
                    }
                    if ((int) $locked->settle_version > 0) {
                        return;
                    }
                    if (! (bool) $locked->is_reopened && $locked->resultBatches()->exists()) {
                        return;
                    }
                    $this->executeLocked($locked);
                    $rung++;
                });
            } catch (\Throwable $e) {
                $errors[] = (string) $drawId.': '.$e->getMessage();
            }
        }

        return ['rung' => $rung, 'errors' => $errors];
    }
}
