<?php

declare(strict_types=1);

namespace App\Framework\Config;

use App\Framework\Config\Exceptions\RequiredEnvironmentVariableException;
use App\Framework\Config\ValueObjects\SecurityContext;
use App\Framework\Encryption\EncryptionInterface;
use App\Framework\Http\ServerEnvironment;
use App\Framework\Http\ServerKey;
use App\Framework\Random\RandomGenerator;
use App\Framework\UserAgent\UserAgent;

/**
 * Secure secrets management using pluggable encryption
 */
final readonly class SecretManager
{
    private const string SECRET_PREFIX = 'SECRET_';

    public function __construct(
        private Environment $environment,
        private EncryptionInterface $encryption,
        private ServerEnvironment $serverEnvironment,
        private RandomGenerator $randomGenerator
    ) {
    }

    /**
     * Get a secret value, automatically decrypting if necessary
     */
    public function getSecret(EnvKey|string $key, mixed $default = null): mixed
    {
        $value = $this->environment->get($key, $default);

        if ($value === null) {
            return $default;
        }

        if (is_string($value) && $this->encryption->isEncrypted($value)) {
            $this->auditSecretAccess($this->environment->keyToString($key), 'read');

            return $this->encryption->decrypt($value);
        }

        return $value;
    }

    /**
     * Get a required secret, throwing exception if not found
     */
    public function getRequiredSecret(EnvKey|string $key): mixed
    {
        $value = $this->getSecret($key);

        if ($value === null) {
            $keyString = $this->environment->keyToString($key);

            throw new RequiredEnvironmentVariableException($keyString);
        }

        return $value;
    }

    /**
     * Encrypt a secret value for storage
     */
    public function encryptSecret(string $value): string
    {
        return $this->encryption->encrypt($value);
    }

    /**
     * Check if a value is encrypted
     */
    public function isEncrypted(mixed $value): bool
    {
        return is_string($value) && $this->encryption->isEncrypted($value);
    }

    /**
     * Get all secret keys (those starting with SECRET_PREFIX)
     */
    public function getSecretKeys(): array
    {
        $allVars = $this->environment->all();
        $secretKeys = [];

        foreach (array_keys($allVars) as $key) {
            if (str_starts_with($key, self::SECRET_PREFIX)) {
                $secretKeys[] = $key;
            }
        }

        return $secretKeys;
    }

    /**
     * Rotate encryption for all secrets with new encryption instance
     */
    public function rotateSecrets(EncryptionInterface $newEncryption): array
    {
        $secretKeys = $this->getSecretKeys();
        $rotatedSecrets = [];

        foreach ($secretKeys as $key) {
            $currentValue = $this->environment->get($key);

            if ($this->isEncrypted($currentValue)) {
                // Decrypt with old encryption, encrypt with new encryption
                $decrypted = $this->encryption->decrypt($currentValue);
                $rotatedSecrets[$key] = $newEncryption->encrypt($decrypted);

                $this->auditSecretAccess($key, 'rotate');
            }
        }

        return $rotatedSecrets;
    }

    /**
     * Generate a secure token for API keys, passwords, etc.
     */
    public function generateSecureToken(int $length = 32): string
    {
        $bytes = $this->randomGenerator->bytes($length);

        return bin2hex($bytes);
    }

    /**
     * Generate a secure password with mixed character types
     */
    public function generateSecurePassword(int $length = 16): string
    {
        $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';
        $charactersLength = strlen($characters);
        $password = '';

        for ($i = 0; $i < $length; $i++) {
            $randomIndex = $this->randomGenerator->int(0, $charactersLength - 1);
            $password .= $characters[$randomIndex];
        }

        return $password;
    }

    /**
     * Audit log for secret access (for compliance)
     */
    public function auditSecretAccess(string $key, string $action = 'read'): void
    {
        $timestamp = date('Y-m-d H:i:s');
        $user = $this->serverEnvironment->get(ServerKey::REMOTE_USER, 'unknown');
        $clientIp = (string)$this->serverEnvironment->getClientIp();
        $userAgent = $this->maskUserAgent($this->serverEnvironment->getUserAgent());

        $logEntry = sprintf(
            '[SECRETS] %s - Action: %s, Key: %s, User: %s, IP: %s, Method: %s, UA: %s',
            $timestamp,
            $action,
            $key,
            $user,
            $clientIp,
            $this->encryption->getMethod(),
            $userAgent
        );

        // Log to system logs (in production, this should go to a secure audit system)
        error_log($logEntry);
    }

    /**
     * Mask sensitive values for logging
     */
    public function maskSecret(string $secret): string
    {
        $length = strlen($secret);

        if ($length <= 4) {
            return str_repeat('*', $length);
        }

        return substr($secret, 0, 2) . str_repeat('*', $length - 4) . substr($secret, -2);
    }

    /**
     * Mask user agent for privacy in logs
     */
    private function maskUserAgent(UserAgent $userAgent): string
    {
        $userAgentString = $userAgent->value;

        if (strlen($userAgentString) <= 20) {
            return $userAgentString;
        }

        return substr($userAgentString, 0, 20) . '...';
    }

    /**
     * Check if the current context is secure (HTTPS)
     */
    public function isSecureContext(): bool
    {
        return $this->serverEnvironment->isHttps();
    }

    /**
     * Get security context information
     */
    public function getSecurityContext(): SecurityContext
    {
        return SecurityContext::create(
            isHttps: $this->serverEnvironment->isHttps(),
            clientIp: $this->serverEnvironment->getClientIp(),
            maskedUserAgent: $this->maskUserAgent($this->serverEnvironment->getUserAgent()),
            serverName: $this->serverEnvironment->getServerName(),
            encryptionMethod: $this->encryption->getMethod()
        );
    }

    /**
     * Validate that secrets are properly encrypted in non-secure contexts
     */
    public function validateSecretsForContext(): array
    {
        $issues = [];
        $secretKeys = $this->getSecretKeys();
        $isSecure = $this->isSecureContext();

        foreach ($secretKeys as $key) {
            $value = $this->environment->get($key);

            if (! $this->isEncrypted($value)) {
                $issues[] = [
                    'key' => $key,
                    'issue' => 'not_encrypted',
                    'severity' => $isSecure ? 'medium' : 'high',
                    'recommendation' => 'Encrypt this secret using SecretManager::encryptSecret()',
                ];
            }
        }

        return $issues;
    }
}
