<?php

app_require('classes/coolify_api_client.php');
app_require('classes/coolify_manager.php');

use classes\coolify_api_client;
use classes\coolify_manager;

class CoolifyManagerHetznerTargetSetFake
{
    public array $targets;

    public function __construct(array $targets)
    {
        $this->targets = array_values($targets);
    }

    public function getLoadBalancer(int|string $id): array
    {
        return [
            'id' => $id,
            'targets' => array_map(
                static fn(string $ip): array => ['type' => 'ip', 'ip' => ['ip' => $ip]],
                $this->targets
            ),
            'services' => [],
        ];
    }

    public function addIpTarget(int|string $loadBalancerId, string $ip): array
    {
        if (!in_array($ip, $this->targets, true)) {
            $this->targets[] = $ip;
        }
        return [];
    }

    public function removeIpTarget(int|string $loadBalancerId, string $ip): array
    {
        $this->targets = array_values(array_filter($this->targets, static fn(string $target): bool => $target !== $ip));
        return [];
    }

    public function addService(int|string $loadBalancerId, string $protocol, int $listenPort, int $destinationPort, array $options = []): array
    {
        return [];
    }

    public function updateService(int|string $loadBalancerId, string $protocol, int $listenPort, int $destinationPort, array $options = []): array
    {
        return [];
    }
}

function coolifyManagerTestLoadBalancerService(string $protocol, int $listenPort, int $destinationPort): array
{
    return [
        'protocol' => $protocol,
        'listen_port' => $listenPort,
        'destination_port' => $destinationPort,
        'proxyprotocol' => false,
        'health_check' => [
            'protocol' => 'tcp',
            'port' => $listenPort,
            'interval' => 15,
            'timeout' => 10,
            'retries' => 3,
        ],
    ];
}

it('normalizes Coolify API base URLs to the v1 API root', function (): void {
    expect(coolify_api_client::normalizeBaseUrl('https://coolify.example.com'))->toBe('https://coolify.example.com/api/v1');
    expect(coolify_api_client::normalizeBaseUrl('https://coolify.example.com/api/v1'))->toBe('https://coolify.example.com/api/v1');
    expect(coolify_api_client::normalizeBaseUrl(' https://coolify.example.com/ '))->toBe('https://coolify.example.com/api/v1');
});

it('parses generated env files for Coolify service env bulk updates', function (): void {
    $env = implode("\n", [
        '# generated',
        'MARIADB_ROOT_PASSWORD=root-secret',
        'MARIADB_PASSWORD=app-secret',
        '',
        'REDIS_PRIMARY_USERNAME=',
    ]);

    expect(coolify_manager::parseEnvFile($env))->toBe([
        'MARIADB_ROOT_PASSWORD' => 'root-secret',
        'MARIADB_PASSWORD' => 'app-secret',
        'REDIS_PRIMARY_USERNAME' => '',
    ]);
});

it('prefers public Coolify server hosts over Docker-local addresses', function (): void {
    expect(coolify_manager::publicServerHostFromCoolifyServer([
        'ip' => 'host.docker.internal',
        'public_ip' => '94.130.142.41',
        'name' => 'node3.truckwash.io',
    ]))->toBe('94.130.142.41');

    expect(coolify_manager::publicServerHostFromCoolifyServer([
        'ip' => 'host.docker.internal',
        'name' => 'node3.truckwash.io',
    ]))->toBe('node3.truckwash.io');

    expect(coolify_manager::publicServerHostFromCoolifyServer([
        'ip' => 'host.docker.internal',
        'name' => 'node3.truckwash.io',
    ], null, false))->toBeNull();

    expect(coolify_manager::publicServerHostFromCoolifyServer([
        'ip' => '10.0.0.10',
        'name' => 'Production Server',
    ]))->toBe('10.0.0.10');

    expect(coolify_manager::publicServerHostFromCoolifyServer([
        'ip' => 'host.docker.internal',
        'name' => 'Production Server',
    ]))->toBeNull();

    expect(coolify_manager::publicDnsServerNameFromCoolifyServer([
        'ip' => 'host.docker.internal',
        'name' => 'node3.truckwash.io',
    ]))->toBe('node3.truckwash.io');

    expect(coolify_manager::publicDnsServerNameFromCoolifyServer([
        'ip' => 'host.docker.internal',
        'name' => 'Production Server',
    ]))->toBeNull();
});

it('blocks planned downtime operations against active replication primaries', function (): void {
    expect(coolify_manager::blocksPrimaryMutation(['role' => 'primary'], 'deploy'))->toBeTrue();
    expect(coolify_manager::blocksPrimaryMutation(['role' => 'primary'], 'restart'))->toBeTrue();
    expect(coolify_manager::blocksPrimaryMutation(['role' => 'replica'], 'restart'))->toBeFalse();
    expect(coolify_manager::blocksPrimaryMutation(['role' => 'primary'], 'failover'))->toBeFalse();
});

it('allows failed Coolify replica targets to be removed after the service disappears', function (): void {
    expect(coolify_manager::targetAllowsReplicaRemoval([
        'role' => 'replica',
        'deployment_status' => 'reconcile_failed',
        'last_reconcile_status' => 'reconcile_failed',
    ]))->toBeTrue();

    expect(coolify_manager::targetAllowsReplicaRemoval([
        'role' => 'replica',
        'deployment_status' => 'deploying',
        'last_reconcile_json' => json_encode(['message' => 'Coolify API request failed: HTTP 404']),
    ]))->toBeTrue();

    expect(coolify_manager::targetAllowsReplicaRemoval([
        'role' => 'replica',
        'deployment_status' => 'provisioned',
        'last_reconcile_status' => 'ok',
    ]))->toBeFalse();

    expect(coolify_manager::targetAllowsReplicaRemoval([
        'role' => 'primary',
        'deployment_status' => 'reconcile_failed',
    ]))->toBeFalse();
});

it('retries Coolify maintenance while linked replication provisioning is still incomplete', function (): void {
    $method = new ReflectionMethod(coolify_manager::class, 'replicationHostStillNeedsProvisioning');

    expect($method->invoke(null, [
        'role' => 'replica',
        'status' => 'provisioning',
        'last_status_json' => json_encode([
            'status' => 'provisioning',
            'replication_percent' => 99.9,
            'blockers' => ['MinIO replica has not caught up.'],
        ]),
    ]))->toBeTrue();

    expect($method->invoke(null, [
        'role' => 'replica',
        'status' => 'ok',
        'last_status_json' => json_encode([
            'status' => 'ok',
            'replication_percent' => 100,
            'blockers' => [],
        ]),
    ]))->toBeFalse();
});

it('plans Hetzner load balancer target and service drift without mutating state', function (): void {
    $manager = new coolify_manager();
    $method = new ReflectionMethod(coolify_manager::class, 'planLoadBalancerReconcile');

    $plan = $method->invoke($manager, [
        'targets' => [
            ['type' => 'ip', 'ip' => ['ip' => '94.130.142.41']],
        ],
        'services' => [
            coolifyManagerTestLoadBalancerService('http', 80, 80),
        ],
    ], [
        ['hostname' => 'node1.truckwash.io', 'target_ip' => '94.130.142.41', 'enabled' => true],
        ['hostname' => 'node2.truckwash.io', 'target_ip' => '65.21.214.30', 'enabled' => true],
    ]);

    $actionTypes = array_map(static fn(array $action): string => (string)$action['type'], $plan['actions']);

    expect($plan['has_drift'])->toBeTrue()
        ->and($plan['missing_targets'])->toContain('65.21.214.30')
        ->and($actionTypes)->toContain('add_target')
        ->and($actionTypes)->toContain('add_service')
        ->and($plan['missing_services'][0])->toMatchArray([
            'protocol' => 'tcp',
            'listen_port' => 443,
            'destination_port' => 443,
        ]);
});

it('plans Hetzner load balancer service health check drift updates', function (): void {
    $manager = new coolify_manager();
    $method = new ReflectionMethod(coolify_manager::class, 'planLoadBalancerReconcile');

    $plan = $method->invoke($manager, [
        'targets' => [
            ['type' => 'ip', 'ip' => ['ip' => '94.130.142.41']],
        ],
        'services' => [
            [
                'protocol' => 'http',
                'listen_port' => 80,
                'destination_port' => 80,
                'proxyprotocol' => false,
                'health_check' => [
                    'protocol' => 'http',
                    'port' => 80,
                    'interval' => 15,
                    'timeout' => 10,
                    'retries' => 3,
                    'http' => [
                        'domain' => '',
                        'path' => '/',
                        'response' => '',
                        'status_codes' => ['2??', '3??'],
                        'tls' => false,
                    ],
                ],
            ],
            coolifyManagerTestLoadBalancerService('tcp', 443, 443),
        ],
    ], [
        ['hostname' => 'node1.truckwash.io', 'target_ip' => '94.130.142.41', 'enabled' => true],
    ]);

    expect($plan['actions'])->toHaveCount(1)
        ->and($plan['actions'][0])->toMatchArray([
            'type' => 'update_service',
            'reason' => 'health_check_drift',
            'protocol' => 'http',
            'listen_port' => 80,
            'destination_port' => 80,
            'health_check' => [
                'protocol' => 'tcp',
                'port' => 80,
                'interval' => 15,
                'timeout' => 10,
                'retries' => 3,
            ],
        ]);
});

it('does not plan removal of the last Hetzner load balancer target', function (): void {
    $manager = new coolify_manager();
    $method = new ReflectionMethod(coolify_manager::class, 'planLoadBalancerReconcile');

    $plan = $method->invoke($manager, [
        'targets' => [
            ['type' => 'ip', 'ip' => ['ip' => '94.130.142.41']],
        ],
        'services' => [
            coolifyManagerTestLoadBalancerService('http', 80, 80),
            coolifyManagerTestLoadBalancerService('tcp', 443, 443),
        ],
    ], [
        ['hostname' => 'node1.truckwash.io', 'target_ip' => '94.130.142.41', 'enabled' => false],
    ]);

    expect($plan['actions'][0])
        ->toHaveKey('type', 'skip_remove_target')
        ->toHaveKey('reason', 'last_reachable_target_guard');
});

it('plans removal only for disabled or deleted Hetzner load balancer targets', function (): void {
    $manager = new coolify_manager();
    $method = new ReflectionMethod(coolify_manager::class, 'planLoadBalancerReconcile');

    $plan = $method->invoke($manager, [
        'targets' => [
            ['type' => 'ip', 'ip' => ['ip' => '94.130.142.41']],
            ['type' => 'ip', 'ip' => ['ip' => '65.21.214.30']],
        ],
        'services' => [
            coolifyManagerTestLoadBalancerService('http', 80, 80),
            coolifyManagerTestLoadBalancerService('tcp', 443, 443),
        ],
    ], [
        ['hostname' => 'node1.truckwash.io', 'target_ip' => '94.130.142.41', 'enabled' => true],
        ['hostname' => 'node2.truckwash.io', 'target_ip' => '65.21.214.30', 'enabled' => true, 'deleted_at' => '2026-05-19 10:00:00'],
    ]);

    expect($plan['actions'])
        ->toHaveCount(1)
        ->and($plan['actions'][0])
        ->toHaveKey('type', 'remove_target')
        ->toHaveKey('target_ip', '65.21.214.30');
});

it('builds gateway API auto-provision context for connected Coolify servers', function (): void {
    $manager = new coolify_manager();
    $contextMethod = new ReflectionMethod(coolify_manager::class, 'gatewayRouteProvisionDeployContext');
    $ipMethod = new ReflectionMethod(coolify_manager::class, 'gatewayRouteServerPublicIp');

    $server = [
        'uuid' => 'server-node1',
        'name' => 'node1.truckwash.io',
        'public_ip' => '94.130.142.41',
        'settings' => ['is_reachable' => true, 'is_usable' => true],
    ];

    expect($ipMethod->invoke(null, $server))->toBe('94.130.142.41');

    $context = $contextMethod->invoke($manager, [
        'id' => 42,
        'channel_slug' => 'internal',
        'deploy_context_json' => json_encode([
            'coolify_project_uuid' => 'project-internal',
            'coolify_base_directory' => 'services/nginx/app',
            'coolify_dockerfile_location' => 'services/php/Dockerfile',
            'coolify_ports_exposes' => '9000',
            'coolify_start_command' => 'php-fpm',
            'coolify_destination_uuid' => 'source-destination',
            'coolify_git_commit_sha' => 'source-commit',
            'coolify_enable_ssl' => false,
        ]),
    ], $server, '94.130.142.41', 'api-v2.truckwash.io', 'https://api-v2.truckwash.io');

    expect($context)->toMatchArray([
        'coolify_project_uuid' => 'project-internal',
        'coolify_auto_create' => true,
        'coolify_enable_ssl' => true,
        'coolify_deploy_now' => true,
        'coolify_build_pack' => 'dockerfile',
        'coolify_dockerfile_location' => '/Dockerfile.coolify-api',
        'coolify_ports_exposes' => '80',
        'coolify_port' => '80',
        'coolify_domain' => 'api-v2.truckwash.io',
        'coolify_public_url' => 'https://api-v2.truckwash.io',
        'coolify_server_uuid' => 'server-node1',
        'server_uuid' => 'server-node1',
        'coolify_destination_uuid' => '',
        'destination_uuid' => '',
        'coolify_service_name' => 'release-internal-api-node1-truckwash-io',
        'gateway_route_autoprovision' => true,
        'gateway_route_source_target_id' => 42,
        'gateway_route_target_ip' => '94.130.142.41',
    ]);
    expect($context)->not->toHaveKey('coolify_git_commit_sha');
    expect($context)->not->toHaveKey('coolify_base_directory');
    expect($context)->not->toHaveKey('coolify_start_command');
});

it('builds gateway frontend auto-provision context with the release Dockerfile', function (): void {
    $manager = new coolify_manager();
    $contextMethod = new ReflectionMethod(coolify_manager::class, 'gatewayRouteProvisionDeployContext');

    $server = [
        'uuid' => 'server-node3',
        'name' => 'node3.truckwash.io',
        'public_ip' => '23.88.23.183',
        'settings' => ['is_reachable' => true, 'is_usable' => true],
    ];

    $context = $contextMethod->invoke($manager, [
        'id' => 43,
        'channel_slug' => 'internal',
        'app' => 'frontend',
        'deploy_context_json' => json_encode([
            'coolify_project_uuid' => 'project-internal',
            'coolify_build_pack' => 'static',
            'coolify_install_command' => 'npm ci',
            'coolify_build_command' => 'npm run build',
            'coolify_publish_directory' => 'dist',
            'coolify_is_static' => true,
            'coolify_is_spa' => true,
        ]),
    ], $server, '23.88.23.183', 'api-v2.truckwash.io', 'https://api-v2.truckwash.io/internal/frontend');

    expect($context)->toMatchArray([
        'coolify_project_uuid' => 'project-internal',
        'coolify_build_pack' => 'dockerfile',
        'coolify_dockerfile_location' => '/Dockerfile.coolify-frontend',
        'coolify_ports_exposes' => '80',
        'coolify_port' => '80',
        'coolify_public_url' => 'https://api-v2.truckwash.io/internal/frontend',
        'coolify_server_uuid' => 'server-node3',
        'coolify_service_name' => 'release-internal-frontend-node3-truckwash-io',
        'gateway_route_autoprovision' => true,
        'gateway_route_source_target_id' => 43,
        'gateway_route_target_ip' => '23.88.23.183',
    ]);
    expect($context)->not->toHaveKey('coolify_install_command');
    expect($context)->not->toHaveKey('coolify_publish_directory');
    expect($context)->not->toHaveKey('coolify_is_static');
    expect($context)->not->toHaveKey('coolify_is_spa');
});

it('adds explicit Coolify application route labels for gateway API domains', function (): void {
    $payloadMethod = new ReflectionMethod(coolify_manager::class, 'gatewayRouteApplicationPayload');
    $publicUrlMethod = new ReflectionMethod(coolify_manager::class, 'gatewayRouteTargetPublicUrl');

    $payload = $payloadMethod->invoke(null, 'https://api-v2.truckwash.io', 'api-app-uuid', 8080, base64_encode(implode("\n", [
        'custom.keep=true',
        'traefik.http.routers.https-0-api-app-uuid.entryPoints=old',
        'traefik.http.routers.https-0-api-app-uuid.tls.certresolver=dns-cloudflare',
        'traefik.http.services.https-0-api-app-uuid.loadbalancer.server.port=9090',
    ])));
    $labels = explode("\n", base64_decode($payload['custom_labels'], true));

    expect($payload['domains'])->toBe('https://api-v2.truckwash.io:8080')
        ->and($payload['is_force_https_enabled'])->toBeTrue()
        ->and($payload['force_domain_override'])->toBeTrue()
        ->and($labels)->toContain('custom.keep=true')
        ->and($labels)->toContain('traefik.http.routers.https-0-api-app-uuid.rule=Host(`api-v2.truckwash.io`) && PathPrefix(`/`)')
        ->and($labels)->toContain('traefik.http.routers.https-0-api-app-uuid.entryPoints=https')
        ->and($labels)->toContain('traefik.http.routers.https-0-api-app-uuid.tls.certresolver=letsencrypt')
        ->and($labels)->toContain('traefik.http.routers.https-0-api-app-uuid.tls.domains[0].main=api-v2.truckwash.io')
        ->and($labels)->toContain('traefik.http.services.https-0-api-app-uuid.loadbalancer.server.port=8080');

    expect($publicUrlMethod->invoke(null, 'api-v2.truckwash.io', [
        'channel_slug' => 'stable',
        'channel_default_channel' => 1,
        'app' => 'api',
    ]))->toBe('https://api-v2.truckwash.io');
    expect($publicUrlMethod->invoke(null, 'api-v2.truckwash.io', [
        'channel_slug' => 'internal',
        'channel_default_channel' => 0,
        'app' => 'api',
    ]))->toBe('https://api-v2.truckwash.io/internal/api');
    expect($publicUrlMethod->invoke(null, 'api-v2.truckwash.io', [
        'channel_slug' => 'internal',
        'channel_default_channel' => 0,
        'app' => 'frontend',
    ]))->toBe('https://api-v2.truckwash.io/internal/frontend');

    $pathPayload = $payloadMethod->invoke(null, 'https://api-v2.truckwash.io/internal/api', 'api-app-uuid', 8080, '');
    $pathLabels = explode("\n", base64_decode($pathPayload['custom_labels'], true));

    expect($pathPayload['domains'])->toBe('https://api-v2.truckwash.io:8080/internal/api')
        ->and($pathLabels)->toContain('traefik.http.routers.https-0-api-app-uuid.rule=Host(`api-v2.truckwash.io`) && PathPrefix(`/internal/api`)')
        ->and($pathLabels)->toContain('traefik.http.middlewares.https-0-api-app-uuid-stripprefix.stripprefix.prefixes=/internal/api')
        ->and($pathLabels)->toContain('traefik.http.routers.https-0-api-app-uuid.middlewares=https-0-api-app-uuid-stripprefix,gzip');

    $frontendPayload = $payloadMethod->invoke(null, 'https://api-v2.truckwash.io/internal/frontend', 'frontend-app-uuid', 80, '');
    $frontendLabels = explode("\n", base64_decode($frontendPayload['custom_labels'], true));

    expect($frontendPayload['domains'])->toBe('https://api-v2.truckwash.io:80/internal/frontend')
        ->and($frontendLabels)->toContain('traefik.http.routers.https-0-frontend-app-uuid.rule=Host(`api-v2.truckwash.io`) && PathPrefix(`/internal/frontend`)')
        ->and($frontendLabels)->toContain('traefik.http.middlewares.https-0-frontend-app-uuid-stripprefix.stripprefix.prefixes=/internal/frontend')
        ->and($frontendLabels)->toContain('traefik.http.routers.https-0-frontend-app-uuid.middlewares=https-0-frontend-app-uuid-stripprefix,gzip');
});

it('isolates and restores Hetzner load balancer IP targets for gateway certificate bootstrap', function (): void {
    $manager = new coolify_manager();
    $method = new ReflectionMethod(coolify_manager::class, 'setLoadBalancerIpTargets');
    $client = new CoolifyManagerHetznerTargetSetFake([
        '94.130.142.41',
        '65.21.214.30',
        '23.88.23.183',
    ]);

    $method->invoke($manager, $client, '6366569', ['65.21.214.30']);
    $isolated = $client->targets;
    sort($isolated);

    $method->invoke($manager, $client, '6366569', [
        '94.130.142.41',
        '65.21.214.30',
        '23.88.23.183',
    ]);
    $restored = $client->targets;
    sort($restored);

    expect($isolated)->toBe(['65.21.214.30'])
        ->and($restored)->toBe([
            '23.88.23.183',
            '65.21.214.30',
            '94.130.142.41',
        ]);
});

it('requires gateway ping probes to return the API ping contract', function (): void {
    $method = new ReflectionMethod(coolify_manager::class, 'gatewayProbePingContract');

    expect($method->invoke(null, json_encode([
        'success' => true,
        'data' => ['message' => 'pong'],
    ])))->toMatchArray(['ok' => true, 'message' => 'pong']);

    expect($method->invoke(null, '<b>Fatal error</b>'))->toMatchArray([
        'ok' => false,
        'reason' => 'invalid_json',
    ]);
});

it('normalizes gateway probe paths for release gateway health checks', function (): void {
    $method = new ReflectionMethod(coolify_manager::class, 'normalizeGatewayProbePath');

    expect($method->invoke(null, 'internal/api/ping'))->toBe('/internal/api/ping')
        ->and($method->invoke(null, '//internal//api//ping//'))->toBe('/internal/api/ping')
        ->and($method->invoke(null, 'https://api-v2.truckwash.io/internal/api/ping'))->toBe('/internal/api/ping')
        ->and($method->invoke(null, ''))->toBe('');
});

it('returns structured errors for failed gateway certificate bootstrap and verification', function (): void {
    $method = new ReflectionMethod(coolify_manager::class, 'gatewayRouteHealthErrors');

    $errors = $method->invoke(null, [
        'ok' => false,
        'reason' => 'load_balancer_enforce_required',
        'results' => [
            ['target_ip' => '94.130.142.41', 'ok' => false],
            ['target_ip' => '65.21.214.30', 'ok' => true],
            ['target_ip' => '23.88.23.183', 'ok' => false],
        ],
    ], [
        'ok' => false,
        'failed_target_ips' => ['94.130.142.41', '23.88.23.183'],
        'results' => [],
    ]);

    expect($errors)->toHaveCount(2)
        ->and($errors[0])->toMatchArray([
            'type' => 'certificate_bootstrap_failed',
            'reason' => 'load_balancer_enforce_required',
            'failed_target_ips' => ['94.130.142.41', '23.88.23.183'],
        ])
        ->and($errors[1])->toMatchArray([
            'type' => 'gateway_route_verification_failed',
            'failed_target_ips' => ['94.130.142.41', '23.88.23.183'],
        ]);
});

it('defines Coolify schema, route permissions, and replication integration hooks', function (): void {
    $schema = file_get_contents(app_path('classes/coolify_schema_bootstrap.php'));
    $manager = file_get_contents(app_path('classes/coolify_manager.php'));
    $route = file_get_contents(app_path('routes/superuserCoolifyRoute.php'));
    $replication = file_get_contents(app_path('classes/replication_manager.php'));
    $status = file_get_contents(app_path('classes/superuser_system_status_service.php'));
    $cron = file_get_contents(app_path('cron/Cron.php'));
    $openapi = file_get_contents(app_path('openapi.yaml'));
    $coolifyConfig = file_get_contents(app_path('modules/coolify/coolify_c.php'));
    $tokenConfig = file_get_contents(app_path('modules/coolify/config/coolify_hetzner_cloud_api_token_c.php'));

    expect($schema)->toContain('CREATE TABLE IF NOT EXISTS coolify_instances');
    expect($schema)->toContain('CREATE TABLE IF NOT EXISTS coolify_targets');
    expect($schema)->toContain('CREATE TABLE IF NOT EXISTS coolify_operations');
    expect($schema)->toContain('CREATE TABLE IF NOT EXISTS coolify_audit_logs');
    expect($schema)->toContain('CREATE TABLE IF NOT EXISTS coolify_instance_gateways');
    expect($schema)->toContain("ensureModuleConfigDefault('Coolify', 'lb_automation_enabled'");
    expect($schema)->toContain("ensureModuleConfigDefault('Coolify', 'lb_automation_mode'");
    expect($schema)->toContain("ensureModuleConfigDefault('Coolify', 'hetzner_load_balancer_id'");
    expect($schema)->toContain("ensureModuleConfigDefault('Coolify', 'hetzner_cloud_api_token'");
    expect($schema)->toContain("ensureModuleConfigDefault('Coolify', 'public_gateway_host', 'api-v2.truckwash.io'");
    expect($schema)->toContain("ensureModuleConfigDefault('Coolify', 'public_gateway_probe_path'");
    expect($schema)->toContain('94.130.142.41');
    expect($schema)->toContain('65.21.214.30');
    expect($schema)->toContain('23.88.23.183');
    expect($schema)->toContain("ensureModuleConfigDefault('Coolify', 'enabled'");

    expect($route)->toContain('/superuser/coolify');
    expect($route)->toContain('/superuser/coolify/load-balancer');
    expect($route)->toContain('/superuser/coolify/load-balancer/reconcile');
    expect($route)->toContain('/superuser/coolify/load-balancer/routes/deploy');
    expect($route)->toContain('/superuser/coolify/load-balancer/api/deploy');
    expect($route)->toContain('/superuser/coolify/gateways');
    expect($route)->toContain('/superuser/coolify/gateways/{id}/test');
    expect($route)->toContain('/superuser/coolify/instances/{id}/test');
    expect($route)->toContain('/superuser/coolify/instances/{id}/placement');
    expect($route)->toContain('/superuser/coolify/targets/{id}/reconcile');
    expect($route)->toContain('/superuser/coolify/targets/{id}/deploy');
    expect($route)->toContain('/superuser/coolify/targets/{id}/restart');
    expect($route)->toContain('/superuser/coolify/targets/{id}/failover');
    expect($route)->toContain("requirePermission('superuser_coolify_view')");
    expect($route)->toContain("requirePermission('superuser_coolify_manage')");
    expect($route)->toContain("requirePermission('superuser_coolify_reconcile')");
    expect($route)->toContain("requirePermission('superuser_coolify_failover')");

    expect($manager)->toContain("Coolify-managed targets must be deployed as replicas first");
    expect($manager)->toContain('ensureFailoverEnabled($kind)');
    expect($manager)->toContain("deployment_provider' => 'coolify'");
    expect($manager)->toContain('discoverInstancePlacement');
    expect($manager)->toContain('applyCoolifyDeploymentDefaults($input, $instance)');
    expect($manager)->toContain('resolveCoolifyServerHost');
    expect($manager)->toContain('publicServerHostFromCoolifyServer');
    expect($manager)->toContain('applyCoolifyPortDefaults');
    expect($manager)->toContain('syncReplicationHostPortsForTarget');
    expect($manager)->toContain('usedPublicPortsForCoolifyServer');
    expect($manager)->toContain('nextAvailablePublicPorts');
    expect($manager)->toContain('syncReplicationHostEndpointForTarget');
    expect($manager)->toContain('knownPublicHostForCoolifyServer');
    expect($manager)->toContain('publicDnsServerNameFromCoolifyServer');
    expect($manager)->toContain('resolvedPublicDnsServerHostFromCoolifyServer');
    expect($manager)->toContain('recordCreatedResource');
    expect($manager)->toContain("'start_requested'");
    expect($manager)->toContain('primaryCredentials');
    expect($manager)->toContain('primary_admin_password');
    expect($manager)->toContain('replication_transfer_limit');
    expect($manager)->toContain('startOrRestartService');
    expect($manager)->toContain('already running');
    expect($manager)->toContain('restart_requested');
    expect($manager)->toContain('isolated_stack');
    expect($manager)->toContain('skip_replication_provisioning');
    expect($manager)->toContain('targetSkipsReplicationProvisioning');
    expect($manager)->toContain('targetComposeRole');
    expect($manager)->toContain('production_data_attached');
    expect($manager)->toContain('deferredProvisionResult');
    expect($manager)->toContain('isTransientProvisionBlock');
    expect($manager)->toContain('provision_deferred');
    expect($manager)->toContain('shouldRetryProvisioning');
    expect($manager)->toContain('shouldRetryProvisioning($target, $host)');
    expect($manager)->toContain('hasRunningReplicationProvisionOperation');
    expect($manager)->toContain('replicationHostStillNeedsProvisioning');
    expect($manager)->toContain('syncDeploymentStateForReplicationHost');
    expect($manager)->toContain('syncLabelForReplicationHost');
    expect($manager)->toContain('syncTargetsForReplicationHost');
    expect($manager)->toContain('targetAllowsReplicaRemoval');
    expect($manager)->toContain('markTargetsRemovedForReplicationHost');
    expect($manager)->toContain('replicationHostIsReady');
    expect($manager)->toContain("'provisioned'");
    expect($manager)->not->toContain('is_container_label_escape_enabled');
    expect($manager)->not->toContain("\$payload['type'] = 'docker-compose';");
    expect($manager)->toContain('encodedDockerCompose');
    expect($manager)->toContain('base64_encode');
    expect($manager)->toContain('if (!$update)');
    expect($manager)->toContain("'project_uuid' => \$target['project_uuid']");
    expect($manager)->toContain('/api/v1/services');
    expect($manager)->toContain('/envs/bulk');
    expect($manager)->toContain('loadBalancerSummary');
    expect($manager)->toContain('reconcileLoadBalancer');
    expect($manager)->toContain('deployGatewayApplicationRoutes');
    expect($manager)->toContain('deployGatewayApiCode');
    expect($manager)->toContain('gateway_api_code_deploy');
    expect($manager)->toContain('deploy_gateway_route_after_code');
    expect($manager)->toContain('loadBalancerReleaseGatewayTargets');
    expect($manager)->toContain('provisionMissingGatewayRouteTargets');
    expect($manager)->toContain('provision_gateway_');
    expect($manager)->toContain('gateway_route_autoprovision');
    expect($manager)->toContain('upsertDeploymentTarget');
    expect($manager)->toContain('startDeployment');
    expect($manager)->toContain("'commit_mode' => \$sourceCommitSha === '' ? 'latest' : 'specific'");
    expect($manager)->toContain("\$deploymentInput['commit_sha'] = \$sourceCommitSha;");
    expect($manager)->toContain('verifyGatewayRoutes');
    expect($manager)->toContain('bootstrapGatewayCertificates');
    expect($manager)->toContain('setLoadBalancerIpTargets');
    expect($manager)->toContain('probeGatewayPublicHost');
    expect($manager)->toContain('GATEWAY_CERT_BOOTSTRAP_ATTEMPTS');
    expect($manager)->toContain('certificate_bootstrap');
    expect($manager)->toContain('certificate_bootstrap_failed');
    expect($manager)->toContain('gateway_route_verification_failed');
    expect($manager)->toContain('recordGatewayProbe');
    expect($manager)->toContain("Gateway route and Let's Encrypt certificate verification is still failing");
    expect($manager)->toContain('CURLOPT_SSL_VERIFYHOST, 2');
    expect($manager)->toContain('CURLOPT_SSL_VERIFYPEER, true');
    expect($manager)->toContain('CURLOPT_CERTINFO');
    expect($manager)->toContain('CURLINFO_SSL_VERIFYRESULT');
    expect($manager)->toContain("Gateway TLS certificate was not issued by Let's Encrypt.");
    expect($manager)->toContain('gatewayProbePath');
    expect($manager)->toContain('public_gateway_probe_path');
    expect($manager)->toContain('loadBalancerReleaseApiTargets');
    expect($manager)->toContain('gatewayProbePingContract');
    expect($manager)->toContain('Gateway ping response did not match the expected API contract.');
    expect($manager)->not->toContain('CURLOPT_SSL_VERIFYHOST, 0');
    expect($manager)->not->toContain('CURLOPT_SSL_VERIFYPEER, false');
    expect($manager)->toContain('information_schema.tables');
    expect($manager)->not->toContain('SHOW TABLES LIKE ?');
    expect($manager)->toContain('gatewayRouteApplicationPayload');
    expect($manager)->toContain('gateway_application_routes_deployed');
    expect($manager)->toContain('target_already_defined');
    expect($manager)->toContain('REQUIRED_LOAD_BALANCER_SERVICES');
    expect($manager)->toContain('skip_remove_target');

    $composer = json_decode((string)file_get_contents(app_path('composer.json')), true);
    expect($composer['autoload']['exclude-from-classmap'] ?? [])->toContain('modules/*/vendor/');

    $client = file_get_contents(app_path('classes/coolify_api_client.php'));
    expect($client)->toContain("request('GET', '/health', null, false)");
    expect($client)->toContain('/github-apps');
    expect($client)->toContain('/applications/private-github-app');
    expect($client)->toContain("request('GET', '/applications/' . rawurlencode(\$uuid))");
    expect($client)->toContain("'/deploy?uuid=' . rawurlencode(\$uuid)");
    expect($client)->toContain('/applications/\' . rawurlencode($uuid) . \'/restart');
    expect($client)->toContain('CURL_HTTP_VERSION_1_1');
    expect($client)->toContain('validationErrorSummary');

    expect($replication)->toContain('deployment_provider');
    expect($replication)->toContain('coolify_manager::deploymentMetadataForReplicationHost');
    expect($replication)->toContain('coolify_manager::syncDeploymentStateForReplicationHost');
    expect($replication)->toContain('databaseEngineKnown');
    expect($status)->toContain("'key' => 'coolify'");
    expect($status)->toContain('probeCoolifyModule');
    expect($cron)->toContain('CoolifyAvailabilityMonitorCron');
    expect($cron)->toContain('CoolifyLoadBalancerReconcileCron');
    expect($coolifyConfig)->toContain('[redacted]');
    expect($coolifyConfig)->toContain('secret_set');
    expect($tokenConfig)->toContain('replication_secret_box::encrypt');
    expect($openapi)->toContain('/superuser/coolify:');
    expect($openapi)->toContain('operationId: getSuperuserCoolifyLoadBalancer');
    expect($openapi)->toContain('operationId: reconcileSuperuserCoolifyLoadBalancer');
    expect($openapi)->toContain('operationId: deploySuperuserCoolifyGatewayRoutes');
    expect($openapi)->toContain('operationId: deploySuperuserCoolifyGatewayApiCode');
    expect($openapi)->toContain('operationId: listSuperuserCoolifyGateways');
    expect($openapi)->toContain('operationId: testSuperuserCoolifyGateway');
    expect($openapi)->toContain('operationId: discoverSuperuserCoolifyInstancePlacement');
    expect($openapi)->toContain('operationId: createSuperuserCoolifyTarget');
    expect($openapi)->toContain('SuperuserCoolifyTarget');
    expect($openapi)->toContain('SuperuserCoolifyLoadBalancer');
    expect($openapi)->toContain('SuperuserCoolifyGateway');
});
