<?php

declare(strict_types=1);

use App\Framework\Config\DockerSecretsResolver;
use App\Framework\Config\EnvKey;
use App\Framework\Config\Environment;
use App\Framework\Exception\Config\EnvironmentVariableNotFoundException;

describe('Environment', function () {
    beforeEach(function () {
        // Create test environment variables
        $this->testVariables = [
            'APP_NAME' => 'TestApp',
            'APP_ENV' => 'development',
            'APP_DEBUG' => 'true',
            'DB_HOST' => 'localhost',
            'DB_PORT' => '3306',
            'DB_NAME' => 'testdb',
            'CACHE_TTL' => '3600',
            'API_TIMEOUT' => '30.5',
            'FEATURE_FLAGS' => 'feature1,feature2,feature3',
            'EMPTY_STRING' => '',
            'NULL_VALUE' => 'null',
            'FALSE_VALUE' => 'false',
            'ZERO_VALUE' => '0',
        ];

        $this->dockerSecretsResolver = new DockerSecretsResolver();
        $this->environment = new Environment($this->testVariables, $this->dockerSecretsResolver);
    });

    describe('get()', function () {
        it('returns string value for existing key', function () {
            $value = $this->environment->get('APP_NAME');

            expect($value)->toBe('TestApp');
        });

        it('returns null for non-existent key', function () {
            $value = $this->environment->get('NON_EXISTENT');

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

        it('returns default value for non-existent key', function () {
            $value = $this->environment->get('NON_EXISTENT', 'default_value');

            expect($value)->toBe('default_value');
        });

        it('returns empty string when value is empty', function () {
            $value = $this->environment->get('EMPTY_STRING');

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

        it('returns string "null" for null value', function () {
            $value = $this->environment->get('NULL_VALUE');

            expect($value)->toBe('null');
        });

        it('accepts EnvKey enum', function () {
            $value = $this->environment->get(EnvKey::APP_NAME);

            expect($value)->toBe('TestApp');
        });
    });

    describe('getInt()', function () {
        it('returns integer for numeric string', function () {
            $value = $this->environment->getInt('DB_PORT');

            expect($value)->toBe(3306);
            expect($value)->toBeInt();
        });

        it('returns null for non-existent key', function () {
            $value = $this->environment->getInt('NON_EXISTENT');

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

        it('returns default value for non-existent key', function () {
            $value = $this->environment->getInt('NON_EXISTENT', 9999);

            expect($value)->toBe(9999);
        });

        it('returns zero for "0" string', function () {
            $value = $this->environment->getInt('ZERO_VALUE');

            expect($value)->toBe(0);
            expect($value)->toBeInt();
        });

        it('accepts EnvKey enum', function () {
            $value = $this->environment->getInt(EnvKey::DB_PORT);

            expect($value)->toBe(3306);
        });

        it('handles negative integers', function () {
            $env = new Environment(['NEGATIVE' => '-42'], $this->dockerSecretsResolver);

            $value = $env->getInt('NEGATIVE');

            expect($value)->toBe(-42);
        });
    });

    describe('getBool()', function () {
        it('returns true for "true" string', function () {
            $value = $this->environment->getBool('APP_DEBUG');

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

        it('returns false for "false" string', function () {
            $value = $this->environment->getBool('FALSE_VALUE');

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

        it('returns null for non-existent key', function () {
            $value = $this->environment->getBool('NON_EXISTENT');

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

        it('returns default value for non-existent key', function () {
            $value = $this->environment->getBool('NON_EXISTENT', true);

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

        it('handles case-insensitive true values', function () {
            $env = new Environment([
                'BOOL1' => 'true',
                'BOOL2' => 'True',
                'BOOL3' => 'TRUE',
                'BOOL4' => '1',
            ], $this->dockerSecretsResolver);

            expect($env->getBool('BOOL1'))->toBeTrue();
            expect($env->getBool('BOOL2'))->toBeTrue();
            expect($env->getBool('BOOL3'))->toBeTrue();
            expect($env->getBool('BOOL4'))->toBeTrue();
        });

        it('handles case-insensitive false values', function () {
            $env = new Environment([
                'BOOL1' => 'false',
                'BOOL2' => 'False',
                'BOOL3' => 'FALSE',
                'BOOL4' => '0',
                'BOOL5' => '',
            ], $this->dockerSecretsResolver);

            expect($env->getBool('BOOL1'))->toBeFalse();
            expect($env->getBool('BOOL2'))->toBeFalse();
            expect($env->getBool('BOOL3'))->toBeFalse();
            expect($env->getBool('BOOL4'))->toBeFalse();
            expect($env->getBool('BOOL5'))->toBeFalse();
        });

        it('accepts EnvKey enum', function () {
            $value = $this->environment->getBool(EnvKey::APP_DEBUG);

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

    describe('getFloat()', function () {
        it('returns float for decimal string', function () {
            $value = $this->environment->getFloat('API_TIMEOUT');

            expect($value)->toBe(30.5);
            expect($value)->toBeFloat();
        });

        it('returns null for non-existent key', function () {
            $value = $this->environment->getFloat('NON_EXISTENT');

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

        it('returns default value for non-existent key', function () {
            $value = $this->environment->getFloat('NON_EXISTENT', 99.99);

            expect($value)->toBe(99.99);
        });

        it('converts integer string to float', function () {
            $value = $this->environment->getFloat('DB_PORT');

            expect($value)->toBe(3306.0);
            expect($value)->toBeFloat();
        });

        it('handles negative floats', function () {
            $env = new Environment(['NEGATIVE' => '-42.5'], $this->dockerSecretsResolver);

            $value = $env->getFloat('NEGATIVE');

            expect($value)->toBe(-42.5);
        });

        it('accepts EnvKey enum', function () {
            $value = $this->environment->getFloat(EnvKey::API_TIMEOUT);

            expect($value)->toBe(30.5);
        });
    });

    describe('getArray()', function () {
        it('returns array from comma-separated string', function () {
            $value = $this->environment->getArray('FEATURE_FLAGS');

            expect($value)->toBe(['feature1', 'feature2', 'feature3']);
            expect($value)->toBeArray();
        });

        it('returns null for non-existent key', function () {
            $value = $this->environment->getArray('NON_EXISTENT');

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

        it('returns default value for non-existent key', function () {
            $value = $this->environment->getArray('NON_EXISTENT', ['default1', 'default2']);

            expect($value)->toBe(['default1', 'default2']);
        });

        it('returns single-element array for non-comma value', function () {
            $value = $this->environment->getArray('APP_NAME');

            expect($value)->toBe(['TestApp']);
        });

        it('trims whitespace from array elements', function () {
            $env = new Environment([
                'WHITESPACE_ARRAY' => 'value1 , value2 ,  value3  ',
            ], $this->dockerSecretsResolver);

            $value = $env->getArray('WHITESPACE_ARRAY');

            expect($value)->toBe(['value1', 'value2', 'value3']);
        });

        it('returns empty array for empty string', function () {
            $value = $this->environment->getArray('EMPTY_STRING');

            expect($value)->toBe(['']);
        });

        it('accepts custom separator', function () {
            $env = new Environment([
                'PIPE_SEPARATED' => 'value1|value2|value3',
            ], $this->dockerSecretsResolver);

            $value = $env->getArray('PIPE_SEPARATED', separator: '|');

            expect($value)->toBe(['value1', 'value2', 'value3']);
        });

        it('accepts EnvKey enum', function () {
            $value = $this->environment->getArray(EnvKey::FEATURE_FLAGS);

            expect($value)->toBe(['feature1', 'feature2', 'feature3']);
        });
    });

    describe('getEnum()', function () {
        it('returns enum value for valid string', function () {
            $value = $this->environment->getEnum('APP_ENV', AppEnvironment::class);

            expect($value)->toBeInstanceOf(AppEnvironment::class);
            expect($value)->toBe(AppEnvironment::DEVELOPMENT);
        });

        it('returns null for non-existent key', function () {
            $value = $this->environment->getEnum('NON_EXISTENT', AppEnvironment::class);

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

        it('returns default value for non-existent key', function () {
            $value = $this->environment->getEnum(
                'NON_EXISTENT',
                AppEnvironment::class,
                AppEnvironment::PRODUCTION
            );

            expect($value)->toBe(AppEnvironment::PRODUCTION);
        });

        it('handles case-insensitive enum matching', function () {
            $env = new Environment([
                'ENV1' => 'development',
                'ENV2' => 'Development',
                'ENV3' => 'DEVELOPMENT',
            ], $this->dockerSecretsResolver);

            expect($env->getEnum('ENV1', AppEnvironment::class))->toBe(AppEnvironment::DEVELOPMENT);
            expect($env->getEnum('ENV2', AppEnvironment::class))->toBe(AppEnvironment::DEVELOPMENT);
            expect($env->getEnum('ENV3', AppEnvironment::class))->toBe(AppEnvironment::DEVELOPMENT);
        });

        it('accepts EnvKey enum for key parameter', function () {
            $value = $this->environment->getEnum(EnvKey::APP_ENV, AppEnvironment::class);

            expect($value)->toBe(AppEnvironment::DEVELOPMENT);
        });

        it('handles backed enum with integer value', function () {
            $env = new Environment(['STATUS' => '1'], $this->dockerSecretsResolver);

            $value = $env->getEnum('STATUS', TestIntEnum::class);

            expect($value)->toBe(TestIntEnum::ACTIVE);
        });
    });

    describe('require()', function () {
        it('returns value for existing key', function () {
            $value = $this->environment->require('APP_NAME');

            expect($value)->toBe('TestApp');
        });

        it('throws exception for non-existent key', function () {
            $this->environment->require('NON_EXISTENT');
        })->throws(EnvironmentVariableNotFoundException::class);

        it('accepts EnvKey enum', function () {
            $value = $this->environment->require(EnvKey::APP_NAME);

            expect($value)->toBe('TestApp');
        });

        it('throws exception with helpful message', function () {
            try {
                $this->environment->require('MISSING_VARIABLE');
                $this->fail('Expected EnvironmentVariableNotFoundException');
            } catch (EnvironmentVariableNotFoundException $e) {
                expect($e->getMessage())->toContain('MISSING_VARIABLE');
                expect($e->getMessage())->toContain('required');
            }
        });
    });

    describe('requireInt()', function () {
        it('returns integer for existing numeric key', function () {
            $value = $this->environment->requireInt('DB_PORT');

            expect($value)->toBe(3306);
            expect($value)->toBeInt();
        });

        it('throws exception for non-existent key', function () {
            $this->environment->requireInt('NON_EXISTENT');
        })->throws(EnvironmentVariableNotFoundException::class);

        it('accepts EnvKey enum', function () {
            $value = $this->environment->requireInt(EnvKey::DB_PORT);

            expect($value)->toBe(3306);
        });
    });

    describe('requireBool()', function () {
        it('returns boolean for existing boolean key', function () {
            $value = $this->environment->requireBool('APP_DEBUG');

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

        it('throws exception for non-existent key', function () {
            $this->environment->requireBool('NON_EXISTENT');
        })->throws(EnvironmentVariableNotFoundException::class);

        it('accepts EnvKey enum', function () {
            $value = $this->environment->requireBool(EnvKey::APP_DEBUG);

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

    describe('requireFloat()', function () {
        it('returns float for existing numeric key', function () {
            $value = $this->environment->requireFloat('API_TIMEOUT');

            expect($value)->toBe(30.5);
            expect($value)->toBeFloat();
        });

        it('throws exception for non-existent key', function () {
            $this->environment->requireFloat('NON_EXISTENT');
        })->throws(EnvironmentVariableNotFoundException::class);

        it('accepts EnvKey enum', function () {
            $value = $this->environment->requireFloat(EnvKey::API_TIMEOUT);

            expect($value)->toBe(30.5);
        });
    });

    describe('requireArray()', function () {
        it('returns array for existing comma-separated key', function () {
            $value = $this->environment->requireArray('FEATURE_FLAGS');

            expect($value)->toBe(['feature1', 'feature2', 'feature3']);
            expect($value)->toBeArray();
        });

        it('throws exception for non-existent key', function () {
            $this->environment->requireArray('NON_EXISTENT');
        })->throws(EnvironmentVariableNotFoundException::class);

        it('accepts EnvKey enum', function () {
            $value = $this->environment->requireArray(EnvKey::FEATURE_FLAGS);

            expect($value)->toBe(['feature1', 'feature2', 'feature3']);
        });

        it('accepts custom separator', function () {
            $env = new Environment([
                'PIPE_SEPARATED' => 'value1|value2|value3',
            ], $this->dockerSecretsResolver);

            $value = $env->requireArray('PIPE_SEPARATED', separator: '|');

            expect($value)->toBe(['value1', 'value2', 'value3']);
        });
    });

    describe('requireEnum()', function () {
        it('returns enum value for valid string', function () {
            $value = $this->environment->requireEnum('APP_ENV', AppEnvironment::class);

            expect($value)->toBeInstanceOf(AppEnvironment::class);
            expect($value)->toBe(AppEnvironment::DEVELOPMENT);
        });

        it('throws exception for non-existent key', function () {
            $this->environment->requireEnum('NON_EXISTENT', AppEnvironment::class);
        })->throws(EnvironmentVariableNotFoundException::class);

        it('accepts EnvKey enum for key parameter', function () {
            $value = $this->environment->requireEnum(EnvKey::APP_ENV, AppEnvironment::class);

            expect($value)->toBe(AppEnvironment::DEVELOPMENT);
        });
    });

    describe('has()', function () {
        it('returns true for existing key', function () {
            $exists = $this->environment->has('APP_NAME');

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

        it('returns false for non-existent key', function () {
            $exists = $this->environment->has('NON_EXISTENT');

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

        it('returns true for empty string value', function () {
            $exists = $this->environment->has('EMPTY_STRING');

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

        it('accepts EnvKey enum', function () {
            $exists = $this->environment->has(EnvKey::APP_NAME);

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

    describe('all()', function () {
        it('returns all environment variables', function () {
            $allVars = $this->environment->all();

            expect($allVars)->toBe($this->testVariables);
            expect($allVars)->toBeArray();
        });

        it('returns empty array for empty environment', function () {
            $emptyEnv = new Environment([], $this->dockerSecretsResolver);

            $allVars = $emptyEnv->all();

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

    describe('Docker Secrets Integration', function () {
        it('resolves Docker secret when file exists', function () {
            // Create temporary Docker secret file
            $secretPath = '/tmp/docker_secret_test';
            file_put_contents($secretPath, 'secret_value_from_file');

            $env = new Environment([
                'DB_PASSWORD_FILE' => $secretPath,
            ], $this->dockerSecretsResolver);

            $value = $env->get('DB_PASSWORD');

            expect($value)->toBe('secret_value_from_file');

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

        it('returns null when Docker secret file does not exist', function () {
            $env = new Environment([
                'DB_PASSWORD_FILE' => '/nonexistent/path/to/secret',
            ], $this->dockerSecretsResolver);

            $value = $env->get('DB_PASSWORD');

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

        it('prioritizes direct variable over Docker secret', function () {
            // Create temporary Docker secret file
            $secretPath = '/tmp/docker_secret_test';
            file_put_contents($secretPath, 'secret_from_file');

            $env = new Environment([
                'DB_PASSWORD' => 'direct_value',
                'DB_PASSWORD_FILE' => $secretPath,
            ], $this->dockerSecretsResolver);

            $value = $env->get('DB_PASSWORD');

            // Direct variable should win
            expect($value)->toBe('direct_value');

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

        it('trims whitespace from Docker secret content', function () {
            // Create temporary Docker secret file with whitespace
            $secretPath = '/tmp/docker_secret_test';
            file_put_contents($secretPath, "  secret_value_with_whitespace  \n");

            $env = new Environment([
                'API_KEY_FILE' => $secretPath,
            ], $this->dockerSecretsResolver);

            $value = $env->get('API_KEY');

            expect($value)->toBe('secret_value_with_whitespace');

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

        it('handles multiple Docker secrets', function () {
            // Create multiple secret files
            $dbPasswordPath = '/tmp/db_password_secret';
            $apiKeyPath = '/tmp/api_key_secret';

            file_put_contents($dbPasswordPath, 'db_secret_123');
            file_put_contents($apiKeyPath, 'api_secret_456');

            $env = new Environment([
                'DB_PASSWORD_FILE' => $dbPasswordPath,
                'API_KEY_FILE' => $apiKeyPath,
            ], $this->dockerSecretsResolver);

            expect($env->get('DB_PASSWORD'))->toBe('db_secret_123');
            expect($env->get('API_KEY'))->toBe('api_secret_456');

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

    describe('Edge Cases', function () {
        it('handles keys with underscores', function () {
            $env = new Environment([
                'SOME_LONG_KEY_NAME' => 'value',
            ], $this->dockerSecretsResolver);

            $value = $env->get('SOME_LONG_KEY_NAME');

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

        it('handles keys with numbers', function () {
            $env = new Environment([
                'VAR_123' => 'value',
            ], $this->dockerSecretsResolver);

            $value = $env->get('VAR_123');

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

        it('handles very long values', function () {
            $longValue = str_repeat('a', 10000);
            $env = new Environment([
                'LONG_VALUE' => $longValue,
            ], $this->dockerSecretsResolver);

            $value = $env->get('LONG_VALUE');

            expect($value)->toBe($longValue);
        });

        it('handles special characters in values', function () {
            $env = new Environment([
                'SPECIAL' => 'value!@#$%^&*()_+-=[]{}|;:\'",.<>?/`~',
            ], $this->dockerSecretsResolver);

            $value = $env->get('SPECIAL');

            expect($value)->toBe('value!@#$%^&*()_+-=[]{}|;:\'",.<>?/`~');
        });

        it('handles unicode characters', function () {
            $env = new Environment([
                'UNICODE' => 'Hello 世界 🌍',
            ], $this->dockerSecretsResolver);

            $value = $env->get('UNICODE');

            expect($value)->toBe('Hello 世界 🌍');
        });

        it('handles JSON strings as values', function () {
            $jsonString = '{"key":"value","nested":{"foo":"bar"}}';
            $env = new Environment([
                'JSON_CONFIG' => $jsonString,
            ], $this->dockerSecretsResolver);

            $value = $env->get('JSON_CONFIG');

            expect($value)->toBe($jsonString);
            expect(json_decode($value, true))->toBe([
                'key' => 'value',
                'nested' => ['foo' => 'bar'],
            ]);
        });

        it('handles URL strings as values', function () {
            $env = new Environment([
                'DATABASE_URL' => 'mysql://user:pass@localhost:3306/dbname',
            ], $this->dockerSecretsResolver);

            $value = $env->get('DATABASE_URL');

            expect($value)->toBe('mysql://user:pass@localhost:3306/dbname');
        });

        it('handles base64-encoded values', function () {
            $base64Value = base64_encode('secret_data');
            $env = new Environment([
                'ENCODED_SECRET' => $base64Value,
            ], $this->dockerSecretsResolver);

            $value = $env->get('ENCODED_SECRET');

            expect($value)->toBe($base64Value);
            expect(base64_decode($value))->toBe('secret_data');
        });
    });

    describe('Type Coercion Edge Cases', function () {
        it('handles non-numeric string for getInt', function () {
            $env = new Environment([
                'NOT_A_NUMBER' => 'abc',
            ], $this->dockerSecretsResolver);

            $value = $env->getInt('NOT_A_NUMBER');

            expect($value)->toBe(0); // PHP's (int) cast behavior
        });

        it('handles non-boolean string for getBool', function () {
            $env = new Environment([
                'NOT_A_BOOL' => 'maybe',
            ], $this->dockerSecretsResolver);

            $value = $env->getBool('NOT_A_BOOL');

            expect($value)->toBeFalse(); // Non-empty string that's not "true" or "1"
        });

        it('handles scientific notation for getFloat', function () {
            $env = new Environment([
                'SCIENTIFIC' => '1.5e3',
            ], $this->dockerSecretsResolver);

            $value = $env->getFloat('SCIENTIFIC');

            expect($value)->toBe(1500.0);
        });

        it('handles hexadecimal strings for getInt', function () {
            $env = new Environment([
                'HEX_VALUE' => '0xFF',
            ], $this->dockerSecretsResolver);

            $value = $env->getInt('HEX_VALUE');

            expect($value)->toBe(0); // String "0xFF" casts to 0
        });
    });
});

// Test enums for getEnum() testing
enum AppEnvironment: string
{
    case DEVELOPMENT = 'development';
    case STAGING = 'staging';
    case PRODUCTION = 'production';
}

enum TestIntEnum: int
{
    case INACTIVE = 0;
    case ACTIVE = 1;
    case PENDING = 2;
}
