<?php

app_require('objects/department_gates_o.php');
app_require('modules/selfserve/traits/selfserve_lane_command_t.php');
app_require('modules/selfserve/helpers/selfserve_lane_command.php');
app_require('modules/selfserve/classes/selfserve_lane_command_arguments.php');

use modules\selfserve\classes\selfserve_lane_command_arguments;
use modules\selfserve\helpers\selfserve_lane_command;
use modules\selfserve\traits\selfserve_lane_command_t;
use objects\department_gates_o;

class SelfserveLanePropertyGateValueFake
{
    public function __construct(private readonly int $value) {}

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

class SelfserveLanePropertyGateDepartmentLaneFake
{
    public SelfserveLanePropertyGateValueFake $department;

    public function __construct(int $departmentId)
    {
        $this->department = new SelfserveLanePropertyGateValueFake($departmentId);
    }
}

class SelfserveLanePropertyGateFake extends department_gates_o
{
    public int $openCalls = 0;

    public function __construct(
        private readonly bool $existsFlag = true,
        private readonly bool $throwOnOpen = false,
        private readonly string $throwMessage = 'gate open failed',
    ) {}

    public function exists(): bool
    {
        return $this->existsFlag;
    }

    public function openGate(): void
    {
        $this->openCalls++;
        if ($this->throwOnOpen) {
            throw new \RuntimeException($this->throwMessage);
        }
    }
}

class SelfserveLanePropertyGateHarness
{
    use selfserve_lane_command_t;

    public const DEFAULT_WASH_START_TIME = 0;
    public const DEFAULT_CUSTOMER_NUMBER = 0;
    public const DEFAULT_LICENSE_PLATE = '';

    public int $id = 901;
    public object $department_lane;
    public bool $selfServeEnabled = true;
    public ?department_gates_o $entranceGate = null;
    public ?department_gates_o $exitGate = null;

    public function __construct(int $departmentId = 77)
    {
        $this->department_lane = new SelfserveLanePropertyGateDepartmentLaneFake($departmentId);
    }

    protected function isDepartmentSelfServeEnabled(): bool
    {
        return $this->selfServeEnabled;
    }

    protected function resolveDepartmentGateForCommand(int $department_id, bool $isAccessGate): ?department_gates_o
    {
        return $isAccessGate ? $this->entranceGate : $this->exitGate;
    }
}

it('opens entrance gate for OPEN_PROPERTY_ACCESS_GATE command', function (): void {
    $lane = new SelfserveLanePropertyGateHarness();
    $gate = new SelfserveLanePropertyGateFake();
    $lane->entranceGate = $gate;

    $lane->execute(selfserve_lane_command::OPEN_PROPERTY_ACCESS_GATE, new selfserve_lane_command_arguments());

    expect($gate->openCalls)->toBe(1);
});

it('opens exit gate for OPEN_PROPERTY_EXIT_GATE command', function (): void {
    $lane = new SelfserveLanePropertyGateHarness();
    $gate = new SelfserveLanePropertyGateFake();
    $lane->exitGate = $gate;

    $lane->execute(selfserve_lane_command::OPEN_PROPERTY_EXIT_GATE, new selfserve_lane_command_arguments());

    expect($gate->openCalls)->toBe(1);
});

it('blocks property gate commands when department self-serve is disabled', function (): void {
    $lane = new SelfserveLanePropertyGateHarness();
    $lane->selfServeEnabled = false;
    $lane->entranceGate = new SelfserveLanePropertyGateFake();

    expect(function () use ($lane): void {
        $lane->execute(selfserve_lane_command::OPEN_PROPERTY_ACCESS_GATE, new selfserve_lane_command_arguments());
    })->toThrow(\RuntimeException::class, 'Self-serve is not enabled');
});

it('throws a clear error when entrance gate is missing for property access command', function (): void {
    $lane = new SelfserveLanePropertyGateHarness();
    $lane->entranceGate = null;

    expect(function () use ($lane): void {
        $lane->execute(selfserve_lane_command::OPEN_PROPERTY_ACCESS_GATE, new selfserve_lane_command_arguments());
    })->toThrow(\RuntimeException::class, 'No entrance gate configured');
});

it('wraps low-level gate errors for property access command failures', function (): void {
    $lane = new SelfserveLanePropertyGateHarness();
    $lane->entranceGate = new SelfserveLanePropertyGateFake(
        existsFlag: true,
        throwOnOpen: true,
        throwMessage: 'simulated gateway timeout',
    );

    expect(function () use ($lane): void {
        $lane->execute(selfserve_lane_command::OPEN_PROPERTY_ACCESS_GATE, new selfserve_lane_command_arguments());
    })->toThrow(\RuntimeException::class, 'Failed to open property access gate.');
});

it('wraps low-level gate errors for property exit command failures', function (): void {
    $lane = new SelfserveLanePropertyGateHarness();
    $lane->exitGate = new SelfserveLanePropertyGateFake(
        existsFlag: true,
        throwOnOpen: true,
        throwMessage: 'simulated provider error',
    );

    expect(function () use ($lane): void {
        $lane->execute(selfserve_lane_command::OPEN_PROPERTY_EXIT_GATE, new selfserve_lane_command_arguments());
    })->toThrow(\RuntimeException::class, 'Failed to open property exit gate.');
});
