package io.quarkiverse.operatorsdk.runtime;

import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET;
import static io.quarkiverse.operatorsdk.runtime.Constants.QOSDK_USE_BUILDTIME_NAMESPACES_SET;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;

import org.jboss.logging.Logger;

import io.fabric8.kubernetes.client.KubernetesClient;
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
import io.javaoperatorsdk.operator.api.config.InformerStoppedHandler;
import io.javaoperatorsdk.operator.api.config.LeaderElectionConfiguration;
import io.javaoperatorsdk.operator.api.monitoring.Metrics;
import io.quarkus.arc.Arc;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.runtime.configuration.ConfigUtils;

@Recorder
public class ConfigurationServiceRecorder {

    static final Logger log = Logger.getLogger(ConfigurationServiceRecorder.class.getName());
    private final RuntimeValue<RunTimeOperatorConfiguration> runTimeConfigurationRuntimeValue;

    public ConfigurationServiceRecorder(RuntimeValue<RunTimeOperatorConfiguration> runTimeConfigurationRuntimeValue) {
        this.runTimeConfigurationRuntimeValue = runTimeConfigurationRuntimeValue;
    }

    @SuppressWarnings({ "rawtypes", "unchecked", "resource" })
    public Supplier<QuarkusConfigurationService> configurationServiceSupplier(
            BuildTimeConfigurationService buildTimeConfigurationService,
            Map<String, QuarkusBuildTimeControllerConfiguration<?>> configurations) {
        final var runTimeConfiguration = runTimeConfigurationRuntimeValue.getValue();
        final var maxThreads = runTimeConfiguration.concurrentReconciliationThreads()
                .orElse(ConfigurationService.DEFAULT_RECONCILIATION_THREADS_NUMBER);
        final var timeout = runTimeConfiguration.terminationTimeoutSeconds()
                .orElse(QuarkusConfigurationService.UNSET_TERMINATION_TIMEOUT_SECONDS);
        final var workflowThreads = runTimeConfiguration.concurrentWorkflowThreads()
                .orElse(ConfigurationService.DEFAULT_WORKFLOW_EXECUTOR_THREAD_NUMBER);
        final var cacheSyncTimeout = runTimeConfiguration.cacheSyncTimeout();
        final var asyncStart = runTimeConfiguration.asyncStart();

        final var externalControllerConfigs = runTimeConfiguration.controllers();
        final List<QuarkusControllerConfiguration> runtimeConfigurations = configurations.values().stream().map(c -> {
            final var extConfig = externalControllerConfigs.get(c.getName());

            // then override with controller-specific configuration if present
            if (extConfig != null) {
                extConfig.finalizer().ifPresent(c::setFinalizer);
                extConfig.selector().ifPresent(c::setLabelSelector);
                extConfig.fieldSelectors().ifPresent(c::setFieldSelector);
                extConfig.maxReconciliationInterval().ifPresent(c::setMaxReconciliationInterval);
                c.updateRetryConfiguration(extConfig.retry());
                setNamespacesFromRuntime(c, extConfig.namespaces());
            }

            // if the namespaces weren't set on controller level or as an annotation, use the operator-level configuration if it exists
            if (!c.isWereNamespacesSet()) {
                setNamespacesFromRuntime(c, runTimeConfiguration.namespaces());
            }

            return new QuarkusControllerConfiguration(c.getInformerConfig(), c.getName(), c.isGenerationAware(),
                    c.getAssociatedReconcilerClassName(), c.getRetry(), c.getRateLimiter(),
                    c.getMaxReconciliationInterval(), c.getFinalizerName(), c.fieldManager(), c.getWorkflow(),
                    c.getResourceTypeName(), c.getResourceClass());
        }).toList();

        return () -> {
            final var container = Arc.container();

            // deactivate leader election in dev mode
            LeaderElectionConfiguration leaderElectionConfiguration = null;
            final var profiles = ConfigUtils.getProfiles();
            if (buildTimeConfigurationService.activateLeaderElection(profiles)) {
                leaderElectionConfiguration = container.instance(LeaderElectionConfiguration.class).get();
            } else {
                log.infof(
                        "Leader election deactivated because it is only activated for %s profiles. Currently active profiles: %s",
                        buildTimeConfigurationService.getLeaderElectionActivationProfiles(),
                        profiles);
            }

            return new QuarkusConfigurationService(
                    buildTimeConfigurationService.getVersion(),
                    runtimeConfigurations,
                    container.instance(KubernetesClient.class).get(),
                    buildTimeConfigurationService.getCrdInfo(),
                    maxThreads,
                    workflowThreads,
                    timeout,
                    cacheSyncTimeout,
                    asyncStart,
                    container.instance(Metrics.class).get(),
                    buildTimeConfigurationService.isStartOperator(),
                    leaderElectionConfiguration,
                    container.instance(InformerStoppedHandler.class).orElse(null),
                    buildTimeConfigurationService.isCloseClientOnStop(),
                    buildTimeConfigurationService.isStopOnInformerErrorDuringStartup(),
                    buildTimeConfigurationService.isEnableSSA(),
                    buildTimeConfigurationService.isDefensiveCloning());
        };
    }

    @SuppressWarnings({ "rawtypes", "unchecked", "OptionalUsedAsFieldOrParameterType" })
    private static void setNamespacesFromRuntime(QuarkusBuildTimeControllerConfiguration controllerConfig,
            Optional<List<String>> runtimeNamespaces) {
        // The namespaces field has a default value so that we are able to detect if the configuration value is set to "".
        // Setting the value to "" will reset the configuration and result in an empty Optional.
        // Not setting the value at all will result in the default being applied, which we can test for.
        if (runtimeNamespaces.isPresent()) {
            final var namespaces = new HashSet<>(runtimeNamespaces.get());
            // If it's not the default value, use it because it was set.
            // If it is the default value, ignore it and let any build time config be used.
            if (!QOSDK_USE_BUILDTIME_NAMESPACES_SET.equals(namespaces)) {
                controllerConfig.setNamespaces(namespaces);
            }
        } else {
            // Value has been explicitly reset (value was empty string), use all namespaces mode
            controllerConfig.setNamespaces(DEFAULT_NAMESPACES_SET);
        }
    }
}
