<?php

app_require('routes/moduleSelfServeRoute.php');
app_require('modules/selfserve/classes/selfserve_lane.php');
app_require('objects/department_lanes_o.php');
app_require('classes/object_property.php');

use classes\object_property;
use modules\selfserve\classes\selfserve_lane;
use objects\department_lanes_o;
use routes\moduleSelfServeRoute;

class SelfserveCustomerLaneAccessRouteHarness extends moduleSelfServeRoute
{
    /** @var array<string,bool> */
    public array $permissions = [
        'list_own_department_selfserve_vehicle_conditions' => true,
        'add_own_department_selfserve_vehicle_conditions' => true,
    ];
    public bool $laneEnabled = true;
    public bool $moduleEnabled = true;
    public bool $activeWashResult = false;
    /** @var array<int,array{department_id:int,customer_number:int}> */
    public array $activeWashChecks = [];
    /** @var array<int,string> */
    public array $forbiddenPermissions = [];

    public function __construct()
    {
        // Avoid route_t request initialization in this focused unit test.
    }

    public function hasPermission(string|\classes\permission_node $permission, int $customer_number = null): bool
    {
        $key = $permission instanceof \classes\permission_node ? (string)$permission->permission : $permission;
        return $this->permissions[$key] ?? false;
    }

    public function canUseLane(selfserve_lane $lane, int $customer_number): bool
    {
        return $this->canCustomerUseSelfServeLane($lane, $customer_number);
    }

    public function canUseActiveLane(selfserve_lane $lane, int $customer_number): bool
    {
        return $this->canCustomerUseActiveSelfServeLane($lane, $customer_number);
    }

    protected function isSelfServeModuleEnabled(): bool
    {
        return $this->moduleEnabled;
    }

    protected function isLaneSelfServeOperationallyEnabled(selfserve_lane $lane): bool
    {
        return $this->laneEnabled;
    }

    protected function emitForbidden(array $permissions): void
    {
        $this->forbiddenPermissions = array_values(array_map(
            static fn(string|\classes\permission_node $permission): string => $permission instanceof \classes\permission_node
                ? $permission->permission
                : $permission,
            $permissions
        ));
        throw new RuntimeException('forbidden');
    }

    public function requireCommandPermissionForTest(
        selfserve_lane $lane,
        int $customer_number,
        string $command_permission,
        bool $allow_customer_self_serve,
        bool $requires_active_wash = false
    ): void {
        $method = new ReflectionMethod(moduleSelfServeRoute::class, 'requireSelfServeLaneCommandPermission');
        $method->invoke($this, $lane, $customer_number, $command_permission, $allow_customer_self_serve, $requires_active_wash);
    }

    protected function customerHasActiveSelfServeWashInDepartment(int $department_id, int $customer_number, ?int $subuser_id = null): bool
    {
        $this->activeWashChecks[] = [
            'department_id' => $department_id,
            'customer_number' => $customer_number,
            'subuser_id' => $subuser_id,
        ];

        return $this->activeWashResult;
    }
}

class SelfserveCustomerLaneAccessDepartmentLaneFake extends department_lanes_o
{
    public function __construct(int $department_id)
    {
        $this->id = -1;
        $this->department = new object_property('department_lanes', -1, 'department', 'int');
        $this->department->set($department_id);
    }

    public function structure(): void
    {
        // Skip database bootstrap for this unit test.
    }
}

class SelfserveCustomerLaneAccessLaneFake extends selfserve_lane
{
    public ?int $fakeCustomerNumber = null;

    public function __construct(int $department_id, ?int $customer_number = null)
    {
        $this->id = 91;
        $this->fakeCustomerNumber = $customer_number;
        $this->department_lane = new SelfserveCustomerLaneAccessDepartmentLaneFake($department_id);
    }

    public function getCustomerNumber(): ?int
    {
        return $this->fakeCustomerNumber;
    }
}

it('allows customers with own self-serve permission to use enabled self-serve lanes', function (): void {
    $route = new SelfserveCustomerLaneAccessRouteHarness();
    $lane = new SelfserveCustomerLaneAccessLaneFake(4);

    expect($route->canUseLane($lane, 12345679))->toBeTrue();
});

it('blocks customer lane mutations without own self-serve permission', function (): void {
    $route = new SelfserveCustomerLaneAccessRouteHarness();
    $route->permissions = [];
    $lane = new SelfserveCustomerLaneAccessLaneFake(4);

    expect($route->canUseLane($lane, 12345679))->toBeFalse();
});

it('blocks customer lane mutations when the lane is not operationally enabled', function (): void {
    $route = new SelfserveCustomerLaneAccessRouteHarness();
    $route->laneEnabled = false;
    $lane = new SelfserveCustomerLaneAccessLaneFake(4);

    expect($route->canUseLane($lane, 12345679))->toBeFalse();
});

it('allows active wash operations when the lane runtime belongs to the customer', function (): void {
    $route = new SelfserveCustomerLaneAccessRouteHarness();
    $lane = new SelfserveCustomerLaneAccessLaneFake(4, 12345679);

    expect($route->canUseActiveLane($lane, 12345679))->toBeTrue();
    expect($route->activeWashChecks)->toBe([]);
});

it('keeps active wash operations available if a lane is disabled after start', function (): void {
    $route = new SelfserveCustomerLaneAccessRouteHarness();
    $route->laneEnabled = false;
    $lane = new SelfserveCustomerLaneAccessLaneFake(4, 12345679);

    expect($route->canUseActiveLane($lane, 12345679))->toBeTrue();
    expect($route->activeWashChecks)->toBe([]);
});

it('falls back to active department sessions for customer active wash operations', function (): void {
    $route = new SelfserveCustomerLaneAccessRouteHarness();
    $route->activeWashResult = true;
    $lane = new SelfserveCustomerLaneAccessLaneFake(4, null);

    expect($route->canUseActiveLane($lane, 12345679))->toBeTrue();
    expect($route->activeWashChecks)->toBe([
        [
            'department_id' => 4,
            'customer_number' => 12345679,
            'subuser_id' => null,
        ],
    ]);
});

it('blocks active wash operations for other customers', function (): void {
    $route = new SelfserveCustomerLaneAccessRouteHarness();
    $route->activeWashResult = false;
    $lane = new SelfserveCustomerLaneAccessLaneFake(4, 99999999);

    expect($route->canUseActiveLane($lane, 12345679))->toBeFalse();
    expect($route->activeWashChecks)->toBe([
        [
            'department_id' => 4,
            'customer_number' => 12345679,
            'subuser_id' => null,
        ],
    ]);
});


it('denies customer self-serve command fallback when the self-serve module is disabled', function (): void {
    $route = new SelfserveCustomerLaneAccessRouteHarness();
    $route->moduleEnabled = false;
    $lane = new SelfserveCustomerLaneAccessLaneFake(4, 12345679);

    expect(fn() => $route->requireCommandPermissionForTest(
        $lane,
        12345679,
        'modules_selfserve_lane_command_execute_stop',
        true,
        true
    ))->toThrow(RuntimeException::class, 'forbidden');
});

it('returns deterministic forbidden permissions when disabled module blocks customer self-serve command fallback', function (): void {
    $route = new SelfserveCustomerLaneAccessRouteHarness();
    $route->moduleEnabled = false;
    $lane = new SelfserveCustomerLaneAccessLaneFake(4, 12345679);

    try {
        $route->requireCommandPermissionForTest(
            $lane,
            12345679,
            'modules_selfserve_lane_command_execute_stop',
            true,
            true
        );
    } catch (RuntimeException $exception) {
        expect($exception->getMessage())->toBe('forbidden');
    }

    expect($route->forbiddenPermissions)->toBe([
        'modules_selfserve_lane_command_execute',
        'modules_selfserve_lane_command_execute_stop',
        'list_own_department_selfserve_vehicle_conditions',
    ]);
});

it('continues allowing operator self-serve commands when the self-serve module is disabled', function (): void {
    $route = new SelfserveCustomerLaneAccessRouteHarness();
    $route->moduleEnabled = false;
    $route->permissions = [
        'modules_selfserve_lane_command_execute' => true,
        'modules_selfserve_lane_command_execute_stop' => true,
    ];
    $lane = new SelfserveCustomerLaneAccessLaneFake(4, 12345679);

    $route->requireCommandPermissionForTest(
        $lane,
        12345679,
        'modules_selfserve_lane_command_execute_stop',
        true,
        true
    );

    expect($route->forbiddenPermissions)->toBe([]);
});
