<?php

declare(strict_types=1);

use classes\edge_gateway_manager;

usesApiSuite();

it('serves installer artifacts and recovers gateway runtime status after fresh heartbeats', function (): void {
    $department = api_fixtures()->createDepartment([
        'name' => 'Edge Agent Department',
    ]);
    $session = api_fixtures()->createEdgeOperatorSession((int)$department['id']);
    $installTokenResponse = api_client()->post('/edge-gateways/install-token', [
        'department_id' => (int)$department['id'],
        'label' => 'Edge Agent Install',
    ], $session['headers']);

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

    $installToken = $installTokenResponse->data();

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

    expect($installScript->status)->toBe(200)
        ->and($installScript->body)
        ->toContain('/edge-agent/install-token/status')
        ->toContain('agent.php')
        ->toContain((string)$installToken['token']);

    $artifact = api_client()->get('/edge-agent/artifacts/agent.php');
    expect($artifact->status)->toBe(200)
        ->and($artifact->body)
        ->toContain('<?php');

    $manifest = api_client()->get('/edge-agent/artifacts/manifest.json');
    expect($manifest->status)->toBe(200);
    $manifestPayload = json_decode($manifest->body, true);
    expect($manifestPayload)->toBeArray()
        ->and($manifestPayload['version'] ?? null)->toBe(edge_gateway_manager::DEFAULT_INSTALL_VERSION);
    $manifestArtifacts = array_column((array)($manifestPayload['artifacts'] ?? []), null, 'name');
    expect($manifestArtifacts)->toHaveKey('agent.php')
        ->and($manifestArtifacts['agent.php']['sha256'] ?? null)->toBe(hash('sha256', $artifact->body));

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

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

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

    expect($gatewayId)->toBeGreaterThan(0)
        ->and($agentToken)->not->toBe('');

    edge_agent_test_set_heartbeat_age($gatewayId, edge_gateway_manager::HEARTBEAT_DEGRADED_AFTER_SECONDS + 1);

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

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

    expect($degradedDetail->data())
        ->toHaveKey('status', 'DEGRADED');

    edge_agent_test_set_heartbeat_age($gatewayId, edge_gateway_manager::HEARTBEAT_OFFLINE_AFTER_SECONDS + 1);

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

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

    expect($offlineDetail->data())
        ->toHaveKey('status', 'OFFLINE');

    $heartbeatResponse = api_client()->post('/edge-agent/gateways/' . $gatewayId . '/heartbeat', [
        'agent_token' => $agentToken,
        'status' => 'ONLINE',
        'hostname' => 'edge-agent-api',
        'metadata' => [
            'agent_instance_id' => 'edge-agent-api-test',
            'broker_connected' => true,
            'broker_url' => 'wss://broker.example.test/edge-broker',
            'broker_last_connected_at' => '2026-04-08T10:05:00+00:00',
            'system_metrics' => [
                'cpu_percent' => 21,
                'memory_mb' => 128,
            ],
        ],
        'inventory' => edge_agent_test_inventory('heartbeat'),
    ]);

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

    expect($heartbeatResponse->data())
        ->toHaveKey('status', 'ONLINE')
        ->and($heartbeatResponse->data()['broker_url'] ?? null)
        ->toBeString()
        ->toContain('/edge-broker');

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

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

    expect($recoveredDetail->data())
        ->toHaveKey('status', 'ONLINE')
        ->and($recoveredDetail->data()['metadata']['system_metrics']['cpu_percent'] ?? null)
        ->toBe(21)
        ->and($recoveredDetail->data()['metadata']['broker_presence']['connected'] ?? null)
        ->toBeTrue()
        ->and($recoveredDetail->data()['channel_status']['broker']['connected'] ?? null)
        ->toBeTrue()
        ->and($recoveredDetail->data()['channel_status']['command']['preferred'] ?? null)
        ->toBe(edge_gateway_manager::DELIVERY_CHANNEL_BROKER);
});

it('polls operations and commands, submits results, and records broker presence for task pages', function (): void {
    $department = api_fixtures()->createDepartment([
        'name' => 'Edge Agent Operations Department',
    ]);
    $session = api_fixtures()->createEdgeOperatorSession((int)$department['id']);
    $gateway = api_fixtures()->createClaimedEdgeGateway([
        'department_id' => (int)$department['id'],
        'label' => 'Edge Agent Runtime',
    ]);

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

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

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

    $operationLease = api_client()->post('/edge-agent/gateways/' . (int)$gateway['id'] . '/operations/next', [
        'agent_token' => (string)$gateway['agent_token'],
        'wait_seconds' => 0,
        'agent_instance_id' => 'edge-agent-api-test',
    ]);

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

    expect($operationLease->data())
        ->toHaveKey('id', $operationId)
        ->toHaveKey('status', 'IN_PROGRESS');

    $eventResponse = api_client()->post(
        '/edge-agent/gateways/' . (int)$gateway['id'] . '/operations/' . $operationId . '/events',
        [
            'agent_token' => (string)$gateway['agent_token'],
            'level' => 'INFO',
            'code' => 'DISCOVERY_RUNNING',
            'message' => 'Discovery is running through the agent API.',
            'context' => [
                'progress' => 55,
                'label' => 'Discovery running',
            ],
        ]
    );

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

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

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

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

    $job = api_fixtures()->createEdgeCommandJob([
        'gateway_id' => (int)$gateway['id'],
        'command_type' => 'GET_RELAY_STATUS',
        'request' => [
            'relayId' => 'relay-main',
            'localIp' => '10.0.0.18',
        ],
    ]);

    $commandPoll = api_client()->post('/edge-agent/gateways/' . (int)$gateway['id'] . '/commands/poll', [
        'agent_token' => (string)$gateway['agent_token'],
        'wait_seconds' => 0,
    ]);

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

    expect($commandPoll->data())
        ->toHaveKey('id', (int)$job['id'])
        ->toHaveKey('command_type', 'GET_RELAY_STATUS');

    $commandResult = api_client()->post(
        '/edge-agent/gateways/' . (int)$gateway['id'] . '/commands/' . (int)$job['id'] . '/result',
        [
            'agent_token' => (string)$gateway['agent_token'],
            'ok' => true,
            'result' => [
                'relayId' => 'relay-main',
                'online' => true,
            ],
        ]
    );

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

    expect($commandResult->data())
        ->toHaveKey('acknowledged', true)
        ->and($commandResult->data()['job']['status'] ?? null)
        ->toBe('COMPLETED');

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

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

    expect($presenceResponse->data())
        ->toHaveKey('connected', true);

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

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

    expect($tasksPage->data()['operations'] ?? [])
        ->toBeArray()
        ->not->toBeEmpty()
        ->and($tasksPage->data()['recent_commands'] ?? [])
        ->toBeArray()
        ->not->toBeEmpty();

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

it('rejects missing and invalid edge agent tokens', function (): void {
    $department = api_fixtures()->createDepartment([
        'name' => 'Edge Agent Auth Department',
    ]);
    $gateway = api_fixtures()->createClaimedEdgeGateway([
        'department_id' => (int)$department['id'],
    ]);

    $missingToken = api_client()->get('/edge-agent/install-token/verify');

    $missingToken
        ->assertStatus(400)
        ->assertEnvelope()
        ->assertSuccess(false)
        ->assertMessage('Missing token');

    $missingAgentToken = api_client()->post('/edge-agent/gateways/' . (int)$gateway['id'] . '/heartbeat', [
        'status' => 'ONLINE',
    ]);

    $missingAgentToken
        ->assertStatus(401)
        ->assertEnvelope()
        ->assertSuccess(false)
        ->assertMessage('Missing edge gateway agent token');

    $invalidAgentToken = api_client()->post('/edge-agent/gateways/' . (int)$gateway['id'] . '/operations/next', [
        'agent_token' => 'invalid-token',
        'wait_seconds' => 0,
    ]);

    $invalidAgentToken
        ->assertStatus(401)
        ->assertEnvelope()
        ->assertSuccess(false)
        ->assertMessage('Invalid edge gateway token');
});

function edge_agent_test_set_heartbeat_age(int $gatewayId, int $secondsAgo): void
{
    $db = api_test_runtime()->db();
    $row = api_fixtures()->fetchRowById('edge_gateways', $gatewayId) ?? [];
    $referenceTimestamp = strtotime((string)($row['last_heartbeat_at'] ?? $row['updated_at'] ?? $row['created_at'] ?? ''));
    if ($referenceTimestamp === false || $referenceTimestamp <= 0) {
        $referenceTimestamp = time();
    }
    $timestamp = date('Y-m-d H:i:s', $referenceTimestamp - max(0, $secondsAgo));
    $escapedTimestamp = $db->real_escape_string($timestamp);
    $db->query("UPDATE edge_gateways SET last_heartbeat_at = '{$escapedTimestamp}', status = 'ONLINE' WHERE id = " . (int)$gatewayId);

    $redis = api_test_runtime()->redis();
    if ($redis !== null) {
        foreach ([
            'obj_prop:*:' . $gatewayId . ':status',
            'obj_prop:*:' . $gatewayId . ':last_heartbeat_at',
            'obj_prop:*:' . $gatewayId . ':updated_at',
        ] as $pattern) {
            $keys = $redis->keys($pattern);
            if (is_array($keys) && $keys !== []) {
                $redis->del($keys);
            }
        }
    }

    api_fixtures()->clearEdgeGatewayViewCache();
}

function edge_agent_test_inventory(string $suffix): array
{
    return [[
        'device_id' => 'agent-' . $suffix,
        'local_ip' => '10.30.40.50',
        'model' => 'TruckWash Edge Agent',
        'channel_count' => 1,
        'online' => true,
        'capabilities' => [
            'gateway_management_v2' => true,
        ],
        'metadata' => [
            'hostname' => 'edge-' . $suffix,
        ],
    ]];
}
