package io.apicurio.registry.cli.config;

import io.apicurio.registry.cli.common.CliException;
import io.apicurio.registry.cli.utils.Mapper;
import io.apicurio.registry.cli.utils.Output;
import io.quarkus.runtime.StartupEvent;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import lombok.Getter;
import lombok.Setter;

import java.io.IOException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;

import static io.apicurio.registry.cli.common.CliException.APPLICATION_ERROR_RETURN_CODE;
import static io.apicurio.registry.cli.utils.Mapper.copy;
import static io.apicurio.registry.cli.utils.Utils.isBlank;
import static java.lang.System.err;
import static java.lang.System.out;

@ApplicationScoped
public class Config {

    /**
     * Static reference for use by {@link AcrHomeConfigSource}, which is loaded via SPI
     * before CDI is available. Commands should use {@code @Inject Config} instead.
     */
    static Config instance;

    private ConfigModel cachedConfig;

    @Getter
    @Setter
    private Output stdOut = out::print;

    @Getter
    @Setter
    private Output stdErr = err::print;

    @Setter
    private Path acrCurrentHomePath;

    private final Map<String, String> envOverrides = new HashMap<>();

    void onStart(@Observes StartupEvent ev) {
        instance = this;
    }

    /**
     * Gets an environment variable value, checking overrides first.
     * This allows tests to inject environment variables without modifying the system environment.
     *
     * @param name the environment variable name
     * @return the environment variable value (from override or system), or null if not set
     */
    public String getEnv(final String name) {
        if (envOverrides.containsKey(name)) {
            return envOverrides.get(name);
        }
        return System.getenv(name);
    }

    /**
     * Sets an environment variable override for testing purposes.
     * The override takes precedence over the actual system environment variable.
     *
     * @param name  the environment variable name
     * @param value the value to return (null to simulate unset variable)
     */
    public void setEnvOverride(final String name, final String value) {
        envOverrides.put(name, value);
    }

    /**
     * Clears all environment variable overrides.
     * Should be called in test teardown to avoid test interference.
     */
    public void clearEnvOverrides() {
        envOverrides.clear();
    }

    public Path getAcrCurrentHomePath() {
        if (acrCurrentHomePath != null) {
            return acrCurrentHomePath;
        } else {
            var acrCurrentHome = System.getenv("ACR_CURRENT_HOME");
            if (isBlank(acrCurrentHome)) {
                throw new CliException("ACR_CURRENT_HOME environment variable is not set.", APPLICATION_ERROR_RETURN_CODE);
            }
            acrCurrentHomePath = Path.of(acrCurrentHome).normalize().toAbsolutePath();
        }
        return acrCurrentHomePath;
    }

    Path getConfigFilePath() {
        return getAcrCurrentHomePath().resolve("config.json");
    }

    public ConfigModel read() {
        if (cachedConfig == null) {
            var configPath = getConfigFilePath();
            try {
                cachedConfig = Mapper.MAPPER.readValue(configPath.toFile(), ConfigModel.class);
            } catch (IOException ex) {
                throw new CliException("Could not read config file '%s'.".formatted(configPath), ex, APPLICATION_ERROR_RETURN_CODE);
            }
        }
        return copy(cachedConfig);
    }

    public void write(ConfigModel config) {
        var configPath = getConfigFilePath();
        try {
            Mapper.MAPPER.writeValue(configPath.toFile(), config);
            cachedConfig = null;
        } catch (IOException ex) {
            throw new CliException("Could not write config file '%s'.".formatted(configPath), ex, APPLICATION_ERROR_RETURN_CODE);
        }
    }

    /**
     * Resets cached state. Should be called between tests to avoid interference.
     */
    public void reset() {
        cachedConfig = null;
        stdOut = out::print;
        stdErr = err::print;
        acrCurrentHomePath = null;
        envOverrides.clear();
    }
}
