<?php

use App\Models\OddsItem;
use App\Models\PlayConfigItem;
use Illuminate\Support\Collection;
use App\Services\Ticket\PlayRuleEngine;
use App\Services\Ticket\NumberNormalizer;
use App\Exceptions\TicketOperationException;

function selectionTypeEngine(): PlayRuleEngine
{
    return new PlayRuleEngine(new NumberNormalizer);
}

function selectionTypePlayConfig(int $dimension = 4): PlayConfigItem
{
    return new PlayConfigItem([
        'play_code' => 'big',
        'dimension' => $dimension,
        'min_bet_amount' => 1,
        'max_bet_amount' => 1_000_000,
    ]);
}

/** @return Collection<int, OddsItem> */
function selectionTypeOdds(): Collection
{
    return collect([
        new OddsItem([
            'provider_code' => 'GLOBAL',
            'prize_scope' => 'first',
            'dimension' => 4,
            'odds_value' => 20_000,
            'rebate_rate' => '0.0000',
            'commission_rate' => '0.0000',
        ]),
    ]);
}

/** @return array<string, mixed> */
function evaluateSelectionType(string $selectionType, int $amount = 100, string $number = '1234'): array
{
    return selectionTypeEngine()->evaluateLine(
        [
            'play_code' => 'big',
            'number' => $number,
            'amount' => $amount,
            'selection_type' => $selectionType,
        ],
        selectionTypePlayConfig(),
        selectionTypeOdds(),
    );
}

test('selection types expand 4D numbers and apply the correct stake semantics', function (): void {
    $straight = evaluateSelectionType('straight');
    $reverse = evaluateSelectionType('reverse');
    $halfPlay = evaluateSelectionType('half_play');
    $fullCover = evaluateSelectionType('full_cover', 2_400);
    $fullPlay = evaluateSelectionType('full_play');

    expect($straight['combination_count'])->toBe(1)
        ->and($straight['total_bet_amount'])->toBe(100)
        ->and($straight['combinations'][0]['number_4d'])->toBe('1234')
        ->and($reverse['combination_count'])->toBe(2)
        ->and($reverse['total_bet_amount'])->toBe(200)
        ->and(collect($reverse['combinations'])->pluck('number_4d')->all())->toBe(['1234', '4321'])
        ->and($halfPlay['combination_count'])->toBe(12)
        ->and($halfPlay['total_bet_amount'])->toBe(1_200)
        ->and($fullCover['combination_count'])->toBe(24)
        ->and($fullCover['unit_bet_amount'])->toBe(100)
        ->and($fullCover['total_bet_amount'])->toBe(2_400)
        ->and($fullPlay['combination_count'])->toBe(24)
        ->and($fullPlay['unit_bet_amount'])->toBe(100)
        ->and($fullPlay['total_bet_amount'])->toBe(2_400);
});

test('selection types use unique permutations when a number contains repeated digits', function (): void {
    $fullPlay = evaluateSelectionType('full_play', 100, '1122');
    $halfPlay = evaluateSelectionType('half_play', 100, '1232');

    expect($fullPlay['combination_count'])->toBe(6)
        ->and($fullPlay['total_bet_amount'])->toBe(600)
        ->and($halfPlay['combination_count'])->toBe(4)
        ->and($halfPlay['total_bet_amount'])->toBe(400);
});

test('full cover rejects an amount that cannot be evenly divided across combinations', function (): void {
    expect(fn (): array => evaluateSelectionType('full_cover', 2_399))
        ->toThrow(TicketOperationException::class, 'full_cover_amount_not_divisible');
});

test('selection types enforce their dimension availability', function (): void {
    expect(fn (): array => selectionTypeEngine()->evaluateLine(
        [
            'play_code' => 'pos_3a',
            'number' => '123',
            'amount' => 100,
            'selection_type' => 'half_play',
        ],
        selectionTypePlayConfig(3),
        selectionTypeOdds(),
    ))->toThrow(TicketOperationException::class, 'selection_type_not_allowed');
});
