/*
 * Copyright Strimzi authors.
 * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
 */
package io.strimzi.systemtest.upgrade;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import io.strimzi.systemtest.Environment;
import io.strimzi.systemtest.utils.TestKafkaVersion;
import io.strimzi.test.TestUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.params.provider.Arguments;

import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Stream;

/**
 * This Class is a basic data loader that takes type of upgrade as a constructor parameter
 * and loads only data for that specific type of upgrade
 */
public class VersionModificationDataLoader {
    public enum ModificationType {
        OLM_UPGRADE,
        BUNDLE_UPGRADE,
        BUNDLE_DOWNGRADE;
    }
    private static final Logger LOGGER = LogManager.getLogger(VersionModificationDataLoader.class);
    private OlmVersionModificationData olmUpgradeData;
    private List<BundleVersionModificationData> bundleVersionModificationDataList;

    public VersionModificationDataLoader(ModificationType upgradeType) {
        if (upgradeType == ModificationType.OLM_UPGRADE) {
            loadOlmUpgradeData();
        } else if (upgradeType == ModificationType.BUNDLE_UPGRADE) {
            loadBundleUpgradeData();
        } else if (upgradeType == ModificationType.BUNDLE_DOWNGRADE) {
            loadBundleDowngradeData();
        }
    }

    public void loadBundleUpgradeData() {
        try {
            ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
            CollectionType modificationDataListType = mapper.getTypeFactory().constructCollectionType(List.class, BundleVersionModificationData.class);
            List<BundleVersionModificationData> upgradeDatalist = mapper.readValue(new File(TestUtils.USER_PATH + "/src/test/resources/upgrade/BundleUpgrade.yaml"), modificationDataListType);

            upgradeDatalist.forEach(upgradeData -> {
                // Set upgrade data destination to latest version which is HEAD
                upgradeData.setToUrl("HEAD");
                upgradeData.setToVersion("HEAD");
                upgradeData.setToExamples("HEAD");
            });
            this.bundleVersionModificationDataList = upgradeDatalist;
        } catch (IOException e) {
            LOGGER.error("Error while parsing ST data from YAML ");
            throw new RuntimeException(e);
        }
    }

    public void loadBundleDowngradeData() {
        try {
            ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
            CollectionType modificationDataListType = mapper.getTypeFactory().constructCollectionType(List.class, BundleVersionModificationData.class);
            this.bundleVersionModificationDataList = mapper.readValue(new File(TestUtils.USER_PATH + "/src/test/resources/upgrade/BundleDowngrade.yaml"), modificationDataListType);
        } catch (IOException e) {
            LOGGER.error("Error while parsing ST data from YAML ");
            throw new RuntimeException(e);
        }
    }

    public void loadOlmUpgradeData() {
        try {
            ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
            this.olmUpgradeData = mapper.readValue(new File(TestUtils.USER_PATH + "/src/test/resources/upgrade/OlmUpgrade.yaml"), OlmVersionModificationData.class);
            // By default upgrade to the latest released version
            this.olmUpgradeData.setToVersion(Environment.OLM_OPERATOR_LATEST_RELEASE_VERSION);
            // Create and set procedures -> replace kafka version after operator upgrade.
            // This is needed, because for upgrade it's generated by method which generated data for parametrized test
            this.olmUpgradeData.setProcedures(new UpgradeKafkaVersion(TestKafkaVersion.getDefaultSupportedKafkaVersion()));
            this.olmUpgradeData.setToUrl("HEAD");
        } catch (IOException e) {
            LOGGER.error("Error while parsing ST data from YAML ");
            throw new RuntimeException(e);
        }
    }

    public OlmVersionModificationData getOlmUpgradeData() {
        return olmUpgradeData;
    }

    public List<BundleVersionModificationData> getBundleUpgradeOrDowngradeDataList() {
        return bundleVersionModificationDataList;
    }

    public BundleVersionModificationData getBundleUpgradeOrDowngradeData(final int index) {
        return bundleVersionModificationDataList.get(index);
    }

    public int getBundleUpgradeOrDowngradeDataSize() {
        return bundleVersionModificationDataList.size();
    }

    public BundleVersionModificationData buildDataForUpgradeAcrossVersions() {
        List<TestKafkaVersion> sortedVersions = TestKafkaVersion.getSupportedKafkaVersions();
        TestKafkaVersion latestKafkaSupported = sortedVersions.get(sortedVersions.size() - 1);

        BundleVersionModificationData acrossUpgradeData = getBundleUpgradeOrDowngradeData(getBundleUpgradeOrDowngradeDataList().size() - 1);
        BundleVersionModificationData startingVersion = acrossUpgradeData;

        startingVersion.setDefaultKafka(acrossUpgradeData.getDefaultKafkaVersionPerStrimzi());

        acrossUpgradeData.setFromVersion(startingVersion.getFromVersion());
        acrossUpgradeData.setFromExamples(startingVersion.getFromExamples());
        acrossUpgradeData.setFromUrl(startingVersion.getFromUrl());
        acrossUpgradeData.setStartingKafkaVersion(startingVersion.getOldestKafka());
        acrossUpgradeData.setDefaultKafka(startingVersion.getDefaultKafka());
        acrossUpgradeData.setOldestKafka(startingVersion.getOldestKafka());

        // Generate procedures for upgrade
        UpgradeKafkaVersion procedures = new UpgradeKafkaVersion(latestKafkaSupported.version());

        acrossUpgradeData.setProcedures(procedures);

        LOGGER.info("Upgrade data for the test: {}", acrossUpgradeData.toString());

        return acrossUpgradeData;
    }

    public static Stream<Arguments> loadYamlDowngradeData() {
        VersionModificationDataLoader dataLoader = new VersionModificationDataLoader(ModificationType.BUNDLE_DOWNGRADE);
        List<Arguments> parameters = new LinkedList<>();

        dataLoader.getBundleUpgradeOrDowngradeDataList().forEach(downgradeData -> {
            parameters.add(Arguments.of(downgradeData.getFromVersion(), downgradeData.getToVersion(), downgradeData));
        });

        return parameters.stream();
    }

    public static Stream<Arguments> loadYamlUpgradeData() {
        VersionModificationDataLoader upgradeDataList = new VersionModificationDataLoader(ModificationType.BUNDLE_UPGRADE);
        List<Arguments> parameters = new LinkedList<>();

        List<TestKafkaVersion> testKafkaVersions = TestKafkaVersion.getSupportedKafkaVersions();
        TestKafkaVersion testKafkaVersion = testKafkaVersions.get(testKafkaVersions.size() - 1);

        // Generate procedures for upgrade
        UpgradeKafkaVersion procedures = new UpgradeKafkaVersion(testKafkaVersion.version());

        upgradeDataList.getBundleUpgradeOrDowngradeDataList().forEach(upgradeData -> {
            upgradeData.setProcedures(procedures);
            parameters.add(Arguments.of(
                upgradeData.getFromVersion(), upgradeData.getToVersion(),
                upgradeData.getFeatureGatesBefore(), upgradeData.getFeatureGatesAfter(),
                upgradeData
            ));
        });

        return parameters.stream();
    }
}
