<?php

app_require('classes/edge_gateway_agent_artifact_locator.php');
app_require('classes/edge_gateway_install_service.php');
app_require('classes/edge_gateway_manager.php');

use classes\edge_gateway_agent_artifact_locator;
use classes\edge_gateway_install_service;
use classes\edge_gateway_manager;

class EdgeGatewayInstallServiceHarness extends edge_gateway_install_service
{
    public function __construct()
    {
    }
}

class EdgeGatewayManagerArtifactHarness extends edge_gateway_manager
{
    public function __construct()
    {
    }
}

function with_edge_gateway_artifact_env(array $values, callable $callback): void
{
    $keys = ['EDGE_AGENT_ARTIFACT_DIR'];
    $originals = [];
    foreach ($keys as $key) {
        $originals[$key] = getenv($key);
    }

    try {
        foreach ($keys as $key) {
            if (array_key_exists($key, $values) && $values[$key] !== null) {
                putenv($key . '=' . $values[$key]);
                continue;
            }
            putenv($key);
        }

        $callback();
    } finally {
        foreach ($originals as $key => $value) {
            if ($value === false) {
                putenv($key);
                continue;
            }
            putenv($key . '=' . $value);
        }
    }
}

function remove_edge_gateway_temp_path(string $path): void
{
    if (is_file($path)) {
        unlink($path);
        return;
    }

    if (is_dir($path)) {
        rmdir($path);
    }
}

it('resolves edge-agent artifacts from a supported runtime layout', function (): void {
    $path = edge_gateway_agent_artifact_locator::resolve('agent.php', app_path());
    $normalizedPath = str_replace('\\', '/', $path);

    expect($normalizedPath)->toEndWith('/resources/edge-gateway-agent/agent.php');
    expect(is_file($path))->toBeTrue();
});

it('prioritizes router-owned artifacts before implicit generated build output', function (): void {
    with_edge_gateway_artifact_env(['EDGE_AGENT_ARTIFACT_DIR' => null], function (): void {
        $candidatePaths = array_map(
            static fn(string $path): string => str_replace('\\', '/', $path),
            edge_gateway_agent_artifact_locator::candidatePaths(
                'agent.php',
                '/var/www/html',
                '/services/edge-agent/php-agent',
                '/opt/truckwash-edge-agent-artifacts'
            )
        );

        expect(array_slice($candidatePaths, 0, 5))->toBe([
            '/var/www/html/resources/edge-gateway-agent/agent.php',
            '/edge-agent/build/install/agent.php',
            '/services/edge-agent/build/install/agent.php',
            '/var/edge-agent/build/install/agent.php',
            '/var/www/edge-agent/build/install/agent.php',
        ]);
        expect($candidatePaths)->toContain('/services/edge-agent/php-agent/agent.php');
        expect($candidatePaths)->toContain('/opt/truckwash-edge-agent-artifacts/agent.php');
        expect($candidatePaths)->toContain('/var/edge-agent/php-agent/agent.php');
        expect($candidatePaths)->toContain('/var/www/edge-agent/php-agent/agent.php');
    });
});

it('prioritizes EDGE_AGENT_ARTIFACT_DIR before generated edge-agent build output', function (): void {
    with_edge_gateway_artifact_env(['EDGE_AGENT_ARTIFACT_DIR' => '/tmp/custom-edge-artifacts'], function (): void {
        $candidatePaths = array_map(
            static fn(string $path): string => str_replace('\\', '/', $path),
            edge_gateway_agent_artifact_locator::candidatePaths(
                'agent.php',
                '/var/www/html',
                '/services/edge-agent/php-agent',
                '/opt/truckwash-edge-agent-artifacts'
            )
        );

        expect(array_slice($candidatePaths, 0, 4))->toBe([
            '/tmp/custom-edge-artifacts/agent.php',
            '/var/www/html/resources/edge-gateway-agent/agent.php',
            '/edge-agent/build/install/agent.php',
            '/services/edge-agent/build/install/agent.php',
        ]);
        expect($candidatePaths)->toContain('/var/edge-agent/build/install/agent.php');
    });
});

it('serves a manifest with hashes for the resolved artifacts', function (): void {
    $service = new EdgeGatewayInstallServiceHarness();
    $manifest = json_decode($service->readArtifact('manifest.json'), true);

    expect($manifest)->toBeArray()
        ->and($manifest['version'] ?? null)->toBe(edge_gateway_manager::DEFAULT_INSTALL_VERSION);

    $artifacts = [];
    foreach ((array)($manifest['artifacts'] ?? []) as $artifact) {
        if (is_array($artifact)) {
            $artifacts[(string)($artifact['name'] ?? '')] = $artifact;
        }
    }

    foreach ([
        'agent.php',
        'lan-worker.php',
        'auto-updater.php',
        'docker-compose.gateway.yml',
        'Dockerfile.edge-agent',
        'Dockerfile.lan-worker',
        'Dockerfile.auto-updater',
        'gateway-launcher.sh',
        'truckwash-edge-gateway-stack.service',
        'truckwash-edge-agent.service',
    ] as $fileName) {
        $path = edge_gateway_agent_artifact_locator::resolve($fileName, app_path());
        expect($artifacts)->toHaveKey($fileName)
            ->and($artifacts[$fileName]['sha256'] ?? null)->toBe(hash_file('sha256', $path))
            ->and($artifacts[$fileName]['bytes'] ?? null)->toBe(filesize($path));
    }
});

it('reads install artifacts through the shared locator', function (): void {
    $service = new EdgeGatewayInstallServiceHarness();
    $contents = $service->readArtifact('truckwash-edge-gateway-stack.service');

    expect($contents)->toContain('ExecStart=/opt/truckwash-edge-agent/gateway-launcher.sh up');
});

it('normalizes install artifact line endings for shell-safe streaming', function (): void {
    $tempRoot = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'edge-gateway-crlf-' . bin2hex(random_bytes(6));
    $artifactPath = $tempRoot . DIRECTORY_SEPARATOR . 'gateway-launcher.sh';

    @mkdir($tempRoot, 0777, true);
    file_put_contents($artifactPath, "#!/usr/bin/env bash\r\nset -Eeuo pipefail\r\n");

    try {
        with_edge_gateway_artifact_env(['EDGE_AGENT_ARTIFACT_DIR' => $tempRoot], function (): void {
            $service = new EdgeGatewayInstallServiceHarness();
            $contents = $service->readArtifact('gateway-launcher.sh');

            expect($contents)->toBe("#!/usr/bin/env bash\nset -Eeuo pipefail\n");
            expect($contents)->not->toContain("\r");
        });
    } finally {
        remove_edge_gateway_temp_path($artifactPath);
        remove_edge_gateway_temp_path($tempRoot);
    }
});

it('builds update payloads with checksums from resolved artifact paths', function (): void {
    $manager = new EdgeGatewayManagerArtifactHarness();
    $payload = $manager->buildUpdateOperationRequest('2.0.0', 'stable');

    expect($payload['artifactSha256'])->toBe(hash_file('sha256', app_path('resources/edge-gateway-agent/agent.php')));
    expect($payload['serviceUnitSha256'])->toBe(hash_file('sha256', app_path('resources/edge-gateway-agent/truckwash-edge-agent.service')));
    expect($payload['stackServiceUnitSha256'])->toBe(hash_file('sha256', app_path('resources/edge-gateway-agent/truckwash-edge-gateway-stack.service')));
    expect($payload['composeFileSha256'])->toBe(hash_file('sha256', app_path('resources/edge-gateway-agent/docker-compose.gateway.yml')));
    expect($payload['launcherScriptSha256'])->toBe(hash_file('sha256', app_path('resources/edge-gateway-agent/gateway-launcher.sh')));
});

it('falls back to baked-in artifacts when the mount path is absent', function (): void {
    $tempRoot = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'edge-gateway-baked-' . bin2hex(random_bytes(6));
    $appPath = $tempRoot . DIRECTORY_SEPARATOR . 'services' . DIRECTORY_SEPARATOR . 'nginx' . DIRECTORY_SEPARATOR . 'app';
    $missingMountPath = $tempRoot . DIRECTORY_SEPARATOR . 'services' . DIRECTORY_SEPARATOR . 'edge-agent' . DIRECTORY_SEPARATOR . 'php-agent';
    $bakedPath = $tempRoot . DIRECTORY_SEPARATOR . 'baked-artifacts';
    $bakedAgentPath = $bakedPath . DIRECTORY_SEPARATOR . 'agent.php';

    @mkdir($appPath, 0777, true);
    @mkdir($bakedPath, 0777, true);
    file_put_contents($bakedAgentPath, "<?php\n// baked artifact\n");

    try {
        $resolvedPath = edge_gateway_agent_artifact_locator::resolve(
            'agent.php',
            $appPath,
            $missingMountPath,
            $bakedPath
        );

        expect(str_replace('\\', '/', $resolvedPath))->toBe(str_replace('\\', '/', $bakedAgentPath));
    } finally {
        remove_edge_gateway_temp_path($bakedAgentPath);
        foreach ([
            $bakedPath,
            $appPath,
            dirname($appPath),
            dirname(dirname($appPath)),
            dirname($missingMountPath),
            dirname(dirname($missingMountPath)),
            $tempRoot,
        ] as $directory) {
            remove_edge_gateway_temp_path($directory);
        }
    }
});

it('explains the legacy dist mount mismatch when php artifacts are unavailable', function (): void {
    $tempRoot = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'edge-gateway-artifacts-' . bin2hex(random_bytes(6));
    $appPath = $tempRoot . DIRECTORY_SEPARATOR . 'services' . DIRECTORY_SEPARATOR . 'nginx' . DIRECTORY_SEPARATOR . 'app';
    $legacyDistPath = $tempRoot . DIRECTORY_SEPARATOR . 'services' . DIRECTORY_SEPARATOR . 'edge-agent' . DIRECTORY_SEPARATOR . 'dist';

    @mkdir($appPath, 0777, true);
    @mkdir($legacyDistPath, 0777, true);
    file_put_contents($legacyDistPath . DIRECTORY_SEPARATOR . 'agent.mjs', '// legacy node artifact');

    try {
        edge_gateway_agent_artifact_locator::resolve('agent.php', $appPath);
        test()->fail('Expected artifact resolution to fail when php-agent artifacts are missing.');
    } catch (Exception $exception) {
        expect($exception->getMessage())->toContain('Missing edge agent artifact: agent.php.');
        expect($exception->getMessage())->toContain('Deploy the router-owned artifacts under');
        expect($exception->getMessage())->toContain('Legacy Node dist artifact found at');
        expect($exception->getMessage())->toContain('Remove /services/edge-agent/dist and deploy the router resources instead of depending on the legacy mount.');
    } finally {
        remove_edge_gateway_temp_path($legacyDistPath . DIRECTORY_SEPARATOR . 'agent.mjs');
        foreach ([
            $legacyDistPath,
            dirname($legacyDistPath),
            $appPath,
            dirname($appPath),
            dirname(dirname($appPath)),
            $tempRoot,
        ] as $directory) {
            remove_edge_gateway_temp_path($directory);
        }
    }
});
