<?php

namespace Modules\Vrc\Tests\Unit;

use Modules\Vrc\Services\RepairOrder\InvalidTransitionException;
use Modules\Vrc\Services\RepairOrder\RepairOrderStateMachine;
use PHPUnit\Framework\TestCase;

/**
 * Pure unit tests for the repair-order state machine (spec §4.3). No framework
 * boot / DB — the transition map is injected, mirroring config('vrc...').
 */
class RepairOrderStateMachineTest extends TestCase
{
    private function machine(): RepairOrderStateMachine
    {
        // Mirror of config/vrc.php repair_order.transitions.
        $transitions = [
            'draft'             => ['scheduled', 'checked_in', 'cancelled'],
            'scheduled'         => ['checked_in', 'cancelled'],
            'checked_in'        => ['awaiting_estimate', 'in_progress', 'on_hold', 'cancelled'],
            'awaiting_estimate' => ['estimate_sent', 'on_hold', 'cancelled'],
            'estimate_sent'     => ['estimate_approved', 'estimate_declined', 'on_hold', 'cancelled'],
            'estimate_approved' => ['in_progress', 'on_hold', 'cancelled'],
            'estimate_declined' => ['awaiting_estimate', 'cancelled'],
            'in_progress'       => ['awaiting_parts', 'quality_check', 'on_hold', 'cancelled'],
            'awaiting_parts'    => ['in_progress', 'on_hold', 'cancelled'],
            'on_hold'           => ['checked_in', 'awaiting_estimate', 'estimate_sent', 'estimate_approved', 'in_progress', 'awaiting_parts', 'cancelled'],
            'quality_check'     => ['in_progress', 'ready_for_pickup'],
            'ready_for_pickup'  => ['invoiced'],
            'invoiced'          => ['completed'],
            'completed'         => [],
            'cancelled'         => [],
        ];

        return new RepairOrderStateMachine($transitions, 'draft');
    }

    public function test_happy_path_transitions_are_allowed(): void
    {
        $m = $this->machine();
        $path = [
            ['draft', 'checked_in'],
            ['checked_in', 'awaiting_estimate'],
            ['awaiting_estimate', 'estimate_sent'],
            ['estimate_sent', 'estimate_approved'],
            ['estimate_approved', 'in_progress'],
            ['in_progress', 'quality_check'],
            ['quality_check', 'ready_for_pickup'],
            ['ready_for_pickup', 'invoiced'],
            ['invoiced', 'completed'],
        ];

        foreach ($path as [$from, $to]) {
            $this->assertTrue($m->canTransition($from, $to), "$from → $to should be allowed");
        }
    }

    public function test_illegal_transitions_are_rejected(): void
    {
        $m = $this->machine();
        $this->assertFalse($m->canTransition('draft', 'completed'));
        $this->assertFalse($m->canTransition('checked_in', 'invoiced'));
        $this->assertFalse($m->canTransition('estimate_declined', 'in_progress'));
        $this->assertFalse($m->canTransition('completed', 'in_progress'));
    }

    public function test_assert_throws_on_illegal_transition(): void
    {
        $this->expectException(InvalidTransitionException::class);
        $this->machine()->assertCanTransition('draft', 'completed');
    }

    public function test_assert_passes_on_legal_transition(): void
    {
        $this->machine()->assertCanTransition('draft', 'checked_in');
        $this->assertTrue(true); // reached without exception
    }

    public function test_terminal_states(): void
    {
        $m = $this->machine();
        $this->assertTrue($m->isTerminal('completed'));
        $this->assertTrue($m->isTerminal('cancelled'));
        $this->assertFalse($m->isTerminal('in_progress'));
    }

    public function test_side_states_reachable(): void
    {
        $m = $this->machine();
        $this->assertTrue($m->canTransition('in_progress', 'awaiting_parts'));
        $this->assertTrue($m->canTransition('awaiting_parts', 'in_progress'));
        $this->assertTrue($m->canTransition('in_progress', 'on_hold'));
        $this->assertTrue($m->canTransition('on_hold', 'in_progress'));
        $this->assertTrue($m->canTransition('estimate_sent', 'estimate_declined'));
    }

    public function test_cancellation_allowed_from_pre_completion_states_only(): void
    {
        $m = $this->machine();
        foreach (['draft', 'checked_in', 'awaiting_estimate', 'in_progress'] as $s) {
            $this->assertTrue($m->canTransition($s, 'cancelled'), "$s → cancelled");
        }
        // Cannot cancel once invoiced or completed.
        $this->assertFalse($m->canTransition('invoiced', 'cancelled'));
        $this->assertFalse($m->canTransition('completed', 'cancelled'));
    }

    public function test_statuses_cover_appendix_a(): void
    {
        $expected = [
            'draft', 'scheduled', 'checked_in', 'awaiting_estimate', 'estimate_sent',
            'estimate_approved', 'estimate_declined', 'in_progress', 'awaiting_parts',
            'on_hold', 'quality_check', 'ready_for_pickup', 'invoiced', 'completed', 'cancelled',
        ];
        $actual = $this->machine()->statuses();
        sort($expected);
        sort($actual);
        $this->assertSame($expected, $actual);
    }
}
