package mpern.sap.commerce.ccv2;

import groovy.json.JsonSlurper;
import groovy.lang.Tuple2;
import mpern.sap.commerce.build.HybrisPlugin;
import mpern.sap.commerce.build.HybrisPluginExtension;
import mpern.sap.commerce.build.tasks.HybrisAntTask;
import mpern.sap.commerce.ccv2.model.Addon;
import mpern.sap.commerce.ccv2.model.Aspect;
import mpern.sap.commerce.ccv2.model.Manifest;
import mpern.sap.commerce.ccv2.model.Property;
import mpern.sap.commerce.ccv2.model.TestConfiguration;
import mpern.sap.commerce.ccv2.model.Webapp;
import mpern.sap.commerce.ccv2.tasks.GenerateLocalextensions;
import mpern.sap.commerce.ccv2.tasks.PatchLocalExtensions;
import org.gradle.api.Action;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.tasks.Copy;
import org.gradle.api.tasks.Delete;
import org.gradle.api.tasks.WriteProperties;

import java.io.File;
import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;

public class CloudV2Plugin implements Plugin<Project> {

    public static final String EXTENSION_PACK = "cloudExtensionPack";
    private static final String GROUP = "CCv2 Build";
    private static final String MANIFEST_PATH = "manifest.json";
    private CCv2Extension extension;

    @Override
    public void apply(Project project) {

        File manifestFile = project.file(MANIFEST_PATH);

        if (!manifestFile.exists()) {
            throw new InvalidUserDataException(MANIFEST_PATH + " not found!");
        }
        JsonSlurper slurper = new JsonSlurper();
        Map parsed = (Map) slurper.parse(manifestFile);
        Manifest manifest = Manifest.fromMap(parsed);

        extension = project.getExtensions().create("CCV2", CCv2Extension.class, project, manifest);
        extension.getGeneratedConfiguration().set(project.file("generated-configuration"));
        extension.getCloudExtensionPackFolder().set(project.file("cloud-extension-pack"));

        final Configuration extensionPack = project.getConfigurations().create(EXTENSION_PACK);
        extensionPack.defaultDependencies(deps -> {
            //de/hybris/platform/hybris-cloud-extension-pack/1905.06/
            String commerceSuiteVersion = manifest.commerceSuiteVersion;
            String cepVersion = commerceSuiteVersion.replaceAll("(.+)(\\.\\d\\d)?", "$1.+");

            deps.add(project.getDependencies().create("de.hybris.platform:hybris-cloud-extension-pack:" + cepVersion + "@zip"));
        });


        project.getPlugins().withType(HybrisPlugin.class, hybrisPlugin -> {
            Object byName = project.getExtensions().getByName(HybrisPlugin.HYBRIS_EXTENSION);
            if (byName instanceof HybrisPluginExtension) {
                ((HybrisPluginExtension) byName).getVersion().set(project.provider(() -> manifest.commerceSuiteVersion));
                configureAddonInstall(project, manifest.storefrontAddons);
                configureTests(project, manifest.tests);
                configureWebTests(project, manifest.webTests);
                configureCloudExtensionPackBootstrap(project, manifest.useCloudExtensionPack);
            }
        });
        configurePropertyFileGeneration(project, manifest);
        configureExtensionGeneration(project, manifest);
    }

    private void configureAddonInstall(Project project, List<Addon> storefrontAddons) {
        if (storefrontAddons.isEmpty()) {
            return;
        }
        Task installManifestAddons = project.getTasks().create("installManifestAddons");
        installManifestAddons.setGroup(GROUP);
        installManifestAddons.setDescription("runs ant addoninstall for all addons configured in manifest.json");

        Map<Tuple2<String, String>, Set<String>> addonsPerStorefront = new HashMap<>();
        for (Addon c : storefrontAddons) {
            Tuple2<String, String> templateStorefront = new Tuple2<>(c.template, c.storefront);
            addonsPerStorefront.computeIfAbsent(templateStorefront, t -> new LinkedHashSet<>()).add(c.addon);
        }
        for (Map.Entry<Tuple2<String, String>, Set<String>> tuple2SetEntry : addonsPerStorefront.entrySet()) {
            Tuple2<String, String> templateStorefront = tuple2SetEntry.getKey();
            Set<String> addons = tuple2SetEntry.getValue();
            HybrisAntTask install = project.getTasks().create(
                    String.format("addonInstall_%s_%s", templateStorefront.getFirst(), templateStorefront.getSecond()),
                    HybrisAntTask.class,
                    t -> {
                        t.args("addoninstall");
                        t.antProperty("addonnames", String.join(",", addons));
                        t.antProperty("addonStorefront." + templateStorefront.getFirst(), templateStorefront.getSecond());
                    });
            installManifestAddons.dependsOn(install);
        }
    }

    private void configureTests(Project project, TestConfiguration tests) {
        if (tests == TestConfiguration.NO_VALUE) {
            return;
        }
        HybrisAntTask allTests = project.getTasks().create("cloudTests", HybrisAntTask.class, configureTest(tests));
        allTests.setArgs(Collections.singletonList("alltests"));
        allTests.setGroup(GROUP);
        allTests.setDescription("run ant alltests with manifest configuration");
    }

    private void configureWebTests(Project project, TestConfiguration test) {
        if (test == TestConfiguration.NO_VALUE) {
            return;
        }
        HybrisAntTask allWebTests = project.getTasks().create("cloudWebTests", HybrisAntTask.class, configureTest(test));
        allWebTests.setArgs(Collections.singletonList("allwebtests"));
        allWebTests.setGroup(GROUP);
        allWebTests.setDescription("run ant allwebtests with manifest configuration");
    }

    private void configureCloudExtensionPackBootstrap(Project project, boolean useCloudExtensionPack) {
        if (!useCloudExtensionPack) {
            return;
        }
        Task cleanCep = project.getTasks().create("cleanCloudExtensionPack", Delete.class, d -> {
            d.delete(extension.getCloudExtensionPackFolder().getAsFile());
        });
        Copy unpackCep = project.getTasks().create("unpackCloudExtensionPack", Copy.class, c -> {
            c.dependsOn(cleanCep);
            c.from(project.provider(() -> project.getConfigurations()
                            .getByName(EXTENSION_PACK)
                            .getFiles()
                            .stream()
                            .map(project::zipTree)
                            .collect(Collectors.toSet())
                    )
            );
            c.into(extension.getCloudExtensionPackFolder().getAsFile());
            c.doLast(a -> project.getConfigurations().getByName(EXTENSION_PACK)
                    .getResolvedConfiguration()
                    .getFirstLevelModuleDependencies()
                    .forEach(r -> a.getLogger().lifecycle("Using Cloud Extension Pack: {}", r.getModuleVersion())));
        });

        Task bootstrapPlatform = project.getTasks().getByName("bootstrapPlatform");
        bootstrapPlatform.dependsOn(unpackCep);
        String reservedTypeCodes = "hybris/bin/platform/ext/core/resources/core/unittest/reservedTypecodes.txt";
        Copy copyTypeCodes = project.getTasks().create("copyCEPTypeCode", Copy.class, c -> {
            c.mustRunAfter(unpackCep);
            c.mustRunAfter("unpackPlatform");
            c.from(extension.getCloudExtensionPackFolder().file(reservedTypeCodes));
            c.into(project.file(reservedTypeCodes).getParent());
        });
        bootstrapPlatform.dependsOn(copyTypeCodes);
        PatchLocalExtensions patch = project.getTasks().create("patchLocalExtensions", PatchLocalExtensions.class, p -> {
            p.getTarget().set(project.file("hybris/config/localextensions.xml"));
            p.getCepFolder().set(extension.getCloudExtensionPackFolder());
        });
        bootstrapPlatform.dependsOn(patch);
    }

    private void configurePropertyFileGeneration(Project project, Manifest manifest) {

        Map<String, Map<String, Object>> allProps = new TreeMap<>();
        for (Property property : manifest.properties) {
            Map<String, Object> props = allProps.computeIfAbsent(propertyFileKey("common", property.persona), k -> new LinkedHashMap<>());
            props.put(property.key, property.value);
        }
        for (Aspect aspect : manifest.aspects) {
            for (Property property : aspect.properties) {
                Map<String, Object> props = allProps.computeIfAbsent(propertyFileKey(aspect.name, property.persona), k -> new LinkedHashMap<>());
                props.put(property.key, property.value);
            }
            for (Webapp webapp : aspect.webapps) {
                Map<String, Object> props = allProps.computeIfAbsent(propertyFileKey(aspect.name, ""), k -> new LinkedHashMap<>());
                props.put(webapp.name + ".webroot", webapp.contextPath);
            }
        }
        Task generateCloudProperties = project.getTasks().create("generateCloudProperties");
        generateCloudProperties.setGroup(GROUP);
        generateCloudProperties.setDescription("generate property files per aspect and persona");
        for (Map.Entry<String, Map<String, Object>> properties : allProps.entrySet()) {
            WriteProperties w = project.getTasks().create("write_" + properties.getKey(), WriteProperties.class, t -> {
                t.setEncoding("UTF-8");
                t.setOutputFile(extension.getGeneratedConfiguration().file(properties.getKey() + ".properties"));
                t.setProperties(properties.getValue());
                t.getInputs().file(project.file(MANIFEST_PATH));
                t.setComment(String.format("GENERATED by task %s at %s", t.getName(), Instant.now()));
            });
            generateCloudProperties.dependsOn(w);
        }
    }

    private void configureExtensionGeneration(Project project, Manifest manifest) {
        project.getTasks().create("generateCloudLocalextensions", GenerateLocalextensions.class, t -> {
            t.setGroup(GROUP);
            t.setDescription("generate localextensions.xml based on manifest");

            t.getTarget().set(extension.getGeneratedConfiguration().file("localextensions.xml"));
            t.getCloudExtensions().set(manifest.extensions);
        });
    }

    private Action<HybrisAntTask> configureTest(TestConfiguration tests) {
        return t -> {
            String extensions = String.join(",", tests.extensions);
            String annotations = String.join(",", tests.annotations);
            String packages = String.join(",", tests.packages);
            Set<String> ex = new LinkedHashSet<>(tests.excludedPackages);
            ex.add("de.hybris.*");
            ex.add("com.hybris.*");
            String excludedPackages = String.join(",", ex);

            t.antProperty("testclasses.extensions", extensions);
            t.antProperty("testclasses.annotations", annotations);
            t.antProperty("testclasses.packages", packages);
            t.antProperty("testclasses.packages.excluded", excludedPackages);
        };
    }

    private String propertyFileKey(String prefix, String suffix) {
        return suffix.isEmpty() ? prefix : prefix + "_" + suffix;
    }
}