package de.gecheckt.pdf.umbenenner.adapter.out.validation;

import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;

import de.gecheckt.pdf.umbenenner.application.config.provider.AiProviderFamily;
import de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor;
import de.gecheckt.pdf.umbenenner.application.validation.editor.ApiKeyResolutionPort;

/**
 * Implementierung des {@link ApiKeyResolutionPort}, die Umgebungsvariablen aus der
 * Systemprozessumgebung liest und den effektiven API-Key-Herkunftsdeskriptor zurückgibt.
 * <p>
 * Die Vorrangregel lautet:
 * <ol>
 *   <li>Providerspezifische Umgebungsvariable ({@code ANTHROPIC_API_KEY} für Claude,
 *       {@code OPENAI_COMPATIBLE_API_KEY} für OpenAI-kompatibel)</li>
 *   <li>Bei {@code openai-compatible}: Legacy-Variable {@code PDF_UMBENENNER_API_KEY}</li>
 *   <li>Property-Wert aus dem Editor-Feld ({@code propertyValue})</li>
 *   <li>{@code ABSENT}, wenn keine Quelle einen Wert liefert</li>
 * </ol>
 * <p>
 * Verwendet dieselben ENV-Variablen-Namen wie der bestehende headless Konfigurationspfad, so dass
 * GUI-Validierung und headless Bootstrap identisch auflösen.
 * <p>
 * Für Tests kann der Umgebungslookup injiziert werden, ohne den echten Prozess-Umgebungszustand
 * zu verändern.
 */
public class EnvironmentApiKeyResolutionAdapter implements ApiKeyResolutionPort {

    /** Providerspezifische Umgebungsvariable für den Claude-Provider. */
    static final String ENV_CLAUDE_API_KEY = "ANTHROPIC_API_KEY";

    /** Providerspezifische Umgebungsvariable für den OpenAI-kompatiblen Provider. */
    static final String ENV_OPENAI_API_KEY = "OPENAI_COMPATIBLE_API_KEY";

    /** Legacy-Umgebungsvariable für den OpenAI-kompatiblen Provider (Rückwärtskompatibilität). */
    static final String ENV_LEGACY_OPENAI_API_KEY = "PDF_UMBENENNER_API_KEY";

    private final Function<String, String> environmentLookup;

    /**
     * Erstellt einen Adapter, der die echte Prozessumgebung liest.
     */
    public EnvironmentApiKeyResolutionAdapter() {
        this(System::getenv);
    }

    /**
     * Erstellt einen Adapter mit einem injizierten Umgebungslookup.
     * <p>
     * Dieser Konstruktor erlaubt deterministisches Testen ohne Änderungen am echten
     * Prozess-Umgebungszustand.
     *
     * @param environmentLookup Funktion zum Lesen von Umgebungsvariablen; darf nicht {@code null} sein
     */
    EnvironmentApiKeyResolutionAdapter(Function<String, String> environmentLookup) {
        this.environmentLookup = Objects.requireNonNull(environmentLookup,
                "environmentLookup must not be null");
    }

    /**
     * Ermittelt die Herkunft des effektiven API-Schlüssels für den angegebenen Provider.
     *
     * @param family         die Provider-Familie; darf nicht {@code null} sein
     * @param propertyValue  aktueller Property-Wert aus dem Editor; darf nicht {@code null} sein
     * @return der Herkunftsdeskriptor; nie {@code null}
     */
    @Override
    public EffectiveApiKeyDescriptor resolve(AiProviderFamily family, String propertyValue) {
        Objects.requireNonNull(family, "family must not be null");
        Objects.requireNonNull(propertyValue, "propertyValue must not be null");

        return switch (family) {
            case CLAUDE -> resolveClaude(propertyValue);
            case OPENAI_COMPATIBLE -> resolveOpenAiCompatible(propertyValue);
        };
    }

    private EffectiveApiKeyDescriptor resolveClaude(String propertyValue) {
        String envValue = environmentLookup.apply(ENV_CLAUDE_API_KEY);
        if (isPresent(envValue)) {
            return EffectiveApiKeyDescriptor.fromProviderEnvVar(ENV_CLAUDE_API_KEY);
        }
        if (isPresent(propertyValue)) {
            return EffectiveApiKeyDescriptor.fromPropertyFile();
        }
        return EffectiveApiKeyDescriptor.absent();
    }

    private EffectiveApiKeyDescriptor resolveOpenAiCompatible(String propertyValue) {
        String primaryEnv = environmentLookup.apply(ENV_OPENAI_API_KEY);
        if (isPresent(primaryEnv)) {
            return EffectiveApiKeyDescriptor.fromProviderEnvVar(ENV_OPENAI_API_KEY);
        }
        String legacyEnv = environmentLookup.apply(ENV_LEGACY_OPENAI_API_KEY);
        if (isPresent(legacyEnv)) {
            return EffectiveApiKeyDescriptor.fromLegacyEnvVar(ENV_LEGACY_OPENAI_API_KEY);
        }
        if (isPresent(propertyValue)) {
            return EffectiveApiKeyDescriptor.fromPropertyFile();
        }
        return EffectiveApiKeyDescriptor.absent();
    }

    /**
     * Liefert den effektiven API-Schlüssel-Rohwert anhand derselben Vorrangregel wie {@link #resolve}.
     *
     * @param family        die Provider-Familie; darf nicht {@code null} sein
     * @param propertyValue aktueller Property-Wert aus dem Editor; darf nicht {@code null} sein
     * @return der effektive Rohwert, falls verfügbar; sonst leer
     */
    @Override
    public Optional<String> resolveEffectiveApiKeyValue(AiProviderFamily family, String propertyValue) {
        Objects.requireNonNull(family, "family must not be null");
        Objects.requireNonNull(propertyValue, "propertyValue must not be null");

        return switch (family) {
            case CLAUDE -> resolveClaudeValue(propertyValue);
            case OPENAI_COMPATIBLE -> resolveOpenAiCompatibleValue(propertyValue);
        };
    }

    private Optional<String> resolveClaudeValue(String propertyValue) {
        String envValue = environmentLookup.apply(ENV_CLAUDE_API_KEY);
        if (isPresent(envValue)) {
            return Optional.of(envValue);
        }
        if (isPresent(propertyValue)) {
            return Optional.of(propertyValue);
        }
        return Optional.empty();
    }

    private Optional<String> resolveOpenAiCompatibleValue(String propertyValue) {
        String primaryEnv = environmentLookup.apply(ENV_OPENAI_API_KEY);
        if (isPresent(primaryEnv)) {
            return Optional.of(primaryEnv);
        }
        String legacyEnv = environmentLookup.apply(ENV_LEGACY_OPENAI_API_KEY);
        if (isPresent(legacyEnv)) {
            return Optional.of(legacyEnv);
        }
        if (isPresent(propertyValue)) {
            return Optional.of(propertyValue);
        }
        return Optional.empty();
    }

    private static boolean isPresent(String value) {
        return value != null && !value.isBlank();
    }
}
