<?php

declare(strict_types=1);

namespace App\Framework\Config;

use App\Framework\Config\Exceptions\RequiredEnvironmentVariableException;
use App\Framework\Filesystem\ValueObjects\FilePath;
use BackedEnum;

final readonly class Environment
{
    /**
     * @param array<string, mixed> $variables
     */
    public function __construct(
        private array $variables = []
    ) {
    }

    public function get(EnvKey|string $key, mixed $default = null): mixed
    {
        $key = $this->keyToString($key);

        // Priorität: 1. System ENV, 2. Loaded variables, 3. Default
        return /*$_ENV[$key] ?? $_SERVER[$key] ?? getenv($key) ?:*/ $this->variables[$key] ?? $default;
    }

    public function getRequired(EnvKey|string $key): mixed
    {
        $key = $this->keyToString($key);
        $value = $this->get($key);
        if ($value === null) {
            throw new RequiredEnvironmentVariableException($key);
        }

        return $value;
    }

    public function getInt(EnvKey|string $key, int $default = 0): int
    {
        $key = $this->keyToString($key);

        return (int) $this->get($key, $default);
    }

    public function getFloat(EnvKey|string $key, float $default = 0.0): float
    {
        $key = $this->keyToString($key);

        return (float) $this->get($key, $default);
    }

    public function getBool(EnvKey|string $key, bool $default = false): bool
    {
        $key = $this->keyToString($key);
        $value = $this->get($key, $default);
        if (is_string($value)) {
            return match (strtolower($value)) {
                'true', '1', 'yes', 'on' => true,
                'false', '0', 'no', 'off' => false,
                default => $default
            };
        }

        return (bool) $value;
    }

    public function getString(EnvKey|string $key, string $default = ''): string
    {
        $key = $this->keyToString($key);

        return (string) $this->get($key, $default);
    }

    /** @param class-string<BackedEnum> $enumClass */
    public function getEnum(EnvKey|string $key, string $enumClass, BackedEnum $default): object
    {
        $key = $this->keyToString($key);

        if (! $default instanceof $enumClass) {
            throw new \InvalidArgumentException('Default value must be an instance of the enum class');
        }

        return forward_static_call([$enumClass, 'tryFrom'], $key) ?? $default;

        #$enumClass::tryFrom($this->get($key, $default));
    }

    public function has(EnvKey|string $key): bool
    {
        $key = $this->keyToString($key);

        return $this->get($key) !== null;
    }

    /**
     * @return array<string, mixed>
     */
    public function all(): array
    {
        return array_merge(
            $_ENV,
            $_SERVER,
            $this->variables
        );
    }

    /**
     * Factory method für .env file loading
     */
    public static function fromFile(FilePath|string $envPath): self
    {
        $variables = [];
        $filePath = $envPath instanceof FilePath ? $envPath : FilePath::create($envPath);

        if ($filePath->exists() && $filePath->isReadable()) {
            $lines = file($filePath->toString(), FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);

            foreach ($lines as $line) {
                if (str_starts_with(trim($line), '#') || ! str_contains($line, '=')) {
                    continue;
                }

                [$name, $value] = explode('=', $line, 2);
                $name = trim($name);
                $value = trim($value);

                // Remove quotes
                if (str_starts_with($value, '"') && str_ends_with($value, '"')) {
                    $value = substr($value, 1, -1);
                }

                $variables[$name] = self::castValue($value);
            }
        }

        return new self($variables);
    }

    private static function castValue(string $value): mixed
    {
        return match (strtolower($value)) {
            'true' => true,
            'false' => false,
            'null' => null,
            default => is_numeric($value) ? (str_contains($value, '.') ? (float) $value : (int) $value) : $value
        };
    }

    public function keyToString(EnvKey|string $key): string
    {
        if (is_string($key)) {
            return $key;
        }

        return $key->value;
    }

    /**
     * Für Tests
     */
    public function withVariable(string $key, mixed $value): self
    {
        $variables = $this->variables;
        $variables[$key] = $value;

        return new self($variables);
    }

    /**
     * @param array<string, mixed> $variables
     */
    public function withVariables(array $variables): self
    {
        return new self(array_merge($this->variables, $variables));
    }
}
