<?php

declare(strict_types=1);

use App\Framework\Config\EncryptedEnvLoader;
use App\Framework\Config\EnvFileParser;
use App\Framework\Config\Environment;
use App\Framework\Config\EnvKey;
use App\Framework\Encryption\EncryptionFactory;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Random\SecureRandomGenerator;

/**
 * Integration tests for end-to-end environment loading flow
 *
 * These tests verify the entire environment loading system works correctly:
 * - EncryptedEnvLoader loading from .env files
 * - Environment object created with parsed variables
 * - DockerSecretsResolver integration in real scenarios
 * - EnvFileParser correctly parsing various .env formats
 * - Two-pass loading with encryption support
 * - Production vs Development priority handling
 * - PHP-FPM getenv() priority fix verification
 */
describe('Environment Loading Integration', function () {
    beforeEach(function () {
        $this->testDir = '/home/michael/dev/michaelschiemer/tests/tmp';

        // Create test directory if it doesn't exist
        if (!is_dir($this->testDir)) {
            mkdir($this->testDir, 0777, true);
        }

        // Initialize services
        $this->parser = new EnvFileParser();
        $this->encryptionFactory = new EncryptionFactory(new SecureRandomGenerator());
        $this->loader = new EncryptedEnvLoader($this->encryptionFactory, $this->parser);

        // Store original $_ENV and getenv() for restoration
        $this->originalEnv = $_ENV;
        $this->originalGetenv = getenv();
    });

    afterEach(function () {
        // Restore original environment
        $_ENV = $this->originalEnv;

        // Clear test directory
        if (is_dir($this->testDir)) {
            $files = glob($this->testDir . '/*');
            foreach ($files as $file) {
                if (is_file($file)) {
                    unlink($file);
                }
            }
        }
    });

    describe('Basic .env File Loading', function () {
        it('loads environment from simple .env file', function () {
            $envContent = <<<ENV
APP_NAME=TestApp
APP_ENV=production
APP_DEBUG=false
DB_HOST=localhost
DB_PORT=3306
ENV;

            file_put_contents($this->testDir . '/.env', $envContent);

            $env = $this->loader->load($this->testDir);

            expect($env->get('APP_NAME'))->toBe('TestApp');
            expect($env->get('APP_ENV'))->toBe('production');
            expect($env->getBool('APP_DEBUG'))->toBeFalse();
            expect($env->get('DB_HOST'))->toBe('localhost');
            expect($env->getInt('DB_PORT'))->toBe(3306);
        });

        it('handles quoted values correctly', function () {
            $envContent = <<<ENV
SINGLE_QUOTED='single quoted value'
DOUBLE_QUOTED="double quoted value"
UNQUOTED=unquoted value
ENV;

            file_put_contents($this->testDir . '/.env', $envContent);

            $env = $this->loader->load($this->testDir);

            expect($env->get('SINGLE_QUOTED'))->toBe('single quoted value');
            expect($env->get('DOUBLE_QUOTED'))->toBe('double quoted value');
            expect($env->get('UNQUOTED'))->toBe('unquoted value');
        });

        it('handles type casting correctly', function () {
            $envContent = <<<ENV
BOOL_TRUE=true
BOOL_FALSE=false
INT_VALUE=123
FLOAT_VALUE=123.45
NULL_VALUE=null
ARRAY_VALUE=item1,item2,item3
ENV;

            file_put_contents($this->testDir . '/.env', $envContent);

            $env = $this->loader->load($this->testDir);

            expect($env->getBool('BOOL_TRUE'))->toBeTrue();
            expect($env->getBool('BOOL_FALSE'))->toBeFalse();
            expect($env->getInt('INT_VALUE'))->toBe(123);
            expect($env->getFloat('FLOAT_VALUE'))->toBe(123.45);
            expect($env->get('NULL_VALUE'))->toBeNull();
            expect($env->getArray('ARRAY_VALUE'))->toBe(['item1', 'item2', 'item3']);
        });

        it('handles comments and empty lines', function () {
            $envContent = <<<ENV
# This is a comment
APP_NAME=TestApp

# Another comment
APP_ENV=development

ENV;

            file_put_contents($this->testDir . '/.env', $envContent);

            $env = $this->loader->load($this->testDir);

            expect($env->get('APP_NAME'))->toBe('TestApp');
            expect($env->get('APP_ENV'))->toBe('development');
        });
    });

    describe('Environment-Specific File Loading', function () {
        it('loads environment-specific .env file', function () {
            // Base .env
            file_put_contents($this->testDir . '/.env', <<<ENV
APP_NAME=BaseApp
APP_ENV=development
DB_HOST=localhost
ENV
            );

            // Environment-specific .env.development
            file_put_contents($this->testDir . '/.env.development', <<<ENV
DB_HOST=dev-database
DB_DEBUG=true
ENV
            );

            $env = $this->loader->load($this->testDir);

            expect($env->get('APP_NAME'))->toBe('BaseApp');
            expect($env->get('APP_ENV'))->toBe('development');
            expect($env->get('DB_HOST'))->toBe('dev-database'); // Overridden
            expect($env->getBool('DB_DEBUG'))->toBeTrue(); // Added
        });

        it('loads production-specific configuration', function () {
            // Base .env
            file_put_contents($this->testDir . '/.env', <<<ENV
APP_NAME=MyApp
APP_ENV=production
DB_HOST=localhost
ENV
            );

            // Environment-specific .env.production
            file_put_contents($this->testDir . '/.env.production', <<<ENV
DB_HOST=prod-database
CACHE_DRIVER=redis
ENV
            );

            $env = $this->loader->load($this->testDir);

            expect($env->get('APP_NAME'))->toBe('MyApp');
            expect($env->get('APP_ENV'))->toBe('production');
            expect($env->get('DB_HOST'))->toBe('prod-database');
            expect($env->get('CACHE_DRIVER'))->toBe('redis');
        });
    });

    describe('Docker Environment Variables Priority', function () {
        it('prioritizes Docker ENV vars over .env files in production', function () {
            // Simulate Docker ENV vars via $_ENV
            $_ENV['APP_ENV'] = 'production';
            $_ENV['DB_HOST'] = 'docker-mysql';
            $_ENV['DB_PORT'] = '3306';

            // .env file with different values
            file_put_contents($this->testDir . '/.env', <<<ENV
APP_ENV=production
DB_HOST=localhost
DB_PORT=5432
DB_NAME=mydb
ENV
            );

            $env = $this->loader->load($this->testDir);

            // Docker ENV vars should win in production
            expect($env->get('DB_HOST'))->toBe('docker-mysql');
            expect($env->getInt('DB_PORT'))->toBe(3306);

            // But .env can add new variables
            expect($env->get('DB_NAME'))->toBe('mydb');
        });

        it('allows .env files to override in development', function () {
            // Simulate system ENV vars
            $_ENV['APP_ENV'] = 'development';
            $_ENV['DB_HOST'] = 'system-host';

            // .env file with override
            file_put_contents($this->testDir . '/.env', <<<ENV
APP_ENV=development
DB_HOST=local-override
ENV
            );

            $env = $this->loader->load($this->testDir);

            // .env should override in development
            expect($env->get('DB_HOST'))->toBe('local-override');
        });
    });

    describe('PHP-FPM getenv() Priority Fix', function () {
        it('prioritizes getenv() over $_ENV in PHP-FPM scenario', function () {
            // Simulate PHP-FPM scenario where $_ENV is empty but getenv() works
            $_ENV = []; // Empty like in PHP-FPM

            // Create .env file
            file_put_contents($this->testDir . '/.env', <<<ENV
APP_NAME=FromFile
APP_ENV=development
ENV
            );

            $env = $this->loader->load($this->testDir);

            // Should load from .env file since getenv() is also empty
            expect($env->get('APP_NAME'))->toBe('FromFile');
            expect($env->get('APP_ENV'))->toBe('development');
        });
    });

    describe('Docker Secrets Integration', function () {
        it('resolves Docker secrets from _FILE variables', function () {
            // Create secret files
            $dbPasswordPath = $this->testDir . '/db_password_secret';
            $apiKeyPath = $this->testDir . '/api_key_secret';

            file_put_contents($dbPasswordPath, 'super_secret_password');
            file_put_contents($apiKeyPath, 'api_key_12345');

            // .env file with _FILE variables
            file_put_contents($this->testDir . '/.env', <<<ENV
APP_NAME=TestApp
DB_PASSWORD_FILE={$dbPasswordPath}
API_KEY_FILE={$apiKeyPath}
ENV
            );

            $env = $this->loader->load($this->testDir);

            expect($env->get('APP_NAME'))->toBe('TestApp');
            expect($env->get('DB_PASSWORD'))->toBe('super_secret_password');
            expect($env->get('API_KEY'))->toBe('api_key_12345');

            // Cleanup secret files
            unlink($dbPasswordPath);
            unlink($apiKeyPath);
        });

        it('prioritizes direct variables over Docker secrets', function () {
            // Create secret file
            $secretPath = $this->testDir . '/secret';
            file_put_contents($secretPath, 'secret_from_file');

            // .env file with both direct and _FILE
            file_put_contents($this->testDir . '/.env', <<<ENV
DB_PASSWORD=direct_password
DB_PASSWORD_FILE={$secretPath}
ENV
            );

            $env = $this->loader->load($this->testDir);

            // Direct variable should win
            expect($env->get('DB_PASSWORD'))->toBe('direct_password');

            // Cleanup
            unlink($secretPath);
        });

        it('handles missing Docker secret files gracefully', function () {
            // .env file with _FILE pointing to non-existent file
            file_put_contents($this->testDir . '/.env', <<<ENV
APP_NAME=TestApp
DB_PASSWORD_FILE=/non/existent/path
ENV
            );

            $env = $this->loader->load($this->testDir);

            expect($env->get('APP_NAME'))->toBe('TestApp');
            expect($env->get('DB_PASSWORD'))->toBeNull(); // Secret not resolved
        });
    });

    describe('Two-Pass Loading with Encryption', function () {
        it('loads encryption key in first pass and secrets in second pass', function () {
            // Generate encryption key
            $encryptionKey = bin2hex(random_bytes(32));

            // Base .env with encryption key
            file_put_contents($this->testDir . '/.env', <<<ENV
APP_NAME=TestApp
ENCRYPTION_KEY={$encryptionKey}
ENV
            );

            // Note: .env.secrets would normally contain encrypted values
            // For this test, we verify the two-pass loading mechanism

            $env = $this->loader->load($this->testDir);

            expect($env->get('APP_NAME'))->toBe('TestApp');
            expect($env->get('ENCRYPTION_KEY'))->toBe($encryptionKey);
        });

        it('handles missing encryption key gracefully', function () {
            // .env without encryption key
            file_put_contents($this->testDir . '/.env', <<<ENV
APP_NAME=TestApp
APP_ENV=development
ENV
            );

            $env = $this->loader->load($this->testDir);

            // Should load successfully without encryption
            expect($env->get('APP_NAME'))->toBe('TestApp');
            expect($env->get('ENCRYPTION_KEY'))->toBeNull();
        });
    });

    describe('Complex Real-World Scenarios', function () {
        it('handles multi-layer environment configuration', function () {
            // Simulate Docker ENV vars
            $_ENV['APP_ENV'] = 'production';
            $_ENV['CONTAINER_NAME'] = 'web-01';

            // Base .env
            file_put_contents($this->testDir . '/.env', <<<ENV
APP_NAME=MyApp
APP_ENV=production
DB_HOST=localhost
DB_PORT=3306
CACHE_DRIVER=file
ENV
            );

            // Environment-specific .env.production
            file_put_contents($this->testDir . '/.env.production', <<<ENV
DB_HOST=prod-db-cluster
CACHE_DRIVER=redis
REDIS_HOST=redis-cluster
ENV
            );

            // Create Docker secret
            $dbPasswordPath = $this->testDir . '/db_password';
            file_put_contents($dbPasswordPath, 'production_password');

            $_ENV['DB_PASSWORD_FILE'] = $dbPasswordPath;

            $env = $this->loader->load($this->testDir);

            // Verify layered loading
            expect($env->get('APP_NAME'))->toBe('MyApp'); // From .env
            expect($env->get('APP_ENV'))->toBe('production'); // From Docker ENV (priority)
            expect($env->get('CONTAINER_NAME'))->toBe('web-01'); // From Docker ENV only
            expect($env->get('DB_HOST'))->toBe('prod-db-cluster'); // From .env.production
            expect($env->getInt('DB_PORT'))->toBe(3306); // From .env
            expect($env->get('CACHE_DRIVER'))->toBe('redis'); // From .env.production (override)
            expect($env->get('REDIS_HOST'))->toBe('redis-cluster'); // From .env.production
            expect($env->get('DB_PASSWORD'))->toBe('production_password'); // From Docker secret

            // Cleanup
            unlink($dbPasswordPath);
        });

        it('handles database connection string configuration', function () {
            $envContent = <<<ENV
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=myapp_db
DB_USERNAME=dbuser
DB_PASSWORD=dbpass
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci
ENV;

            file_put_contents($this->testDir . '/.env', $envContent);

            $env = $this->loader->load($this->testDir);

            // Verify all database configuration
            expect($env->get('DB_CONNECTION'))->toBe('mysql');
            expect($env->get('DB_HOST'))->toBe('127.0.0.1');
            expect($env->getInt('DB_PORT'))->toBe(3306);
            expect($env->get('DB_DATABASE'))->toBe('myapp_db');
            expect($env->get('DB_USERNAME'))->toBe('dbuser');
            expect($env->get('DB_PASSWORD'))->toBe('dbpass');
            expect($env->get('DB_CHARSET'))->toBe('utf8mb4');
            expect($env->get('DB_COLLATION'))->toBe('utf8mb4_unicode_ci');
        });

        it('handles application-wide feature flags', function () {
            $envContent = <<<ENV
APP_NAME=MyApp
FEATURE_NEW_UI=true
FEATURE_BETA_ANALYTICS=false
FEATURE_EXPERIMENTAL_CACHE=true
MAINTENANCE_MODE=false
DEBUG_TOOLBAR=true
ENV;

            file_put_contents($this->testDir . '/.env', $envContent);

            $env = $this->loader->load($this->testDir);

            expect($env->get('APP_NAME'))->toBe('MyApp');
            expect($env->getBool('FEATURE_NEW_UI'))->toBeTrue();
            expect($env->getBool('FEATURE_BETA_ANALYTICS'))->toBeFalse();
            expect($env->getBool('FEATURE_EXPERIMENTAL_CACHE'))->toBeTrue();
            expect($env->getBool('MAINTENANCE_MODE'))->toBeFalse();
            expect($env->getBool('DEBUG_TOOLBAR'))->toBeTrue();
        });

        it('handles URL and connection strings', function () {
            $envContent = <<<ENV
APP_URL=https://myapp.com
REDIS_URL=redis://localhost:6379/0
DATABASE_URL=mysql://user:pass@localhost:3306/dbname
ELASTICSEARCH_URL=http://localhost:9200
ENV;

            file_put_contents($this->testDir . '/.env', $envContent);

            $env = $this->loader->load($this->testDir);

            expect($env->get('APP_URL'))->toBe('https://myapp.com');
            expect($env->get('REDIS_URL'))->toBe('redis://localhost:6379/0');
            expect($env->get('DATABASE_URL'))->toBe('mysql://user:pass@localhost:3306/dbname');
            expect($env->get('ELASTICSEARCH_URL'))->toBe('http://localhost:9200');
        });
    });

    describe('Error Handling', function () {
        it('handles missing .env file gracefully', function () {
            // No .env file created
            $env = $this->loader->load($this->testDir);

            // Should return environment with only system variables
            expect($env)->toBeInstanceOf(Environment::class);
        });

        it('handles corrupted .env file gracefully', function () {
            // Create invalid .env file
            file_put_contents($this->testDir . '/.env', <<<ENV
VALID_VAR=value
INVALID LINE WITHOUT EQUALS
ANOTHER_VALID=value2
ENV
            );

            $env = $this->loader->load($this->testDir);

            // Should skip invalid lines and load valid ones
            expect($env->get('VALID_VAR'))->toBe('value');
            expect($env->get('ANOTHER_VALID'))->toBe('value2');
        });

        it('handles unreadable .env file gracefully', function () {
            // Create .env file and make it unreadable
            file_put_contents($this->testDir . '/.env', 'APP_NAME=Test');
            chmod($this->testDir . '/.env', 0000);

            $env = $this->loader->load($this->testDir);

            // Should handle gracefully
            expect($env)->toBeInstanceOf(Environment::class);

            // Restore permissions for cleanup
            chmod($this->testDir . '/.env', 0644);
        });
    });

    describe('EnvKey Enum Integration', function () {
        it('works with EnvKey enum throughout loading', function () {
            $envContent = <<<ENV
APP_NAME=TestApp
APP_ENV=production
DB_HOST=localhost
DB_PORT=3306
ENV;

            file_put_contents($this->testDir . '/.env', $envContent);

            $env = $this->loader->load($this->testDir);

            // Use EnvKey enum for access
            expect($env->get(EnvKey::APP_NAME))->toBe('TestApp');
            expect($env->get(EnvKey::APP_ENV))->toBe('production');
            expect($env->get(EnvKey::DB_HOST))->toBe('localhost');
            expect($env->getInt(EnvKey::DB_PORT))->toBe(3306);
        });
    });

    describe('Performance and Memory', function () {
        it('handles large .env files efficiently', function () {
            // Generate large .env file
            $lines = [];
            for ($i = 0; $i < 1000; $i++) {
                $lines[] = "VAR_{$i}=value_{$i}";
            }
            file_put_contents($this->testDir . '/.env', implode("\n", $lines));

            $startMemory = memory_get_usage();
            $startTime = microtime(true);

            $env = $this->loader->load($this->testDir);

            $endTime = microtime(true);
            $endMemory = memory_get_usage();

            $duration = ($endTime - $startTime) * 1000; // Convert to ms
            $memoryUsed = ($endMemory - $startMemory) / 1024 / 1024; // Convert to MB

            // Verify all variables loaded
            expect($env->get('VAR_0'))->toBe('value_0');
            expect($env->get('VAR_999'))->toBe('value_999');

            // Performance assertions (generous limits for CI environments)
            expect($duration)->toBeLessThan(1000); // Should load in under 1 second
            expect($memoryUsed)->toBeLessThan(10); // Should use less than 10MB
        });
    });
});
