<?php

declare(strict_types=1);

usesApiSuite();

it('validates broker sessions and ingests presence, telemetry, logs, and shell lifecycle data', function (): void {
    $department = api_fixtures()->createDepartment([
        'name' => 'Edge Broker Department',
    ]);
    $session = api_fixtures()->createEdgeOperatorSession((int)$department['id']);
    $gateway = api_fixtures()->createClaimedEdgeGateway([
        'department_id' => (int)$department['id'],
        'label' => 'Broker Gateway',
    ]);

    $validateGateway = api_client()->post(
        '/edge-agent/internal/gateways/' . (int)$gateway['id'] . '/validate',
        ['token' => (string)$gateway['agent_token']],
        edge_test_broker_headers()
    );

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

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

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

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

    expect($presence->data())
        ->toHaveKey('connected', true)
        ->toHaveKey('connection_id', 'broker-presence-1');

    $telemetry = api_client()->post(
        '/edge-agent/internal/gateways/' . (int)$gateway['id'] . '/telemetry',
        [
            'status' => 'ONLINE',
            'metadata' => [
                'system_metrics' => [
                    'cpu_load' => 0.42,
                    'memory_mb' => 512,
                ],
            ],
            'inventory' => edge_broker_test_inventory('telemetry'),
        ],
        edge_test_broker_headers()
    );

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

    expect($telemetry->data()['metadata']['system_metrics']['cpu_load'] ?? null)
        ->toBe(0.42);

    $logEntry = api_client()->post(
        '/edge-agent/internal/gateways/' . (int)$gateway['id'] . '/logs',
        [
            'level' => 'INFO',
            'stream' => 'agent',
            'source' => 'BROKER',
            'message' => 'Broker forwarded a live gateway log.',
            'context' => [
                'source' => 'broker-test',
            ],
        ],
        edge_test_broker_headers()
    );

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

    expect($logEntry->data())
        ->toHaveKey('message', 'Broker forwarded a live gateway log.');

    $relayLogEntry = api_client()->post(
        '/edge-agent/internal/gateways/' . (int)$gateway['id'] . '/logs',
        [
            'level' => 'INFO',
            'stream' => 'relay',
            'source' => 'RELAY_DISPATCH',
            'message' => 'MACHINE ON Roskilde Maskine handled by local via BROKER_FAST_PATH',
            'context' => [
                'module' => 'selfserve',
                'module_responsible' => 'selfserve',
                'reason' => 'Broker relayed machine start',
                'handler' => 'local',
                'delivery_channel' => 'BROKER_FAST_PATH',
                'relay_id' => 'M-7',
                'relay_name' => 'Roskilde Maskine',
                'relay_role' => 'MACHINE',
                'description' => 'MACHINE ON Roskilde Maskine handled by local via BROKER_FAST_PATH',
                'associated' => [
                    'admin_user_id' => 77,
                    'customer_number' => 700123,
                ],
                'signal' => [
                    'command_type' => 'SET_RELAY_STATE',
                    'request' => [
                        'relayId' => 'M-7',
                        'on' => true,
                    ],
                ],
                'response' => [
                    'online' => true,
                    'on' => true,
                ],
            ],
        ],
        edge_test_broker_headers()
    );

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

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

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

    $streamValidation = api_client()->post(
        '/edge-agent/internal/browser-streams/validate',
        ['token' => (string)$streamSession->data()['token']],
        edge_test_broker_headers()
    );

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

    expect($streamValidation->data())
        ->toHaveKey('session_type', 'gateway-stream')
        ->toHaveKey('gateway_id', (int)$gateway['id']);

    api_client()->post(
        '/edge-agent/internal/gateways/' . (int)$gateway['id'] . '/presence',
        [
            'status' => 'connected',
            'connection_id' => 'broker-presence-1',
            'metadata' => [
                'transport' => 'ws',
                'refreshed_for' => 'shell-session',
            ],
        ],
        edge_test_broker_headers()
    )
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    $shellSession = api_client()->post(
        '/edge-gateways/' . (int)$gateway['id'] . '/shell-sessions',
        ['reason' => 'Broker shell validation'],
        $session['headers']
    );

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

    expect($shellSession->data()['diagnostics'] ?? [])
        ->toHaveKey('ready', true)
        ->toHaveKey('reason_code', 'READY')
        ->and($shellSession->data()['diagnostics']['broker_presence']['connection_id'] ?? null)
        ->toBe('broker-presence-1');

    $shellToken = (string)$shellSession->data()['token'];

    $validateShell = api_client()->post(
        '/edge-agent/internal/shell-sessions/validate',
        ['token' => $shellToken],
        edge_test_broker_headers()
    );

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

    expect($validateShell->data())
        ->toHaveKey('status', 'PENDING');

    $openedShell = api_client()->post(
        '/edge-agent/internal/shell-sessions/opened',
        [
            'token' => $shellToken,
            'connection_id' => 'shell-connection-1',
        ],
        edge_test_broker_headers()
    );

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

    expect($openedShell->data())
        ->toHaveKey('status', 'OPEN')
        ->toHaveKey('connection_id', 'shell-connection-1');

    $closedShell = api_client()->post(
        '/edge-agent/internal/shell-sessions/close',
        [
            'token' => $shellToken,
            'transcript' => "edge-broker-shell\n",
            'reason' => 'agent_exit',
            'message' => 'Shell exited cleanly.',
            'code' => 0,
            'stage' => 'shell_active',
            'ws_url' => 'wss://broker.example.test/ws/browser-shell?token=raw-shell-token',
        ],
        edge_test_broker_headers()
    );

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

    expect($closedShell->data())
        ->toHaveKey('status', 'COMPLETED')
        ->and($closedShell->data()['transcript'] ?? null)
        ->toBe("edge-broker-shell\n")
        ->and($closedShell->data()['metadata']['close_reason'] ?? null)
        ->toBe('agent_exit')
        ->and($closedShell->data()['metadata']['close_message'] ?? null)
        ->toBe('Shell exited cleanly.')
        ->and($closedShell->data()['metadata']['close_code'] ?? null)
        ->toBe(0)
        ->and($closedShell->data()['metadata']['close_stage'] ?? null)
        ->toBe('shell_active')
        ->and($closedShell->data()['metadata']['close_diagnostics']['ws_url'] ?? null)
        ->toBe('wss://broker.example.test/ws/browser-shell?token=***');

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

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

    expect(collect_gateway_messages($logsPage->data()['log_entries'] ?? []))
        ->toContain('Broker forwarded a live gateway log.');
    expect(collect_gateway_messages($logsPage->data()['relay_logs'] ?? []))
        ->toContain('MACHINE ON Roskilde Maskine handled by local via BROKER_FAST_PATH');
    expect($logsPage->data()['relay_logs'][0]['context']['associated']['customer_number'] ?? null)
        ->toBe(700123)
        ->and($logsPage->data()['relay_logs'][0]['context']['module_responsible'] ?? null)
        ->toBe('selfserve')
        ->and($logsPage->data()['relay_logs'][0]['context']['relay_name'] ?? null)
        ->toBe('Roskilde Maskine')
        ->and($logsPage->data()['relay_logs'][0]['context']['relay_role'] ?? null)
        ->toBe('MACHINE')
        ->and($logsPage->data()['relay_logs'][0]['context']['reason'] ?? null)
        ->toBe('Broker relayed machine start');
    expect(collect_gateway_messages($logsPage->data()['timeline'] ?? []))
        ->toContain('GATEWAY_SHELL_SESSION_OPENED')
        ->toContain('GATEWAY_SHELL_SESSION_CLOSED')
        ->toContain('MACHINE ON Roskilde Maskine handled by local via BROKER_FAST_PATH');
    expect($logsPage->data()['shell_sessions'][0]['transcript'] ?? null)
        ->toBe("edge-broker-shell\n");

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

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

    expect($statisticsPage->data())
        ->toHaveKey('system_metrics')
        ->and($statisticsPage->data()['system_metrics']['cpu_load'] ?? null)
        ->toBe(0.42);
});

it('rejects shell session creation while broker presence is unavailable', function (): void {
    $department = api_fixtures()->createDepartment([
        'name' => 'Edge Broker Shell Readiness Department',
    ]);
    $session = api_fixtures()->createEdgeOperatorSession((int)$department['id']);
    $gateway = api_fixtures()->createClaimedEdgeGateway([
        'department_id' => (int)$department['id'],
        'label' => 'Disconnected Broker Gateway',
    ]);

    $response = api_client()->post(
        '/edge-gateways/' . (int)$gateway['id'] . '/shell-sessions',
        ['reason' => 'Should fail without broker presence'],
        $session['headers']
    );

    $response
        ->assertStatus(409)
        ->assertEnvelope()
        ->assertSuccess(false);

    expect($response->data())
        ->toHaveKey('error_code', 'BROKER_DISCONNECTED')
        ->and($response->data()['diagnostics']['ready'] ?? true)
        ->toBeFalse()
        ->and($response->data()['diagnostics']['broker_presence']['connected'] ?? true)
        ->toBeFalse();
});

it('builds broker backlog and completes gateway operations through broker endpoints', function (): void {
    $department = api_fixtures()->createDepartment([
        'name' => 'Edge Broker Backlog Department',
    ]);
    $session = api_fixtures()->createEdgeOperatorSession((int)$department['id']);
    $gateway = api_fixtures()->createClaimedEdgeGateway([
        'department_id' => (int)$department['id'],
        'label' => 'Broker Backlog Gateway',
    ]);

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

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

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

    $backlog = api_client()->post(
        '/edge-agent/internal/gateways/' . (int)$gateway['id'] . '/backlog',
        ['agent_instance_id' => 'broker-agent-1'],
        edge_test_broker_headers()
    );

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

    expect($backlog->data()['dispatch'] ?? [])
        ->toBeArray()
        ->not->toBeEmpty()
        ->and($backlog->data()['dispatch'][0]['type'] ?? null)
        ->toBe('TASK_DISPATCH')
        ->and((int)($backlog->data()['dispatch'][0]['operation']['id'] ?? 0))
        ->toBe($operationId);

    $event = api_client()->post(
        '/edge-agent/internal/gateways/' . (int)$gateway['id'] . '/operations/' . $operationId . '/events',
        [
            'level' => 'INFO',
            'code' => 'BROKER_EXECUTING',
            'message' => 'Broker is executing the operation.',
            'context' => [
                'progress' => 50,
            ],
        ],
        edge_test_broker_headers()
    );

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

    $complete = api_client()->post(
        '/edge-agent/internal/gateways/' . (int)$gateway['id'] . '/operations/' . $operationId . '/complete',
        [
            'ok' => true,
            'result' => [
                'inventory' => edge_broker_test_inventory('completed'),
            ],
        ],
        edge_test_broker_headers()
    );

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

    expect($complete->data())
        ->toHaveKey('status', 'COMPLETED');

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

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

    expect(($operations->data()[0]['status'] ?? null))
        ->toBe('COMPLETED');

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

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

    expect(collect_gateway_messages($events->data()))
        ->toContain('Broker is executing the operation.')
        ->toContain('Operation completed successfully');

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

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

    expect($tasksPage->data()['recent_operations_summary']['completed'] ?? 0)
        ->toBeGreaterThanOrEqual(1);
});

it('rejects invalid edge broker shared secrets', function (): void {
    $department = api_fixtures()->createDepartment([
        'name' => 'Edge Broker Forbidden Department',
    ]);
    $gateway = api_fixtures()->createClaimedEdgeGateway([
        'department_id' => (int)$department['id'],
    ]);

    $response = api_client()->post(
        '/edge-agent/internal/gateways/' . (int)$gateway['id'] . '/presence',
        ['status' => 'connected'],
        ['X-Edge-Broker-Secret' => 'wrong-secret']
    );

    $response
        ->assertStatus(403)
        ->assertEnvelope()
        ->assertSuccess(false)
        ->assertMessage('Invalid edge broker secret');
});

function edge_broker_test_inventory(string $suffix): array
{
    return [[
        'device_id' => 'broker-' . $suffix,
        'local_ip' => '10.40.50.60',
        'model' => 'TruckWash Edge Broker',
        'channel_count' => 1,
        'online' => true,
        'capabilities' => [
            'relay_commands' => true,
        ],
        'metadata' => [
            'hostname' => 'broker-' . $suffix,
        ],
    ]];
}

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

    $messages = [];
    foreach ($items as $item) {
        if (is_array($item) && isset($item['message']) && is_string($item['message'])) {
            $messages[] = $item['message'];
        }
    }

    return $messages;
}
