package org.arquillian.cube.openshift.impl.client;

import io.fabric8.kubernetes.api.model.v3_1.HasMetadata;
import io.fabric8.kubernetes.api.model.v3_1.KubernetesListBuilder;
import io.fabric8.kubernetes.api.model.v3_1.KubernetesResource;
import io.fabric8.kubernetes.api.model.v3_1.Pod;
import io.fabric8.kubernetes.api.model.v3_1.PodBuilder;
import io.fabric8.kubernetes.api.model.v3_1.Service;
import io.fabric8.kubernetes.clnt.v3_1.Config;
import io.fabric8.openshift.api.model.v3_1.Build;
import io.fabric8.openshift.api.model.v3_1.BuildConfig;
import io.fabric8.openshift.api.model.v3_1.BuildConfigBuilder;
import io.fabric8.openshift.api.model.v3_1.BuildRequest;
import io.fabric8.openshift.api.model.v3_1.BuildRequestBuilder;
import io.fabric8.openshift.api.model.v3_1.ImageStream;
import io.fabric8.openshift.api.model.v3_1.ImageStreamBuilder;
import io.fabric8.openshift.clnt.v3_1.DefaultOpenShiftClient;
import io.fabric8.openshift.clnt.v3_1.NamespacedOpenShiftClient;
import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.arquillian.cube.openshift.impl.model.Template;
import org.arquillian.cube.openshift.impl.model.Template.TemplateImageRef;

import static org.arquillian.cube.openshift.impl.client.ResourceUtil.waitForStart;

public class OpenShiftClient {

    private String namespace;

    private NamespacedOpenShiftClient kubernetes;
    private GitServer gitserver;
    private boolean keepAliveGitServer;

    public OpenShiftClient(Config config, String namespace, boolean keepAliveGitServer) {
        this.kubernetes = new DefaultOpenShiftClient(config);
        this.namespace = namespace;
        this.keepAliveGitServer = keepAliveGitServer;
        this.gitserver = new GitServer(this.getClient(), config, namespace);
    }

    public List<Exception> clean(ResourceHolder holder) {
        List<Exception> exceptions = new ArrayList<Exception>();
        List<HasMetadata> resourcesToDelete = new ArrayList<HasMetadata>();
        for (KubernetesResource resource : holder.getResources()) {
            if (resource instanceof HasMetadata) {
                resourcesToDelete.add((HasMetadata) resource);
            }
        }
        try {
            getClient().lists().delete(new KubernetesListBuilder().withItems(resourcesToDelete).build());
        } catch (Exception e) {
            exceptions.add(e);
        }
        return exceptions;
    }

    public ResourceHolder build(Template<Pod> template) throws Exception {
        ResourceHolder holder = new ResourceHolder();
        Map<String, String> defaultLabels = getDefaultLabels();

        if (template.getRefs().size() == 0) {
            Pod service = createStartablePod(template, defaultLabels);
            holder.setPod(service);
            return holder;
        }

        for (TemplateImageRef ref : template.getRefs()) {
            URI repoUri = gitserver.push(new File(ref.getPath()), ref.getContainerName());

            String runID = ref.getContainerName();

            try {

                ImageStream is = new ImageStreamBuilder()
                    .withNewMetadata()
                    .withName(runID)
                    .withNamespace(namespace)
                    .withLabels(defaultLabels)
                    .endMetadata()
                    .build();
                is = getClientExt().imageStreams().createOrReplace(is);
                holder.addResource(is);

                BuildConfig config = new BuildConfigBuilder()
                    .withNewMetadata()
                    .withName(runID)
                    .withNamespace(namespace)
                    .withLabels(defaultLabels)
                    .endMetadata()
                    .withNewSpec()
                    .withNewSource()
                    .withNewGit()
                    .withUri(repoUri.toString())
                    .withRef("master")
                    .endGit()
                    .endSource()
                    .withNewStrategy()
                    .withType("Docker")
                    .withNewDockerStrategy()
                    .withNoCache(false)
                    .endDockerStrategy()
                    .endStrategy()
                    .withNewOutput()
                    .withNewTo()
                    .withKind("ImageStreamTag")
                    .withName(runID + ":latest")
                    .endTo()
                    .endOutput()
                    .endSpec()
                    .build();

                config = getClientExt().buildConfigs().createOrReplace(config);
                holder.addResource(config);

                final Long lastBuildVersion = config.getStatus().getLastVersion();
                BuildRequest br = new BuildRequestBuilder()
                    .withNewMetadata()
                    .withName(config.getMetadata().getName())
                    .withLabels(defaultLabels)
                    .endMetadata()
                    .build();

                getClientExt().buildConfigs().inNamespace(namespace).withName(runID).instantiate(br);
                Build build = ResourceUtil.waitForComplete(
                    getClientExt(),
                    getClientExt().builds().inNamespace(namespace)
                        .withName(String.format("%s-%d", config.getMetadata().getName(), (lastBuildVersion + 1)))
                        .get());

                holder.addResource(build);

                is = getClientExt().imageStreams().inNamespace(namespace).withName(is.getMetadata().getName()).get();

                String imageRef = is.getStatus().getTags().get(0).getItems().get(0).getDockerImageReference();
                template.resolve(ref, imageRef);

                Pod service = createStartablePod(template, defaultLabels);
                holder.setPod(service);
            } catch (Exception e) {
                holder.setException(e);
            }
        }
        return holder;
    }

    private Pod createStartablePod(Template<Pod> template, Map<String, String> defaultLabels) {
        Map<String, String> allLabels = new HashMap<String, String>();
        allLabels.putAll(defaultLabels);
        allLabels.putAll(template.getTarget().getMetadata().getLabels());
        Pod service = new PodBuilder()
            .withNewMetadataLike(template.getTarget().getMetadata())
            .withLabels(allLabels)
            .endMetadata()
            .withNewSpecLike(template.getTarget().getSpec())
            .endSpec()
            .build();
        return service;
    }

    public Pod createAndWait(Pod resource) throws Exception {
        return waitForStart(
            getClient(),
            getClient().pods().inNamespace(namespace).create(resource));
    }

    public Service create(Service resource) throws Exception {
        return (Service) getClient().services().inNamespace(namespace).create(resource);
    }

    public void destroy(Pod resource) throws Exception {
        getClient().pods().inNamespace(namespace).withName(resource.getMetadata().getName()).delete();
    }

    public void destroy(Service resource) throws Exception {
        getClient().services().inNamespace(namespace).withName(resource.getMetadata().getName()).delete();
    }

    public Pod update(Pod resource) throws Exception {
        return getClient().resource(resource).createOrReplace();
    }

    public void shutdown() throws Exception {
        if (!keepAliveGitServer) {
            gitserver.shutdown();
        }
    }

    public NamespacedOpenShiftClient getClient() {
        return kubernetes;
    }

    public io.fabric8.openshift.clnt.v3_1.OpenShiftClient getClientExt() {
        return kubernetes;
    }

    public String getNamespace() {
        return namespace;
    }

    private Map<String, String> getDefaultLabels() {
        Map<String, String> labels = new HashMap<String, String>();
        labels.put("generatedby", "arquillian");
        return labels;
    }

    public static class ResourceHolder {

        public Pod pod;
        public Set<KubernetesResource> resources;
        private Exception exception;

        public ResourceHolder() {
            this(null);
        }

        public ResourceHolder(Pod pod) {
            this.pod = pod;
            this.resources = new HashSet<KubernetesResource>();
        }

        public Exception getException() {
            return exception;
        }

        public void setException(Exception exception) {
            this.exception = exception;
        }

        public void addResource(KubernetesResource resource) {
            this.resources.add(resource);
        }

        public Set<KubernetesResource> getResources() {
            if (resources == null) {
                return new HashSet<KubernetesResource>();
            }
            return resources;
        }

        public Pod getPod() {
            return pod;
        }

        public void setPod(Pod pod) {
            this.pod = pod;
        }
    }
}
