<?php

app_require('modules/selfserve/traits/selfserve_lane_command_t.php');
app_require('modules/selfserve/classes/selfserve_lane_command_arguments.php');
app_require('modules/selfserve/helpers/selfserve_lane_relay.php');

use modules\selfserve\classes\selfserve_lane_command_arguments;
use modules\selfserve\traits\selfserve_lane_command_t;

class SelfserveProgramPickerSelectionValueFake
{
    public function __construct(private readonly string $value) {}

    public function value(): string
    {
        return $this->value;
    }
}

class SelfserveProgramPickerSelectionDepartmentLaneFake
{
    public SelfserveProgramPickerSelectionValueFake $relay_machine_id;
    public SelfserveProgramPickerSelectionValueFake $relay_machine_program_picker_id;
    public SelfserveProgramPickerSelectionValueFake $relay_machine_cleaner_id;

    public function __construct()
    {
        $this->relay_machine_id = new SelfserveProgramPickerSelectionValueFake('relay-machine');
        $this->relay_machine_program_picker_id = new SelfserveProgramPickerSelectionValueFake('relay-program-picker');
        $this->relay_machine_cleaner_id = new SelfserveProgramPickerSelectionValueFake('relay-cleaner');
    }
}

class SelfserveProgramPickerSelectionHarness
{
    use selfserve_lane_command_t;

    public const CACHE_SELFSERVE_LANE_KEY_ALLOWED_SERVICES = 'allowed_services';

    public int $id = 881;
    public object $department_lane;
    public int $licensePlateReads = 0;
    public int $customerNumberReads = 0;
    public int $availabilityChecks = 0;
    public bool $machineAvailable = true;
    /** @var array<int,bool> */
    public array $programPickerWrites = [];
    /** @var array<string,mixed> */
    private array $laneCache = [];

    public function __construct()
    {
        $this->department_lane = new SelfserveProgramPickerSelectionDepartmentLaneFake();
    }

    public function getLaneCache(int $lane_id, string $key): mixed
    {
        return $this->laneCache[$key . '_' . $lane_id] ?? null;
    }

    public function setSelectedServices(array $services): void
    {
        $this->laneCache[self::CACHE_SELFSERVE_LANE_KEY_ALLOWED_SERVICES . '_' . $this->id] = $services;
    }

    public function getLicensePlate(): string
    {
        $this->licensePlateReads++;
        return 'AB12345';
    }

    public function getCustomerNumber(): int
    {
        $this->customerNumberReads++;
        return 12345678;
    }

    public function setMachineProgramPickerRelayStatusHard(bool $on): bool
    {
        $this->programPickerWrites[] = $on;
        return true;
    }

    public function runDeferredStartRelaySideEffects(?string $washType = null): void
    {
        $arguments = (new selfserve_lane_command_arguments())
            ->setDeferRelaySideEffects(true)
            ->setWashMode($washType);
        $this->runRelaySideEffectsForWashStart($arguments);
    }

    public function runNormalStartProgramPickerRelay(?string $washType = null): void
    {
        $arguments = (new selfserve_lane_command_arguments())->setWashMode($washType);
        $this->setProgramPickerRelayStatusForWashStart($arguments);
    }

    public function resolveStartWashMode(?string $washType = null): string
    {
        $arguments = (new selfserve_lane_command_arguments())->setWashMode($washType);
        return $this->resolveSelfServeActionWashModeForStart($arguments);
    }

    protected function isMachineWashSelectedAndAvailableForStart(?selfserve_lane_command_arguments $arguments = null): bool
    {
        unset($arguments);
        $this->availabilityChecks++;
        return $this->machineAvailable;
    }
}

it('turns off the program picker on deferred start when the frontend selected manual wash', function (): void {
    $lane = new SelfserveProgramPickerSelectionHarness();
    $lane->setSelectedServices([]);

    $lane->runDeferredStartRelaySideEffects('Manual');

    expect($lane->programPickerWrites)->toBe([false]);
});

it('does not let backend machine eligibility override a frontend manual wash selection', function (): void {
    $lane = new SelfserveProgramPickerSelectionHarness();
    $lane->setSelectedServices(['MACHINE']);

    $lane->runDeferredStartRelaySideEffects('Manual');

    expect($lane->licensePlateReads)->toBe(0)
        ->and($lane->customerNumberReads)->toBe(0)
        ->and($lane->programPickerWrites)->toBe([false])
        ->and($lane->resolveStartWashMode('Manual'))->toBe('manual');
});

it('does not infer program picker enablement from machine service without a customer machine selection', function (): void {
    $lane = new SelfserveProgramPickerSelectionHarness();
    $lane->setSelectedServices(['MACHINE']);

    $lane->runDeferredStartRelaySideEffects();
    $lane->runNormalStartProgramPickerRelay();

    expect($lane->programPickerWrites)->toBe([false, false])
        ->and($lane->availabilityChecks)->toBe(0);
});

it('keeps normal start program picker off when the customer selected manual wash', function (): void {
    $lane = new SelfserveProgramPickerSelectionHarness();
    $lane->setSelectedServices(['MACHINE']);

    $lane->runNormalStartProgramPickerRelay('Manual');

    expect($lane->programPickerWrites)->toBe([false])
        ->and($lane->availabilityChecks)->toBe(0);
});

it('honors a frontend machine wash selection when machine service is selected', function (): void {
    $lane = new SelfserveProgramPickerSelectionHarness();
    $lane->setSelectedServices(['MACHINE']);

    $lane->runDeferredStartRelaySideEffects('Machine');
    $lane->runNormalStartProgramPickerRelay('Machine');

    expect($lane->programPickerWrites)->toBe([true, true])
        ->and($lane->availabilityChecks)->toBe(1)
        ->and($lane->resolveStartWashMode('Machine'))->toBe('machine');
});
