<?php

namespace App\Console\Commands;

use App\Models\Draw;
use App\Models\Player;
use App\Models\TicketItem;
use App\Models\TicketOrder;
use App\Models\PlayerWallet;
use App\Models\DrawResultItem;
use App\Models\DrawResultBatch;
use App\Lottery\DrawStatus;
use Illuminate\Console\Command;
use App\Services\Draw\DrawPrizeLayout;
use Illuminate\Support\Facades\DB;
use App\Lottery\DrawResultBatchStatus;
use App\Services\Settlement\SettlementOrchestrator;

/**
 * PRD §17.2：单期万级注单结算耗时验收（仅结算编排，不含派彩入账）。
 */
final class LotteryPerfSettlementBenchmarkCommand extends Command
{
    protected $signature = 'lottery:perf-settlement-benchmark
                            {--items=10000 : 待结算 ticket_items 数量}
                            {--max-seconds=30 : 通过阈值（秒）}';

    protected $description = 'Benchmark SettlementOrchestrator for N pending_draw items (§17.2)';

    public function handle(SettlementOrchestrator $orchestrator): int
    {
        if (ini_get('memory_limit') !== '-1' && $this->byteMemoryLimit(ini_get('memory_limit')) < 512 * 1024 * 1024) {
            ini_set('memory_limit', '512M');
        }

        return $this->runBenchmark($orchestrator);
    }

    private function runBenchmark(SettlementOrchestrator $orchestrator): int
    {
        $itemCount = max(1, (int) $this->option('items'));
        $maxSeconds = max(1, (int) $this->option('max-seconds'));
        $maxMs = $maxSeconds * 1000;

        $this->info(sprintf('Seeding %d ticket items…', $itemCount));

        $fixture = $this->seedFixture($itemCount);
        $draw = $fixture['draw'];

        $this->info('Running trySettleDraw…');
        $started = hrtime(true);
        $ran = $orchestrator->trySettleDraw($draw->fresh());
        $elapsedMs = (int) ((hrtime(true) - $started) / 1_000_000);

        if (! $ran) {
            $this->error('Settlement did not run (check draw status / published result batch).');

            return self::FAILURE;
        }

        $settled = TicketItem::query()
            ->where('draw_id', $draw->id)
            ->whereIn('status', ['pending_payout', 'settled_lose', 'settled_win'])
            ->count();

        $pass = $elapsedMs <= $maxMs && $settled === $itemCount;

        $this->table(
            ['metric', 'value'],
            [
                ['items', (string) $itemCount],
                ['settled_rows', (string) $settled],
                ['elapsed_ms', (string) $elapsedMs],
                ['threshold_ms', (string) $maxMs],
                ['result', $pass ? 'PASS' : 'FAIL'],
            ],
        );

        return $pass ? self::SUCCESS : self::FAILURE;
    }

    private function byteMemoryLimit(string $value): int
    {
        $value = trim($value);
        if ($value === '' || $value === '-1') {
            return PHP_INT_MAX;
        }

        $unit = strtolower(substr($value, -1));
        $number = (int) $value;

        return match ($unit) {
            'g' => $number * 1024 * 1024 * 1024,
            'm' => $number * 1024 * 1024,
            'k' => $number * 1024,
            default => $number,
        };
    }

    /**
     * @return array{draw: Draw}
     */
    private function seedFixture(int $itemCount): array
    {
        return DB::transaction(function () use ($itemCount): array {
            $uniq = bin2hex(random_bytes(4));
            $player = Player::query()->create([
                'site_code' => 'perf',
                'site_player_id' => 'perf-settle-'.$uniq,
                'username' => 'perf_'.$uniq,
                'nickname' => null,
                'default_currency' => 'NPR',
                'status' => 0,
            ]);
            PlayerWallet::query()->create([
                'player_id' => $player->id,
                'wallet_type' => 'lottery',
                'currency_code' => 'NPR',
                'balance' => 0,
                'frozen_balance' => 0,
                'status' => 0,
                'version' => 0,
            ]);

            $draw = Draw::query()->create([
                'draw_no' => '20990101-'.str_pad((string) random_int(1, 999), 3, '0', STR_PAD_LEFT),
                'business_date' => '2099-01-01',
                'sequence_no' => 1,
                'status' => DrawStatus::Settling->value,
                'start_time' => now()->subHour(),
                'close_time' => now()->subMinutes(30),
                'draw_time' => now()->subMinutes(20),
                'cooling_end_time' => now()->subMinutes(5),
                'result_source' => 'rng',
                'current_result_version' => 1,
                'settle_version' => 0,
                'is_reopened' => false,
            ]);

            $batch = DrawResultBatch::query()->create([
                'draw_id' => $draw->id,
                'result_version' => 1,
                'source_type' => 'rng',
                'rng_seed_hash' => 'perf-bench',
                'raw_seed_encrypted' => null,
                'status' => DrawResultBatchStatus::Published->value,
                'created_by' => null,
                'confirmed_by' => null,
                'confirmed_at' => now(),
            ]);

            foreach (DrawPrizeLayout::slots() as $slot) {
                DrawResultItem::query()->create([
                    'draw_id' => $draw->id,
                    'result_batch_id' => $batch->id,
                    'prize_type' => $slot['prize_type'],
                    'prize_index' => $slot['prize_index'],
                    'number_4d' => '0001',
                    'suffix_3d' => '001',
                    'suffix_2d' => '01',
                    'head_digit' => 0,
                    'tail_digit' => 1,
                ]);
            }

            $order = TicketOrder::query()->create([
                'order_no' => 'TO-PERF-'.$uniq,
                'player_id' => $player->id,
                'draw_id' => $draw->id,
                'currency_code' => 'NPR',
                'total_bet_amount' => $itemCount * 10,
                'total_rebate_amount' => 0,
                'total_actual_deduct' => $itemCount * 10,
                'total_estimated_payout' => 0,
                'status' => 'placed',
                'submit_source' => 'perf',
                'client_trace_id' => 'perf-settle-'.$uniq,
            ]);

            $now = now();
            $oddsJson = json_encode(['1st' => 250000], JSON_THROW_ON_ERROR);
            $ruleJson = json_encode([], JSON_THROW_ON_ERROR);
            $chunk = 500;

            for ($offset = 0; $offset < $itemCount; $offset += $chunk) {
                $size = min($chunk, $itemCount - $offset);
                $itemRows = [];
                $ticketNos = [];

                for ($i = 0; $i < $size; $i++) {
                    $seq = $offset + $i + 1;
                    $num = str_pad((string) ($seq % 10000), 4, '0', STR_PAD_LEFT);
                    $ticketNo = sprintf('TK-PERF-%s-%06d', $uniq, $seq);
                    $ticketNos[] = $ticketNo;
                    $itemRows[] = [
                        'ticket_no' => $ticketNo,
                        'order_id' => $order->id,
                        'player_id' => $player->id,
                        'draw_id' => $draw->id,
                        'original_number' => $num,
                        'normalized_number' => $num,
                        'play_code' => 'big',
                        'dimension' => 4,
                        'digit_slot' => null,
                        'bet_mode' => 'straight',
                        'unit_bet_amount' => 10,
                        'total_bet_amount' => 10,
                        'rebate_rate_snapshot' => 0,
                        'commission_rate_snapshot' => 0,
                        'actual_deduct_amount' => 10,
                        'odds_snapshot_json' => $oddsJson,
                        'rule_snapshot_json' => $ruleJson,
                        'combination_count' => 1,
                        'estimated_max_payout' => 250,
                        'risk_locked_amount' => 0,
                        'status' => 'pending_draw',
                        'created_at' => $now,
                        'updated_at' => $now,
                    ];
                }

                DB::table('ticket_items')->insert($itemRows);

                $comboRows = DB::table('ticket_items')
                    ->whereIn('ticket_no', $ticketNos)
                    ->get(['id', 'normalized_number'])
                    ->map(fn ($row): array => [
                        'ticket_item_id' => $row->id,
                        'combination_no' => 0,
                        'number_4d' => $row->normalized_number,
                        'bet_amount' => 10,
                        'estimated_payout' => 250,
                        'created_at' => $now,
                    ])
                    ->all();

                DB::table('ticket_combinations')->insert($comboRows);
            }

            return ['draw' => $draw];
        });
    }
}
