<?php

declare(strict_types=1);

use App\Framework\Config\DockerSecretsResolver;
use App\Framework\Filesystem\ValueObjects\FilePath;

describe('DockerSecretsResolver', function () {
    beforeEach(function () {
        $this->resolver = new DockerSecretsResolver();

        // Create temporary test directory
        $this->testDir = '/home/michael/dev/michaelschiemer/tests/tmp';
        if (!is_dir($this->testDir)) {
            mkdir($this->testDir, 0777, true);
        }
    });

    afterEach(function () {
        // Clean up test files
        if (isset($this->testFile) && file_exists($this->testFile)) {
            unlink($this->testFile);
        }
    });

    describe('resolve()', function () {
        it('resolves secret from file when {KEY}_FILE exists', function () {
            $this->testFile = $this->testDir . '/db_password_secret';
            file_put_contents($this->testFile, 'super_secret_password');

            $variables = [
                'DB_PASSWORD_FILE' => $this->testFile
            ];

            $result = $this->resolver->resolve('DB_PASSWORD', $variables);

            expect($result)->toBe('super_secret_password');
        });

        it('trims whitespace from file content', function () {
            $this->testFile = $this->testDir . '/api_key_secret';
            file_put_contents($this->testFile, "  secret_api_key_123  \n  ");

            $variables = [
                'API_KEY_FILE' => $this->testFile
            ];

            $result = $this->resolver->resolve('API_KEY', $variables);

            expect($result)->toBe('secret_api_key_123');
        });

        it('trims newlines and whitespace', function () {
            $this->testFile = $this->testDir . '/token_secret';
            file_put_contents($this->testFile, "\nmy_token\n");

            $variables = [
                'TOKEN_FILE' => $this->testFile
            ];

            $result = $this->resolver->resolve('TOKEN', $variables);

            expect($result)->toBe('my_token');
        });

        it('returns null when {KEY}_FILE variable does not exist', function () {
            $variables = [
                'DB_PASSWORD' => 'plain_password',
                'SOME_OTHER_VAR' => 'value'
            ];

            $result = $this->resolver->resolve('DB_PASSWORD', $variables);

            expect($result)->toBeNull();
        });

        it('returns null when file path is not a string', function () {
            $variables = [
                'DB_PASSWORD_FILE' => 12345 // Not a string
            ];

            $result = $this->resolver->resolve('DB_PASSWORD', $variables);

            expect($result)->toBeNull();
        });

        it('returns null when file path is an array', function () {
            $variables = [
                'DB_PASSWORD_FILE' => ['path', 'to', 'file']
            ];

            $result = $this->resolver->resolve('DB_PASSWORD', $variables);

            expect($result)->toBeNull();
        });

        it('returns null when file does not exist', function () {
            $variables = [
                'DB_PASSWORD_FILE' => $this->testDir . '/non_existent_file'
            ];

            $result = $this->resolver->resolve('DB_PASSWORD', $variables);

            expect($result)->toBeNull();
        });

        it('returns null when file is not readable', function () {
            $this->testFile = $this->testDir . '/unreadable_secret';
            file_put_contents($this->testFile, 'secret');
            chmod($this->testFile, 0000); // Make unreadable

            $variables = [
                'DB_PASSWORD_FILE' => $this->testFile
            ];

            $result = $this->resolver->resolve('DB_PASSWORD', $variables);

            expect($result)->toBeNull();

            // Restore permissions for cleanup
            chmod($this->testFile, 0644);
        });

        it('returns null when FilePath creation fails', function () {
            $variables = [
                'DB_PASSWORD_FILE' => '' // Invalid path
            ];

            $result = $this->resolver->resolve('DB_PASSWORD', $variables);

            expect($result)->toBeNull();
        });

        it('handles multi-line secret files', function () {
            $this->testFile = $this->testDir . '/multi_line_secret';
            file_put_contents($this->testFile, "line1\nline2\nline3");

            $variables = [
                'MULTI_LINE_FILE' => $this->testFile
            ];

            $result = $this->resolver->resolve('MULTI_LINE', $variables);

            expect($result)->toBe("line1\nline2\nline3");
        });

        it('handles empty file', function () {
            $this->testFile = $this->testDir . '/empty_secret';
            file_put_contents($this->testFile, '');

            $variables = [
                'EMPTY_FILE' => $this->testFile
            ];

            $result = $this->resolver->resolve('EMPTY', $variables);

            expect($result)->toBe('');
        });

        it('handles file with only whitespace', function () {
            $this->testFile = $this->testDir . '/whitespace_secret';
            file_put_contents($this->testFile, "   \n\n   ");

            $variables = [
                'WHITESPACE_FILE' => $this->testFile
            ];

            $result = $this->resolver->resolve('WHITESPACE', $variables);

            expect($result)->toBe('');
        });

        it('resolves secrets with special characters', function () {
            $this->testFile = $this->testDir . '/special_chars_secret';
            file_put_contents($this->testFile, 'p@$$w0rd!#%&*()');

            $variables = [
                'SPECIAL_CHARS_FILE' => $this->testFile
            ];

            $result = $this->resolver->resolve('SPECIAL_CHARS', $variables);

            expect($result)->toBe('p@$$w0rd!#%&*()');
        });

        it('resolves secrets with unicode characters', function () {
            $this->testFile = $this->testDir . '/unicode_secret';
            file_put_contents($this->testFile, 'pässwörd_日本語');

            $variables = [
                'UNICODE_FILE' => $this->testFile
            ];

            $result = $this->resolver->resolve('UNICODE', $variables);

            expect($result)->toBe('pässwörd_日本語');
        });

        it('resolves absolute file paths', function () {
            $this->testFile = $this->testDir . '/absolute_path_secret';
            file_put_contents($this->testFile, 'absolute_secret');

            $variables = [
                'ABS_PATH_FILE' => $this->testFile
            ];

            $result = $this->resolver->resolve('ABS_PATH', $variables);

            expect($result)->toBe('absolute_secret');
        });
    });

    describe('hasSecret()', function () {
        it('returns true when secret exists and is readable', function () {
            $this->testFile = $this->testDir . '/db_password_secret';
            file_put_contents($this->testFile, 'secret');

            $variables = [
                'DB_PASSWORD_FILE' => $this->testFile
            ];

            $result = $this->resolver->hasSecret('DB_PASSWORD', $variables);

            expect($result)->toBeTrue();
        });

        it('returns false when {KEY}_FILE variable does not exist', function () {
            $variables = [
                'DB_PASSWORD' => 'plain_password'
            ];

            $result = $this->resolver->hasSecret('DB_PASSWORD', $variables);

            expect($result)->toBeFalse();
        });

        it('returns false when file does not exist', function () {
            $variables = [
                'DB_PASSWORD_FILE' => $this->testDir . '/non_existent_file'
            ];

            $result = $this->resolver->hasSecret('DB_PASSWORD', $variables);

            expect($result)->toBeFalse();
        });

        it('returns false when file is not readable', function () {
            $this->testFile = $this->testDir . '/unreadable_secret';
            file_put_contents($this->testFile, 'secret');
            chmod($this->testFile, 0000);

            $variables = [
                'DB_PASSWORD_FILE' => $this->testFile
            ];

            $result = $this->resolver->hasSecret('DB_PASSWORD', $variables);

            expect($result)->toBeFalse();

            // Restore permissions for cleanup
            chmod($this->testFile, 0644);
        });

        it('returns true for empty file', function () {
            $this->testFile = $this->testDir . '/empty_secret';
            file_put_contents($this->testFile, '');

            $variables = [
                'EMPTY_FILE' => $this->testFile
            ];

            $result = $this->resolver->hasSecret('EMPTY', $variables);

            expect($result)->toBeTrue();
        });

        it('returns false when file path is not a string', function () {
            $variables = [
                'DB_PASSWORD_FILE' => 12345
            ];

            $result = $this->resolver->hasSecret('DB_PASSWORD', $variables);

            expect($result)->toBeFalse();
        });
    });

    describe('resolveMultiple()', function () {
        beforeEach(function () {
            // Create multiple secret files
            $this->dbPasswordFile = $this->testDir . '/db_password';
            $this->apiKeyFile = $this->testDir . '/api_key';
            $this->smtpPasswordFile = $this->testDir . '/smtp_password';

            file_put_contents($this->dbPasswordFile, 'db_secret_123');
            file_put_contents($this->apiKeyFile, 'api_key_456');
            file_put_contents($this->smtpPasswordFile, 'smtp_secret_789');
        });

        afterEach(function () {
            // Clean up multiple test files
            foreach ([$this->dbPasswordFile, $this->apiKeyFile, $this->smtpPasswordFile] as $file) {
                if (file_exists($file)) {
                    unlink($file);
                }
            }
        });

        it('resolves multiple secrets successfully', function () {
            $variables = [
                'DB_PASSWORD_FILE' => $this->dbPasswordFile,
                'API_KEY_FILE' => $this->apiKeyFile,
                'SMTP_PASSWORD_FILE' => $this->smtpPasswordFile
            ];

            $keys = ['DB_PASSWORD', 'API_KEY', 'SMTP_PASSWORD'];

            $result = $this->resolver->resolveMultiple($keys, $variables);

            expect($result)->toBe([
                'DB_PASSWORD' => 'db_secret_123',
                'API_KEY' => 'api_key_456',
                'SMTP_PASSWORD' => 'smtp_secret_789'
            ]);
        });

        it('resolves only available secrets', function () {
            $variables = [
                'DB_PASSWORD_FILE' => $this->dbPasswordFile,
                'API_KEY_FILE' => $this->apiKeyFile
                // SMTP_PASSWORD_FILE missing
            ];

            $keys = ['DB_PASSWORD', 'API_KEY', 'SMTP_PASSWORD'];

            $result = $this->resolver->resolveMultiple($keys, $variables);

            expect($result)->toBe([
                'DB_PASSWORD' => 'db_secret_123',
                'API_KEY' => 'api_key_456'
                // SMTP_PASSWORD not in result
            ]);
        });

        it('returns empty array when no secrets are available', function () {
            $variables = [
                'DB_PASSWORD' => 'plain_password',
                'API_KEY' => 'plain_key'
            ];

            $keys = ['DB_PASSWORD', 'API_KEY', 'SMTP_PASSWORD'];

            $result = $this->resolver->resolveMultiple($keys, $variables);

            expect($result)->toBe([]);
        });

        it('returns empty array for empty keys list', function () {
            $variables = [
                'DB_PASSWORD_FILE' => $this->dbPasswordFile
            ];

            $result = $this->resolver->resolveMultiple([], $variables);

            expect($result)->toBe([]);
        });

        it('skips non-existent files', function () {
            $variables = [
                'DB_PASSWORD_FILE' => $this->dbPasswordFile,
                'API_KEY_FILE' => $this->testDir . '/non_existent',
                'SMTP_PASSWORD_FILE' => $this->smtpPasswordFile
            ];

            $keys = ['DB_PASSWORD', 'API_KEY', 'SMTP_PASSWORD'];

            $result = $this->resolver->resolveMultiple($keys, $variables);

            expect($result)->toBe([
                'DB_PASSWORD' => 'db_secret_123',
                'SMTP_PASSWORD' => 'smtp_secret_789'
            ]);
        });

        it('skips unreadable files', function () {
            chmod($this->apiKeyFile, 0000);

            $variables = [
                'DB_PASSWORD_FILE' => $this->dbPasswordFile,
                'API_KEY_FILE' => $this->apiKeyFile,
                'SMTP_PASSWORD_FILE' => $this->smtpPasswordFile
            ];

            $keys = ['DB_PASSWORD', 'API_KEY', 'SMTP_PASSWORD'];

            $result = $this->resolver->resolveMultiple($keys, $variables);

            expect($result)->toBe([
                'DB_PASSWORD' => 'db_secret_123',
                'SMTP_PASSWORD' => 'smtp_secret_789'
            ]);

            // Restore permissions for cleanup
            chmod($this->apiKeyFile, 0644);
        });

        it('trims whitespace from all resolved secrets', function () {
            file_put_contents($this->dbPasswordFile, "  db_secret  \n");
            file_put_contents($this->apiKeyFile, "\n  api_key  ");

            $variables = [
                'DB_PASSWORD_FILE' => $this->dbPasswordFile,
                'API_KEY_FILE' => $this->apiKeyFile
            ];

            $keys = ['DB_PASSWORD', 'API_KEY'];

            $result = $this->resolver->resolveMultiple($keys, $variables);

            expect($result)->toBe([
                'DB_PASSWORD' => 'db_secret',
                'API_KEY' => 'api_key'
            ]);
        });

        it('handles mixed success and failure scenarios', function () {
            $variables = [
                'DB_PASSWORD_FILE' => $this->dbPasswordFile,
                'API_KEY_FILE' => 12345, // Invalid (not string)
                'SMTP_PASSWORD_FILE' => $this->smtpPasswordFile,
                'MISSING_FILE' => $this->testDir . '/non_existent'
            ];

            $keys = ['DB_PASSWORD', 'API_KEY', 'SMTP_PASSWORD', 'MISSING'];

            $result = $this->resolver->resolveMultiple($keys, $variables);

            expect($result)->toBe([
                'DB_PASSWORD' => 'db_secret_123',
                'SMTP_PASSWORD' => 'smtp_secret_789'
            ]);
        });

        it('preserves order of successfully resolved secrets', function () {
            $variables = [
                'DB_PASSWORD_FILE' => $this->dbPasswordFile,
                'API_KEY_FILE' => $this->apiKeyFile,
                'SMTP_PASSWORD_FILE' => $this->smtpPasswordFile
            ];

            $keys = ['SMTP_PASSWORD', 'DB_PASSWORD', 'API_KEY'];

            $result = $this->resolver->resolveMultiple($keys, $variables);

            expect($result)->toBe([
                'SMTP_PASSWORD' => 'smtp_secret_789',
                'DB_PASSWORD' => 'db_secret_123',
                'API_KEY' => 'api_key_456'
            ]);
        });

        it('handles duplicate keys in input', function () {
            $variables = [
                'DB_PASSWORD_FILE' => $this->dbPasswordFile,
                'API_KEY_FILE' => $this->apiKeyFile
            ];

            $keys = ['DB_PASSWORD', 'API_KEY', 'DB_PASSWORD', 'API_KEY'];

            $result = $this->resolver->resolveMultiple($keys, $variables);

            // Last duplicate wins
            expect($result)->toBe([
                'DB_PASSWORD' => 'db_secret_123',
                'API_KEY' => 'api_key_456'
            ]);
        });
    });

    describe('edge cases', function () {
        it('handles very long file paths', function () {
            $longPath = $this->testDir . '/' . str_repeat('a', 200) . '_secret';
            $this->testFile = $longPath;

            file_put_contents($this->testFile, 'long_path_secret');

            $variables = [
                'LONG_PATH_FILE' => $this->testFile
            ];

            $result = $this->resolver->resolve('LONG_PATH', $variables);

            expect($result)->toBe('long_path_secret');
        });

        it('handles very large file content', function () {
            $this->testFile = $this->testDir . '/large_secret';
            $largeContent = str_repeat('secret', 10000); // 60KB content
            file_put_contents($this->testFile, $largeContent);

            $variables = [
                'LARGE_FILE' => $this->testFile
            ];

            $result = $this->resolver->resolve('LARGE', $variables);

            expect($result)->toBe($largeContent);
        });

        it('handles file with null bytes', function () {
            $this->testFile = $this->testDir . '/null_bytes_secret';
            file_put_contents($this->testFile, "secret\0with\0nulls");

            $variables = [
                'NULL_BYTES_FILE' => $this->testFile
            ];

            $result = $this->resolver->resolve('NULL_BYTES', $variables);

            expect($result)->toBe("secret\0with\0nulls");
        });

        it('handles concurrent access to same file', function () {
            $this->testFile = $this->testDir . '/concurrent_secret';
            file_put_contents($this->testFile, 'concurrent_value');

            $variables = [
                'CONCURRENT_FILE' => $this->testFile
            ];

            // Simulate concurrent access
            $result1 = $this->resolver->resolve('CONCURRENT', $variables);
            $result2 = $this->resolver->resolve('CONCURRENT', $variables);

            expect($result1)->toBe('concurrent_value');
            expect($result2)->toBe('concurrent_value');
        });

        it('handles symlinks to secret files', function () {
            $this->testFile = $this->testDir . '/original_secret';
            $symlinkPath = $this->testDir . '/symlink_secret';

            file_put_contents($this->testFile, 'symlink_value');
            symlink($this->testFile, $symlinkPath);

            $variables = [
                'SYMLINK_FILE' => $symlinkPath
            ];

            $result = $this->resolver->resolve('SYMLINK', $variables);

            expect($result)->toBe('symlink_value');

            // Cleanup symlink
            unlink($symlinkPath);
        });

        it('returns null for directories', function () {
            $dirPath = $this->testDir . '/secret_dir';
            mkdir($dirPath, 0777, true);

            $variables = [
                'DIR_FILE' => $dirPath
            ];

            $result = $this->resolver->resolve('DIR', $variables);

            expect($result)->toBeNull();

            // Cleanup directory
            rmdir($dirPath);
        });
    });

    describe('Docker Secrets real-world scenarios', function () {
        it('resolves typical Docker Swarm secret', function () {
            $this->testFile = $this->testDir . '/docker_secret';
            // Docker secrets typically have trailing newline
            file_put_contents($this->testFile, "production_password\n");

            $variables = [
                'DB_PASSWORD_FILE' => $this->testFile
            ];

            $result = $this->resolver->resolve('DB_PASSWORD', $variables);

            expect($result)->toBe('production_password');
        });

        it('resolves secrets from /run/secrets path pattern', function () {
            // Simulate Docker Swarm /run/secrets pattern
            $secretsDir = $this->testDir . '/run/secrets';
            mkdir($secretsDir, 0777, true);

            $this->testFile = $secretsDir . '/db_password';
            file_put_contents($this->testFile, 'swarm_secret');

            $variables = [
                'DB_PASSWORD_FILE' => $this->testFile
            ];

            $result = $this->resolver->resolve('DB_PASSWORD', $variables);

            expect($result)->toBe('swarm_secret');

            // Cleanup
            unlink($this->testFile);
            rmdir($secretsDir);
        });

        it('handles multiple Docker secrets in production environment', function () {
            $secretsDir = $this->testDir . '/run/secrets';
            mkdir($secretsDir, 0777, true);

            $dbPassword = $secretsDir . '/db_password';
            $apiKey = $secretsDir . '/api_key';
            $jwtSecret = $secretsDir . '/jwt_secret';

            file_put_contents($dbPassword, "prod_db_pass\n");
            file_put_contents($apiKey, "prod_api_key\n");
            file_put_contents($jwtSecret, "prod_jwt_secret\n");

            $variables = [
                'DB_PASSWORD_FILE' => $dbPassword,
                'API_KEY_FILE' => $apiKey,
                'JWT_SECRET_FILE' => $jwtSecret
            ];

            $keys = ['DB_PASSWORD', 'API_KEY', 'JWT_SECRET'];
            $result = $this->resolver->resolveMultiple($keys, $variables);

            expect($result)->toBe([
                'DB_PASSWORD' => 'prod_db_pass',
                'API_KEY' => 'prod_api_key',
                'JWT_SECRET' => 'prod_jwt_secret'
            ]);

            // Cleanup
            unlink($dbPassword);
            unlink($apiKey);
            unlink($jwtSecret);
            rmdir($secretsDir);
        });
    });
});
