<?php

namespace App\Services\Draw;

use Carbon\Carbon;
use App\Models\Draw;
use App\Models\TicketOrder;
use App\Lottery\DrawStatus;
use App\Services\LotterySettings;
use Illuminate\Support\Facades\DB;

/**
 * 管理员修改未开奖期号的时间轴（仅 pending / 无注单的 open）。
 */
final class DrawManualUpdateService
{
    public function __construct(
        private readonly DrawTimelineBuilder $timeline,
    ) {}

    /**
     * @param  array{
     *     draw_time: string,
     *     start_time?: string|null,
     *     close_time?: string|null,
     *     draw_no?: string|null,
     *     business_date?: string|null,
     *     sequence_no?: int|null,
     * }  $input
     */
    public function update(Draw $draw, array $input, ?Carbon $now = null): Draw
    {
        $tz = LotterySettings::drawTimezone();
        $nowUtc = ($now ?? Carbon::now())->utc();

        return DB::transaction(function () use ($draw, $input, $tz, $nowUtc): Draw {
            /** @var Draw $locked */
            $locked = Draw::query()->whereKey($draw->id)->lockForUpdate()->firstOrFail();
            $this->assertEditable($locked);

            $drawLocal = Carbon::parse((string) $input['draw_time'], $tz);
            $startLocal = isset($input['start_time']) && $input['start_time'] !== null && $input['start_time'] !== ''
                ? Carbon::parse((string) $input['start_time'], $tz)
                : null;
            $closeLocal = isset($input['close_time']) && $input['close_time'] !== null && $input['close_time'] !== ''
                ? Carbon::parse((string) $input['close_time'], $tz)
                : null;

            if ($startLocal === null || $closeLocal === null) {
                $defaults = $this->timeline->windowsFromDrawLocal($drawLocal);
                $startLocal ??= $defaults['start_local'];
                $closeLocal ??= $defaults['close_local'];
            }

            if (! $startLocal->lt($closeLocal) || ! $closeLocal->lt($drawLocal)) {
                throw new \RuntimeException('draw_timeline_invalid');
            }

            $businessDate = isset($input['business_date']) && $input['business_date'] !== ''
                ? (string) $input['business_date']
                : $drawLocal->format('Y-m-d');

            $sequenceNo = isset($input['sequence_no']) && $input['sequence_no'] !== null
                ? max(1, (int) $input['sequence_no'])
                : (int) $locked->sequence_no;

            $drawNo = isset($input['draw_no']) && trim((string) $input['draw_no']) !== ''
                ? trim((string) $input['draw_no'])
                : (string) $locked->draw_no;

            if (
                Draw::query()
                    ->where('draw_no', $drawNo)
                    ->where('id', '!=', $locked->id)
                    ->exists()
            ) {
                throw new \RuntimeException('draw_no_exists');
            }

            $built = $this->timeline->buildFromLocals($startLocal, $closeLocal, $drawLocal, $nowUtc);

            $locked->forceFill([
                'draw_no' => $drawNo,
                'business_date' => $businessDate,
                'sequence_no' => $sequenceNo,
                'status' => $built['status'],
                'start_time' => $built['start_utc'],
                'close_time' => $built['close_utc'],
                'draw_time' => $built['draw_utc'],
            ])->save();

            return $locked->refresh();
        });
    }

    private function assertEditable(Draw $draw): void
    {
        if ($draw->resultBatches()->exists()) {
            throw new \RuntimeException('draw_result_exists');
        }

        if (! in_array($draw->status, [DrawStatus::Pending->value, DrawStatus::Open->value], true)) {
            throw new \RuntimeException('draw_not_editable');
        }

        if ($draw->status === DrawStatus::Open->value) {
            $betTotal = (int) TicketOrder::query()->where('draw_id', $draw->id)->sum('total_actual_deduct');
            if ($betTotal > 0) {
                throw new \RuntimeException('draw_has_bets');
            }
        }
    }
}
