<?php

app_require('modules/selfserve/traits/selfserve_lane_port_controller_t.php');
app_require('modules/selfserve/helpers/selfserve_lane_port.php');
app_require('modules/selfserve/helpers/selfserve_lane_state.php');
app_require('modules/selfserve/helpers/selfserve_lane_status.php');
app_require('modules/shelly/helpers/shelly_device_switch.php');

use modules\selfserve\helpers\selfserve_lane_port;
use modules\selfserve\helpers\selfserve_lane_state;
use modules\selfserve\helpers\selfserve_lane_status;
use modules\selfserve\traits\selfserve_lane_port_controller_t;
use modules\shelly\helpers\shelly_device_switch;

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

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

class SelfserveLanePortDepartmentLaneFake
{
    public SelfserveLanePortValueFake $relay_in_id;
    public SelfserveLanePortValueFake $relay_out_id;

    public function __construct(string $relayInId, string $relayOutId)
    {
        $this->relay_in_id = new SelfserveLanePortValueFake($relayInId);
        $this->relay_out_id = new SelfserveLanePortValueFake($relayOutId);
    }
}

class SelfserveLanePortSwitchFake extends shelly_device_switch
{
    /** @var array<int,array{id:string,on:bool,toggle_after:int}> */
    public array $switchCalls = [];

    public function switch(bool $on, ?bool $skip_toggle_after = false): array|object|null
    {
        $this->switchCalls[] = [
            'id' => $this->id,
            'on' => $on,
            'toggle_after' => $this->toggle_after,
        ];
        return ['ok' => true];
    }
}

class SelfservePortSwitchOutputFake extends shelly_device_switch
{
    /** @var array<int,array> */
    public array $switchCalls = [];

    protected function sendShellySwitchRequest(array $parameters): array|object|null
    {
        $this->switchCalls[] = $parameters;
        return ['ok' => true];
    }
}

class SelfserveLanePortControllerHarness
{
    use selfserve_lane_port_controller_t;

    public int $id = 55;
    public object $department_lane;
    public SelfserveLanePortSwitchFake $switchFake;
    public selfserve_lane_status $laneStatus;
    public selfserve_lane_state $laneState;

    public function __construct(string $relayInId, string $relayOutId)
    {
        $this->department_lane = new SelfserveLanePortDepartmentLaneFake($relayInId, $relayOutId);
        $this->switchFake = new SelfserveLanePortSwitchFake();
        $this->laneStatus = selfserve_lane_status::AVAILABLE;
        $this->laneState = selfserve_lane_state::IDLE;
    }

    protected function createShellySwitchDevice(): shelly_device_switch
    {
        return $this->switchFake;
    }

    public function getLaneStatus(): selfserve_lane_status
    {
        return $this->laneStatus;
    }

    public function setLaneState(selfserve_lane_state $state): void
    {
        $this->laneState = $state;
    }

    public function logLaneAction(...$args): void {}
}

it('opens exit port by switching relay_out_id on', function (): void {
    $lane = new SelfserveLanePortControllerHarness(
        relayInId: 'relay-in-123',
        relayOutId: 'relay-out-123'
    );

    $result = $lane->shellyOpenPort(selfserve_lane_port::EXIT);

    expect($result)->toBeTrue();
    expect($lane->switchFake->switchCalls)->toBe([
        [
            'id' => 'relay-out-123',
            'on' => true,
            'toggle_after' => 1,
        ],
    ]);
});

it('opens entrance port by switching relay_in_id on', function (): void {
    $lane = new SelfserveLanePortControllerHarness(
        relayInId: 'relay-in-123',
        relayOutId: 'relay-out-123'
    );

    $result = $lane->shellyOpenPort(selfserve_lane_port::ENTRANCE);

    expect($result)->toBeTrue();
    expect($lane->switchFake->switchCalls)->toBe([
        [
            'id' => 'relay-in-123',
            'on' => true,
            'toggle_after' => 1,
        ],
    ]);
});

it('passes explicit timer values when opening lane gates', function (): void {
    $lane = new SelfserveLanePortControllerHarness(
        relayInId: 'relay-in-123',
        relayOutId: 'relay-out-123'
    );

    $result = $lane->open(selfserve_lane_port::ENTRANCE, 4);

    expect($result)->toBeTrue();
    expect($lane->switchFake->switchCalls)->toBe([
        [
            'id' => 'relay-in-123',
            'on' => true,
            'toggle_after' => 4,
        ],
    ]);
});

it('caps excessive timer values when opening lane gates', function (): void {
    $lane = new SelfserveLanePortControllerHarness(
        relayInId: 'relay-in-123',
        relayOutId: 'relay-out-123'
    );

    $result = $lane->open(selfserve_lane_port::ENTRANCE, 999999999);

    expect($result)->toBeTrue();
    expect($lane->switchFake->switchCalls)->toBe([
        [
            'id' => 'relay-in-123',
            'on' => true,
            'toggle_after' => 5,
        ],
    ]);
});

it('keeps demo relay gate-open queued but skips Shelly switch calls', function (): void {
    $lane = new SelfserveLanePortControllerHarness(
        relayInId: 'demo-relay-in',
        relayOutId: 'demo-relay-out'
    );

    $result = $lane->open(selfserve_lane_port::EXIT);

    expect($result)->toBeTrue();
    expect($lane->laneState)->toBe(selfserve_lane_state::EXIT_PORT_OPEN_QUEUED);
    expect($lane->switchFake->switchCalls)->toBe([]);
});

it('does not print Shelly switch responses to output', function (): void {
    $switch = new SelfservePortSwitchOutputFake();
    $switch->id = 'relay-port-123';

    ob_start();
    $switch->switch(true);
    $output = (string)ob_get_clean();

    expect($output)->toBe('');
    expect($switch->switchCalls)->toHaveCount(1);
    expect($switch->switchCalls[0])->toMatchArray([
        'id' => 'relay-port-123',
        'channel' => 0,
        'on' => true,
        'toggle_after' => 1,
    ]);
});
