<?php

declare(strict_types=1);

usesApiSuite();

it('creates install tokens, tracks installer status, and exposes claimed gateway detail to authorized operators', function (): void {
    $department = api_fixtures()->createDepartment([
        'name' => 'Edge Operator Department',
    ]);
    $session = api_fixtures()->createEdgeOperatorSession((int)$department['id']);

    $createResponse = api_client()->post('/edge-gateways/install-token', [
        'department_id' => (int)$department['id'],
        'label' => 'Dock 7 Gateway',
    ], $session['headers']);

    $createResponse
        ->assertStatus(201)
        ->assertEnvelope()
        ->assertSuccess();

    $installToken = $createResponse->data();
    expect($installToken)
        ->toBeArray()
        ->toHaveKeys(['claim_token_id', 'token', 'install_command']);

    $statusResponse = api_client()->get(
        '/edge-gateways/install-token/' . (int)$installToken['claim_token_id'] . '/status',
        $session['headers']
    );

    $statusResponse
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    expect($statusResponse->data())
        ->toHaveKey('status', 'PENDING')
        ->toHaveKey('gateway_id', null);

    $verifyResponse = api_client()->get('/edge-agent/install-token/verify?token=' . urlencode((string)$installToken['token']));

    $verifyResponse
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    expect($verifyResponse->data())
        ->toHaveKey('valid', true)
        ->toHaveKey('claim_token_id', (int)$installToken['claim_token_id']);

    $runningStatus = api_client()->post('/edge-agent/install-token/status', [
        'token' => (string)$installToken['token'],
        'status' => 'RUNNING',
        'step' => 'BOOTSTRAP',
        'message' => 'Installer is downloading gateway artifacts.',
        'diagnostics' => [
            'download-1',
            'download-2',
            'download-3',
            'download-4',
            'download-5',
            'download-6',
            'download-7',
        ],
    ]);

    $runningStatus
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    $runningStatusDetail = api_client()->get(
        '/edge-gateways/install-token/' . (int)$installToken['claim_token_id'] . '/status',
        $session['headers']
    );

    $runningStatusDetail
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    expect($runningStatusDetail->data())
        ->toHaveKey('status', 'RUNNING')
        ->toHaveKey('step', 'BOOTSTRAP')
        ->and($runningStatusDetail->data()['diagnostics'] ?? [])
        ->toBeArray()
        ->toHaveCount(6);

    $claimResponse = api_client()->post('/edge-agent/claim', [
        'token' => (string)$installToken['token'],
        'hostname' => 'edge-operator-api',
        'installed_version' => 'php-agent-v1',
        'metadata' => [
            'agent_runtime' => 'compose-php',
        ],
    ]);

    $claimResponse
        ->assertStatus(201)
        ->assertEnvelope()
        ->assertSuccess();

    $claimedPayload = $claimResponse->data();
    $gatewayId = (int)($claimedPayload['gateway']['id'] ?? 0);

    expect($claimedPayload)
        ->toHaveKey('agent_token')
        ->and($gatewayId)
        ->toBeGreaterThan(0)
        ->and($claimedPayload['gateway']['status'] ?? null)
        ->toBe('ONLINE');

    $claimedStatus = api_client()->get(
        '/edge-gateways/install-token/' . (int)$installToken['claim_token_id'] . '/status',
        $session['headers']
    );

    $claimedStatus
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    expect($claimedStatus->data())
        ->toHaveKey('status', 'CLAIMED')
        ->toHaveKey('gateway_id', $gatewayId)
        ->toHaveKey('last_error', null);

    $listResponse = api_client()->get('/edge-gateways?department_id=' . (int)$department['id'], $session['headers']);

    $listResponse
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    expect(collect_gateway_ids_from_api_response($listResponse->data()))
        ->toContain($gatewayId);

    $detailResponse = api_client()->get('/edge-gateways/' . $gatewayId, $session['headers']);

    $detailResponse
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    expect($detailResponse->data())
        ->toHaveKey('id', $gatewayId)
        ->toHaveKey('department_id', (int)$department['id'])
        ->toHaveKey('status', 'ONLINE');
});

it('manages edge gateway metadata, bindings, operations, sessions, rotation, cutover, and deletion', function (): void {
    $department = api_fixtures()->createDepartment([
        'name' => 'Edge Operator Control Department',
    ]);
    $session = api_fixtures()->createEdgeOperatorSession((int)$department['id']);
    $gateway = api_fixtures()->createClaimedEdgeGateway([
        'department_id' => (int)$department['id'],
        'label' => 'Original Gateway Label',
    ]);
    api_fixtures()->createClaimedEdgeGateway([
        'department_id' => (int)$department['id'],
        'label' => 'Alternate Gateway Label',
        'is_primary' => 0,
    ]);

    $updateResponse = api_client()->put('/edge-gateways/' . (int)$gateway['id'], [
        'label' => 'Renamed Gateway',
        'is_primary' => false,
    ], $session['headers']);

    $updateResponse
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    expect($updateResponse->data())
        ->toHaveKey('label', 'Renamed Gateway')
        ->toHaveKey('is_primary', false);

    $bindingsResponse = api_client()->put('/edge-gateways/' . (int)$gateway['id'] . '/bindings', [
        'bindings' => [[
            'relay_id' => 'relay-main',
            'device_id' => 'device-main',
            'local_ip' => '10.0.0.18',
            'channel' => 0,
            'binding_source' => 'MANUAL',
        ]],
    ], $session['headers']);

    $bindingsResponse
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    expect($bindingsResponse->data()['bindings'] ?? [])
        ->toBeArray()
        ->toHaveCount(1)
        ->and(($bindingsResponse->data()['bindings'][0]['relay_id'] ?? null))
        ->toBe('relay-main');

    $operationResponse = api_client()->post('/edge-gateways/' . (int)$gateway['id'] . '/operations', [
        'type' => 'DISCOVERY',
        'request' => [
            'inventory' => edge_operator_test_inventory('operator'),
        ],
    ], $session['headers']);

    $operationResponse
        ->assertStatus(201)
        ->assertEnvelope()
        ->assertSuccess();

    $operationId = (int)($operationResponse->data()['operation']['id'] ?? 0);
    expect($operationId)->toBeGreaterThan(0);

    $operationsList = api_client()->get('/edge-gateways/' . (int)$gateway['id'] . '/operations', $session['headers']);

    $operationsList
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    expect(collect_gateway_ids_from_api_response($operationsList->data(), 'id'))
        ->toContain($operationId);

    $eventsResponse = api_client()->get(
        '/edge-gateways/' . (int)$gateway['id'] . '/operations/' . $operationId . '/events',
        $session['headers']
    );

    $eventsResponse
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    expect($eventsResponse->data())
        ->toBeArray()
        ->not->toBeEmpty()
        ->and($eventsResponse->data()[0]['code'] ?? null)
        ->toBe('OPERATION_QUEUED');

    $cancelResponse = api_client()->post(
        '/edge-gateways/' . (int)$gateway['id'] . '/operations/' . $operationId . '/cancel',
        [],
        $session['headers']
    );

    $cancelResponse
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    expect($cancelResponse->data()['operation']['status'] ?? null)
        ->toBe('CANCELLED');

    $streamSession = api_client()->post(
        '/edge-gateways/' . (int)$gateway['id'] . '/stream-session',
        ['scopes' => ['logs', 'tasks']],
        $session['headers']
    );

    $streamSession
        ->assertStatus(201)
        ->assertEnvelope()
        ->assertSuccess();

    expect($streamSession->data())
        ->toHaveKey('token')
        ->toHaveKey('ws_url');

    $brokerPresence = api_client()->post(
        '/edge-agent/internal/gateways/' . (int)$gateway['id'] . '/presence',
        [
            'status' => 'connected',
            'connection_id' => 'operator-shell-broker-1',
        ],
        edge_test_broker_headers()
    );

    $brokerPresence
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    $shellSession = api_client()->post(
        '/edge-gateways/' . (int)$gateway['id'] . '/shell-sessions',
        [
            'reason' => 'Operator smoke session',
            'cwd' => '/opt/truckwash-edge-agent',
            'cols' => 120,
            'rows' => 40,
        ],
        $session['headers']
    );

    $shellSession
        ->assertStatus(201)
        ->assertEnvelope()
        ->assertSuccess();

    expect($shellSession->data())
        ->toHaveKey('token')
        ->toHaveKey('session')
        ->and($shellSession->data()['session']['reason'] ?? null)
        ->toBe('Operator smoke session')
        ->and($shellSession->data()['diagnostics']['broker_presence']['connection_id'] ?? null)
        ->toBe('operator-shell-broker-1');

    $rotateResponse = api_client()->post(
        '/edge-gateways/' . (int)$gateway['id'] . '/rotate-credentials',
        [],
        $session['headers']
    );

    $rotateResponse
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    expect($rotateResponse->data())
        ->toHaveKey('gateway_id', (int)$gateway['id'])
        ->toHaveKey('agent_token')
        ->toHaveKey('config_json');

    $cutoverResponse = api_client()->post(
        '/departments/' . (int)$department['id'] . '/gateway-cutover',
        ['transport_mode' => 'gateway'],
        $session['headers']
    );

    $cutoverResponse
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    expect($cutoverResponse->data())
        ->toHaveKey('department_id', (int)$department['id'])
        ->toHaveKey('transport_mode', 'gateway');

    $deleteResponse = api_client()->delete('/edge-gateways/' . (int)$gateway['id'], null, $session['headers']);

    $deleteResponse
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    expect($deleteResponse->data())
        ->toHaveKey('deleted', true)
        ->toHaveKey('gateway_id', (int)$gateway['id']);
});

it('rejects operator edge routes when module permission or department access is missing', function (): void {
    $department = api_fixtures()->createDepartment([
        'name' => 'Edge Operator Access Department',
    ]);
    $allowed = api_fixtures()->createEdgeOperatorSession((int)$department['id']);
    $token = api_client()->post('/edge-gateways/install-token', [
        'department_id' => (int)$department['id'],
        'label' => 'Restricted Gateway',
    ], $allowed['headers']);

    $token
        ->assertStatus(201)
        ->assertEnvelope()
        ->assertSuccess();

    $missingModule = api_fixtures()->createUserSession([
        'department_access_' . (int)$department['id'],
    ]);

    $missingModuleResponse = api_client()->get(
        '/edge-gateways?department_id=' . (int)$department['id'],
        $missingModule['headers']
    );

    $missingModuleResponse
        ->assertStatus(403)
        ->assertEnvelope()
        ->assertSuccess(false)
        ->assertMissingPermissions(['modules_shelly_config']);

    $missingDepartment = api_fixtures()->createUserSession(['modules_shelly_config']);

    $missingDepartmentResponse = api_client()->get(
        '/edge-gateways/install-token/' . (int)$token->data()['claim_token_id'] . '/status',
        $missingDepartment['headers']
    );

    $missingDepartmentResponse
        ->assertStatus(403)
        ->assertEnvelope()
        ->assertSuccess(false)
        ->assertMissingPermissions(['department_access_' . (int)$department['id']]);
});

it('ignores and soft-deletes edge gateways whose department no longer exists', function (): void {
    $session = api_fixtures()->createUserSession(['modules_shelly_config']);
    $missingDepartmentId = 2147483000;

    $listOrphan = api_fixtures()->createClaimedEdgeGateway([
        'department_id' => $missingDepartmentId,
        'label' => 'Orphaned Fleet Gateway',
    ]);
    api_fixtures()->clearEdgeGatewayViewCache();

    $listResponse = api_client()->get('/edge-gateways', $session['headers']);

    $listResponse
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    expect(collect_gateway_ids_from_api_response($listResponse->data()))
        ->not->toContain((int)$listOrphan['id'])
        ->and(api_fixtures()->fetchRowById('edge_gateways', (int)$listOrphan['id'])['deleted_at'] ?? null)
        ->not->toBeNull();

    $workspaceMissingDepartmentId = $missingDepartmentId - 1;
    $workspaceOrphan = api_fixtures()->createClaimedEdgeGateway([
        'department_id' => $workspaceMissingDepartmentId,
        'label' => 'Orphaned Workspace Gateway',
    ]);
    api_fixtures()->clearEdgeGatewayViewCache();

    $redis = api_test_runtime()->redis();
    expect($redis)->not->toBeNull();

    $redis->set('departments', json_encode([[
        'id' => $workspaceMissingDepartmentId,
        'name' => 'Stale Department Cache',
        'description' => 'This department row is no longer in the database.',
        'order_priority' => 0,
        'visible' => 1,
    ]], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));

    try {
        $workspaceResponse = api_client()->get(
            '/modules/edge-gateways/workspace/departments',
            $session['headers']
        );
    } finally {
        $redis->del(['departments']);
    }

    $workspaceResponse
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    expect($workspaceResponse->data())
        ->toBeArray()
        ->and(collect_gateway_ids_from_api_response($workspaceResponse->data()))
        ->not->toContain((int)$workspaceOrphan['id'])
        ->and(api_fixtures()->fetchRowById('edge_gateways', (int)$workspaceOrphan['id'])['deleted_at'] ?? null)
        ->not->toBeNull();
});

function edge_operator_test_inventory(string $suffix): array
{
    return [[
        'device_id' => 'gateway-' . $suffix,
        'local_ip' => '10.20.30.40',
        'model' => 'TruckWash Edge Test',
        'channel_count' => 2,
        'online' => true,
        'capabilities' => [
            'local_discovery' => true,
            'relay_commands' => true,
        ],
        'metadata' => [
            'hostname' => 'edge-' . $suffix,
        ],
    ]];
}

/**
 * @param mixed $items
 * @return array<int, int>
 */
function collect_gateway_ids_from_api_response(mixed $items, string $key = 'id'): array
{
    if (!is_array($items)) {
        return [];
    }

    $ids = [];
    foreach ($items as $item) {
        if (is_array($item) && isset($item[$key]) && is_numeric($item[$key])) {
            $ids[] = (int)$item[$key];
        }
    }

    return $ids;
}
