<?php

app_require('classes/edge_gateway_manager.php');

use classes\edge_gateway_manager;

function edge_gateway_heartbeat_test_now(string $value): int
{
    $timestamp = edge_gateway_manager::parseApplicationDateTime($value);

    expect($timestamp)->not->toBeNull();

    return $timestamp;
}

it('derives effective gateway status from heartbeat freshness', function (
    string $reportedStatus,
    ?string $lastHeartbeatAt,
    string $currentTime,
    string $expectedStatus
): void {
    $now = edge_gateway_heartbeat_test_now($currentTime);

    $effectiveStatus = edge_gateway_manager::resolveGatewayStatus(
        $reportedStatus,
        $lastHeartbeatAt,
        $now
    );

    expect($effectiveStatus)->toBe($expectedStatus);
})->with([
    'recent heartbeat stays online' => [
        edge_gateway_manager::STATUS_ONLINE,
        '2026-04-08 10:00:00',
        '2026-04-08 10:00:59',
        edge_gateway_manager::STATUS_ONLINE,
    ],
    'late heartbeat degrades after one minute' => [
        edge_gateway_manager::STATUS_ONLINE,
        '2026-04-08 10:00:00',
        '2026-04-08 10:01:00',
        edge_gateway_manager::STATUS_DEGRADED,
    ],
    'stale heartbeat goes offline after five minutes' => [
        edge_gateway_manager::STATUS_ONLINE,
        '2026-04-08 10:00:00',
        '2026-04-08 10:05:00',
        edge_gateway_manager::STATUS_OFFLINE,
    ],
    'explicit offline reports stay offline even when heartbeat is fresh' => [
        edge_gateway_manager::STATUS_OFFLINE,
        '2026-04-08 10:04:55',
        '2026-04-08 10:05:00',
        edge_gateway_manager::STATUS_OFFLINE,
    ],
    'missing heartbeat is treated as offline' => [
        edge_gateway_manager::STATUS_ONLINE,
        null,
        '2026-04-08 10:05:00',
        edge_gateway_manager::STATUS_OFFLINE,
    ],
]);

it('marks ready discovery as stale when the gateway heartbeat has expired', function (): void {
    $gateway = edge_gateway_manager::deriveGatewayRuntimeState([
        'status' => edge_gateway_manager::STATUS_ONLINE,
        'last_heartbeat_at' => '2026-04-08 10:00:00',
        'discovery_status' => 'READY',
    ], edge_gateway_heartbeat_test_now('2026-04-08 21:00:00'));

    expect($gateway['status'])->toBe(edge_gateway_manager::STATUS_OFFLINE);
    expect($gateway['discovery_status'])->toBe('STALE');
});

it('only trusts broker presence while the broker heartbeat is fresh', function (): void {
    $recent = edge_gateway_manager::deriveGatewayRuntimeState([
        'status' => edge_gateway_manager::STATUS_ONLINE,
        'last_heartbeat_at' => '2026-04-08 10:04:50',
        'metadata' => [
            'broker_presence' => [
                'connected' => true,
                'last_seen_at' => '2026-04-08 10:04:30',
            ],
        ],
    ], edge_gateway_heartbeat_test_now('2026-04-08 10:05:00'));

    expect($recent['channel_status']['broker']['connected'])->toBeTrue();
    expect($recent['channel_status']['broker']['state'])->toBe(edge_gateway_manager::STATUS_ONLINE);
    expect($recent['channel_status']['command']['preferred'])->toBe(edge_gateway_manager::DELIVERY_CHANNEL_BROKER);

    $stale = edge_gateway_manager::deriveGatewayRuntimeState([
        'status' => edge_gateway_manager::STATUS_ONLINE,
        'last_heartbeat_at' => '2026-04-08 10:04:50',
        'metadata' => [
            'broker_presence' => [
                'connected' => true,
                'last_seen_at' => '2026-04-08 10:03:00',
            ],
        ],
    ], edge_gateway_heartbeat_test_now('2026-04-08 10:05:00'));

    expect($stale['channel_status']['broker']['connected'])->toBeFalse();
    expect($stale['channel_status']['broker']['state'])->toBe(edge_gateway_manager::STATUS_OFFLINE);
    expect($stale['channel_status']['command']['preferred'])->toBe(edge_gateway_manager::DELIVERY_CHANNEL_API);
});

it('merges incoming heartbeat metadata with existing gateway metadata', function (): void {
    $source = file_get_contents(app_path('classes/edge_gateway_manager.php'));

    expect($source)->toContain("\$existingMetadata = (array)(\$gateway->metadata_json->value() ?? []);");
    expect($source)->toContain("\$payloadMetadata = (array)(\$payload['metadata'] ?? []);");
    expect($source)->toContain('$metadata = $this->mergeHeartbeatBrokerPresence($gatewayId, $existingMetadata, $payloadMetadata);');
    expect($source)->toContain('private function mergeHeartbeatBrokerPresence');
    expect($source)->toContain('$gateway->metadata_json->set($metadata);');
});

it('refreshes broker presence from broker telemetry heartbeats', function (): void {
    $source = file_get_contents(app_path('classes/edge_gateway_manager.php'));

    expect($source)->toContain('public function recordTelemetryFromBroker');
    expect($source)->toContain("'broker_connection_id'");
    expect($source)->toContain('$this->writeBrokerPresence($gatewayId, $presence);');
    expect($source)->toContain('private static function isBrokerPresenceConnected');
});

it('derives relay fallback and transport health details without shell or update runtime state', function (): void {
    $gateway = edge_gateway_manager::deriveGatewayRuntimeState([
        'status' => edge_gateway_manager::STATUS_ONLINE,
        'last_heartbeat_at' => '2026-04-08 10:04:30',
        'department_transport_mode' => edge_gateway_manager::TRANSPORT_MODE_GATEWAY,
        'metadata' => [
            'last_sync_at' => '2026-04-08 10:04:20',
            'broker_presence' => [
                'connected' => false,
                'last_seen_at' => '2026-04-08 10:03:00',
                'last_error' => 'broker timeout',
            ],
            'control_plane_status' => [
                'last_successful_sync_at' => '2026-04-08 10:04:10',
                'last_transport_failure_at' => '2026-04-08 10:04:12',
                'last_transport_error' => 'POST https://api.truckwash.io/edge-agent/gateways/17/heartbeat returned HTTP 502',
            ],
        ],
        'operational_snapshot' => [
            'command_backlog' => 2,
            'operation_backlog' => 1,
        ],
        'active_operation' => [
            'id' => 91,
            'type' => 'DISCOVERY',
            'status' => 'IN_PROGRESS',
            'started_at' => '2026-04-08 09:30:00',
        ],
        'bindings' => [
            [
                'id' => 1,
                'relay_id' => 'M-7',
                'device_id' => 'shelly-plus-01',
                'fallback_mode' => edge_gateway_manager::RELAY_FALLBACK_PREFER_LOCAL,
            ],
            [
                'id' => 2,
                'relay_id' => 'M-7-CANARY',
                'device_id' => 'missing-device',
                'fallback_mode' => edge_gateway_manager::RELAY_FALLBACK_LOCAL_ONLY,
            ],
        ],
        'inventory' => [
            [
                'device_id' => 'shelly-plus-01',
                'online' => true,
                'last_seen_at' => '2026-04-08 09:55:00',
            ],
        ],
        'recent_commands' => [
            [
                'status' => 'COMPLETED',
                'command_type' => 'DISCOVER_SHELLY',
                'completed_at' => '2026-04-08 10:02:00',
            ],
        ],
    ], edge_gateway_heartbeat_test_now('2026-04-08 10:05:00'));

    expect($gateway['channel_status']['command']['active'])->toBe(edge_gateway_manager::DELIVERY_CHANNEL_API);
    expect($gateway['channel_status']['broker']['state'])->toBe(edge_gateway_manager::STATUS_OFFLINE);
    expect($gateway['channel_status'])->not->toHaveKey('shell');
    expect($gateway['relay_health'][0]['execution_path'])->toBe('cloud');
    expect($gateway['relay_health'][0]['reason'])->toBe('device_stale');
    expect($gateway['relay_health'][0]['recommended_action'])->toBe('retry_discovery');
    expect($gateway['relay_health'][1]['execution_path'])->toBe('local');
    expect($gateway['relay_health'][1]['reason'])->toBe('device_missing');
    expect($gateway['fallback_summary']['cloud_relays'])->toBe(1);
    expect($gateway['fallback_summary']['local_only_relays'])->toBe(1);
    expect($gateway['transport_health']['status'])->toBe(edge_gateway_manager::STATUS_DEGRADED);
    expect($gateway['transport_health']['recommended_action'])->toBe('retry_discovery');
    expect($gateway['transport_health']['last_successful_sync_at'])->toBe('2026-04-08 10:04:30');
    expect($gateway['transport_health']['last_transport_failure_at'])->toBe('2026-04-08 10:04:12');
    expect($gateway['transport_health']['last_transport_error'])->toContain('returned HTTP 502');
    expect($gateway['last_sync_at'])->toBe('2026-04-08 10:04:30');
    expect($gateway['last_successful_discovery_at'])->toBe('2026-04-08 10:02:00');
    expect($gateway['diagnostics'])->not->toBeEmpty();
    expect($gateway['error_state']['code'])->toBe('EDGE_GATEWAY_OPERATION_TIMEOUT');
    expect($gateway['backlog_depth'])->toBe([
        'commands' => 2,
        'operations' => 1,
    ]);
});
