<?php

app_require('traits/route_t.php');

use traits\route_t;

class AllowOwnOrDepartmentAccessForbiddenHost
{
    use route_t;

    /** @var array<string, bool> */
    public array $permissionsByKey = [];
    public bool $ownContext = true;
    /** @var array<int, string>|null */
    public ?array $lastForbidden = null;
    public bool $departmentAccessChecked = false;

    public function __construct()
    {
        // no-op for unit tests
    }

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

    public function isOwnCustomerContext(int $targetCustomerNumber): bool
    {
        return $this->ownContext;
    }

    public function requireDepartmentAccess(string $department, string|null $permission = null): void
    {
        $this->departmentAccessChecked = true;
    }

    protected function emitForbidden(array $permissions): void
    {
        $normalized = [];
        foreach ($permissions as $permission) {
            $key = is_string($permission) ? trim($permission) : trim((string)$permission->permission);
            if ($key !== '') {
                $normalized[] = $key;
            }
        }
        $this->lastForbidden = array_values(array_unique($normalized));
        throw new RuntimeException('forbidden');
    }
}

it('returns both own and elevated permissions when both are missing', function (): void {
    $host = new AllowOwnOrDepartmentAccessForbiddenHost();
    $host->permissionsByKey = [
        'perm_own' => false,
        'perm_other' => false,
    ];

    expect(fn() => $host->allowOwnOrDepartmentAccess('perm_own', 'perm_other', 101, null))
        ->toThrow(RuntimeException::class, 'forbidden');

    expect($host->lastForbidden)->toBe(['perm_own', 'perm_other']);
});

it('returns only elevated permission when own permission exists but own context fails', function (): void {
    $host = new AllowOwnOrDepartmentAccessForbiddenHost();
    $host->permissionsByKey = [
        'perm_own' => true,
        'perm_other' => false,
    ];
    $host->ownContext = false;

    expect(fn() => $host->allowOwnOrDepartmentAccess('perm_own', 'perm_other', 202, null))
        ->toThrow(RuntimeException::class, 'forbidden');

    expect($host->lastForbidden)->toBe(['perm_other']);
});

it('allows elevated permission path without forbidden and validates department access', function (): void {
    $host = new AllowOwnOrDepartmentAccessForbiddenHost();
    $host->permissionsByKey = [
        'perm_own' => false,
        'perm_other' => true,
    ];

    $allowed = $host->allowOwnOrDepartmentAccess('perm_own', 'perm_other', 303, 77);

    expect($allowed)->toBeTrue();
    expect($host->departmentAccessChecked)->toBeTrue();
    expect($host->lastForbidden)->toBeNull();
});
