<?php

namespace Tests\Unit;

use App\Support\Sequences\SequenceGenerator;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class SequenceGeneratorTest extends TestCase
{
    use RefreshDatabase;

    private function generator(): SequenceGenerator
    {
        return app(SequenceGenerator::class);
    }

    public function test_it_formats_a_reference_with_prefix_scope_and_padding(): void
    {
        $generator = $this->generator();

        $this->assertSame('INV-DHK-2026-000001', $generator->next('invoice', 'INV', ['DHK', 2026]));
        $this->assertSame('INV-DHK-2026-000002', $generator->next('invoice', 'INV', ['DHK', 2026]));
    }

    public function test_counters_are_independent_per_scope(): void
    {
        $generator = $this->generator();

        $generator->next('invoice', 'INV', ['DHK', 2026]);
        $generator->next('invoice', 'INV', ['DHK', 2026]);

        $this->assertSame('INV-CTG-2026-000001', $generator->next('invoice', 'INV', ['CTG', 2026]));
        $this->assertSame('INV-DHK-2027-000001', $generator->next('invoice', 'INV', ['DHK', 2027]));
    }

    public function test_counters_are_independent_per_type(): void
    {
        $generator = $this->generator();

        $generator->next('invoice', 'INV', ['DHK', 2026]);

        $this->assertSame('RCT-DHK-2026-000001', $generator->next('receipt', 'RCT', ['DHK', 2026]));
    }

    public function test_it_supports_an_unscoped_sequence(): void
    {
        $generator = $this->generator();

        $this->assertSame('JV-000001', $generator->next('journal', 'JV'));
        $this->assertSame('JV-000002', $generator->next('journal', 'JV'));
    }

    public function test_padding_is_configurable(): void
    {
        $this->assertSame('AGT-DHK-0001', $this->generator()->next('agent', 'AGT', ['DHK'], 4));
    }

    public function test_peek_does_not_consume_a_number(): void
    {
        $generator = $this->generator();
        $generator->next('invoice', 'INV', ['DHK', 2026]);

        $this->assertSame(2, $generator->peek('invoice', 'DHK-2026'));
        $this->assertSame(2, $generator->peek('invoice', 'DHK-2026'));
        $this->assertSame('INV-DHK-2026-000002', $generator->next('invoice', 'INV', ['DHK', 2026]));
    }

    public function test_fast_forward_never_moves_a_counter_backwards(): void
    {
        $generator = $this->generator();

        $generator->fastForward('invoice', 'DHK-2026', 500);
        $this->assertSame('INV-DHK-2026-000500', $generator->next('invoice', 'INV', ['DHK', 2026]));

        $generator->fastForward('invoice', 'DHK-2026', 10);
        $this->assertSame('INV-DHK-2026-000501', $generator->next('invoice', 'INV', ['DHK', 2026]));
    }

    public function test_next_unique_skips_numbers_the_counter_does_not_know_about(): void
    {
        $generator = $this->generator();

        // Imagine DEM-SA-2026-0001 and -0002 already exist from a legacy import:
        // the counter starts at 1 and would collide.
        $taken = ['DEM-SA-2026-0001', 'DEM-SA-2026-0002'];

        $code = $generator->nextUnique(
            'job_demand',
            'DEM',
            ['SA', 2026],
            4,
            fn (string $candidate) => in_array($candidate, $taken, true),
        );

        $this->assertSame('DEM-SA-2026-0003', $code);
    }

    public function test_next_unique_gives_up_rather_than_looping_forever(): void
    {
        $this->expectException(\RuntimeException::class);

        $this->generator()->nextUnique(
            'journal',
            'JV',
            [],
            6,
            fn () => true, // Everything is taken.
            maxAttempts: 5,
        );
    }

    public function test_numbers_are_never_reused_across_many_draws(): void
    {
        $generator = $this->generator();

        $numbers = array_map(
            fn () => $generator->next('receipt', 'RCT', ['DHK', 2026]),
            range(1, 50),
        );

        $this->assertCount(50, array_unique($numbers));
        $this->assertSame('RCT-DHK-2026-000050', end($numbers));
    }
}
