/*
 * Copyright 2000-2020 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jetbrains.buildServer.clouds.kubernetes.podSpec;

import io.fabric8.kubernetes.api.model.*;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import java.io.File;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;

import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
import io.fabric8.kubernetes.api.model.apps.DeploymentSpecBuilder;
import jetbrains.buildServer.BaseTestCase;
import jetbrains.buildServer.TempFiles;
import jetbrains.buildServer.clouds.CloudInstanceUserData;
import jetbrains.buildServer.clouds.kubernetes.*;
import jetbrains.buildServer.clouds.kubernetes.auth.KubeAuthStrategyProvider;
import jetbrains.buildServer.clouds.kubernetes.auth.UnauthorizedAccessStrategy;
import jetbrains.buildServer.clouds.kubernetes.podSpec.BuildAgentPodTemplateProvider;
import jetbrains.buildServer.clouds.kubernetes.podSpec.DeploymentBuildAgentPodTemplateProvider;
import jetbrains.buildServer.clouds.kubernetes.podSpec.DeploymentContentProvider;
import jetbrains.buildServer.serverSide.BuildServerListener;
import jetbrains.buildServer.serverSide.ServerPaths;
import jetbrains.buildServer.serverSide.ServerSettings;
import jetbrains.buildServer.serverSide.executors.ExecutorServices;
import jetbrains.buildServer.util.CollectionsUtil;
import jetbrains.buildServer.util.EventDispatcher;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.util.Collections;

/**
 * Created by ekoshkin ([email protected]) on 17.06.17.
 */
@Test
public class DeploymentBuildAgentPodTemplateProviderTest extends BaseTestCase {
    private Mockery m;
    private BuildAgentPodTemplateProvider myPodTemplateProvider;
    private UnauthorizedAccessStrategy myAuthStrategy = new UnauthorizedAccessStrategy();
    private DeploymentContentProvider myDeploymentContentProvider;
    private KubePodNameGenerator myNameGenerator;

    @BeforeMethod
    @Override
    protected void setUp() throws Exception {
        super.setUp();
        m = new Mockery();
        ServerSettings serverSettings = m.mock(ServerSettings.class);
        KubeAuthStrategyProvider authStrategies = m.mock(KubeAuthStrategyProvider.class);
        myDeploymentContentProvider = m.mock(DeploymentContentProvider.class);
        final ExecutorServices executorServices = m.mock(ExecutorServices.class);
        m.checking(new Expectations(){{
            allowing(serverSettings).getServerUUID(); will(returnValue("server uuid"));
            allowing(authStrategies).get(with(UnauthorizedAccessStrategy.ID)); will(returnValue(myAuthStrategy));
            ScheduledExecutorService ses = new ScheduledThreadPoolExecutor(1);
            allowing(executorServices).getNormalExecutorService(); will(returnValue(ses));
        }});
        TempFiles tempFiles = new TempFiles();
        final ServerPaths serverPaths = new ServerPaths(tempFiles.createTempDir());
        final EventDispatcher<BuildServerListener> eventDispatcher = EventDispatcher.create(BuildServerListener.class);
        myNameGenerator = new KubePodNameGeneratorImpl(serverPaths, executorServices, eventDispatcher);
        myPodTemplateProvider = new DeploymentBuildAgentPodTemplateProvider(serverSettings, myDeploymentContentProvider);
    }

    @AfterMethod
    @Override
    protected void tearDown() throws Exception {
        m.assertIsSatisfied();
        super.tearDown();
    }

    public void testGetPodTemplate_UnknownDeployment() throws Exception {
        CloudInstanceUserData instanceTag = createInstanceTag();
        KubeCloudClientParameters clientParams = m.mock(KubeCloudClientParameters.class);
        KubeCloudImage image = m.mock(KubeCloudImage.class);
        m.checking(new Expectations(){{
            allowing(image).getSourceDeploymentName(); will(returnValue("deploymentFoo"));
            allowing(image).getAgentNamePrefix(); will(returnValue("agent-name-prefix"));
            allowing(image).findInstanceById(with("agent-name-prefix-1")); will(returnValue(null));
            allowing(myDeploymentContentProvider).findDeployment(with(any(String.class)), with(any(KubeCloudClientParameters.class))); will(returnValue(null));
        }});
        assertExceptionThrown(() -> myPodTemplateProvider.getPodTemplate(myNameGenerator.generateNewVmName(image), instanceTag, image, clientParams), KubeCloudException.class);
    }

    public void testGetPodTemplate() throws Exception {
        CloudInstanceUserData instanceTag = createInstanceTag();
        KubeCloudClientParameters clientParams = m.mock(KubeCloudClientParameters.class);
        KubeCloudImage image = m.mock(KubeCloudImage.class);
        Deployment deployment = new DeploymentBuilder()
                .withMetadata(new ObjectMetaBuilder()
                        .withLabels(CollectionsUtil.asMap("app", "nginx"))
                        .build())
                .withSpec(new DeploymentSpecBuilder()
                        .withTemplate(new PodTemplateSpecBuilder()
                                .withMetadata(new ObjectMeta())
                                .withSpec(new PodSpecBuilder()
                                        .withContainers(new ContainerBuilder()
                                                .withName("nginx")
                                                .withImage("nginx:1.7.9")
                                                .withPorts(new ContainerPortBuilder()
                                                        .withHostPort(80)
                                                        .build())
                                                .build())
                                        .build())
                                .build())
                        .build())
                .build();
        m.checking(new Expectations(){{
            allowing(clientParams).getNamespace(); will(returnValue("custom namespace"));
            allowing(clientParams).getAuthStrategy(); will(returnValue(UnauthorizedAccessStrategy.ID));
            allowing(image).getId(); will(returnValue("my image id"));
            allowing(image).getName(); will(returnValue("my image name"));
            allowing(image).getSourceDeploymentName(); will(returnValue("deploymentFoo"));
            allowing(image).getAgentNamePrefix(); will(returnValue("agent-name-prefix"));
            allowing(image).findInstanceById(with("agent-name-prefix-1")); will(returnValue(null));
            allowing(image).getAgentName(with("agent name")); will(returnValue("prefix agent name"));
            allowing(myDeploymentContentProvider).findDeployment(with(any(String.class)), with(any(KubeCloudClientParameters.class))); will(returnValue(deployment));
        }});
        Pod podTemplate = myPodTemplateProvider.getPodTemplate(myNameGenerator.generateNewVmName(image), instanceTag, image, clientParams);
        assertNotNull(podTemplate);
        assertNotNull(podTemplate.getMetadata());
        assertNotNull(podTemplate.getSpec());
    }

    public void testShouldNotsetContainerName(){
        CloudInstanceUserData instanceTag = createInstanceTag();
        KubeCloudClientParameters clientParams = m.mock(KubeCloudClientParameters.class);
        KubeCloudImage image = m.mock(KubeCloudImage.class);
        Deployment deployment = new DeploymentBuilder()
                .withMetadata(new ObjectMetaBuilder()
                        .withLabels(CollectionsUtil.asMap("app", "nginx"))
                        .build())
                .withSpec(new DeploymentSpecBuilder()
                        .withTemplate(new PodTemplateSpecBuilder()
                                .withMetadata(new ObjectMeta())
                                .withSpec(new PodSpecBuilder()
                                        .withContainers(new ContainerBuilder()
                                                .withName("nginx")
                                                .withImage("nginx:1.7.9")
                                                .withPorts(new ContainerPortBuilder()
                                                        .withHostPort(80)
                                                        .build())
                                                .build())
                                        .build())
                                .build())
                        .build())
                .build();
        m.checking(new Expectations(){{
            allowing(clientParams).getNamespace(); will(returnValue("custom namespace"));
            allowing(image).getId(); will(returnValue("my image id"));
            allowing(image).getName(); will(returnValue("my image name"));
            allowing(image).getAgentNamePrefix(); will(returnValue("agent-name-prefix"));
            allowing(image).findInstanceById(with("agent-name-prefix-1")); will(returnValue(null));
            allowing(image).getSourceDeploymentName(); will(returnValue("deploymentFoo"));
            allowing(image).getAgentName(with("agent name")); will(returnValue("prefix agent name"));
            allowing(myDeploymentContentProvider).findDeployment(with(any(String.class)), with(any(KubeCloudClientParameters.class))); will(returnValue(deployment));
        }});
        Pod podTemplate = myPodTemplateProvider.getPodTemplate(myNameGenerator.generateNewVmName(image), instanceTag, image, clientParams);
        for(Container container : podTemplate.getSpec().getContainers()){
            assertNotSame(container.getName(), "agent name");
        }
    }

    public void testDoNotLoseSpecAdditionalProperties() throws Exception {
        CloudInstanceUserData instanceTag = createInstanceTag();
        KubeCloudClientParameters clientParams = m.mock(KubeCloudClientParameters.class);
        KubeCloudImage image = m.mock(KubeCloudImage.class);

        Deployment deployment = new DeploymentBuilder()
                .withMetadata(new ObjectMetaBuilder()
                        .withLabels(CollectionsUtil.asMap("app", "nginx"))
                        .build())
                .withSpec(new DeploymentSpecBuilder()
                        .withTemplate(new PodTemplateSpecBuilder()
                                .withMetadata(new ObjectMeta())
                                .withSpec(new PodSpecBuilder()
                                        .withContainers(new ContainerBuilder()
                                                .withName("nginx")
                                                .withImage("nginx:1.7.9")
                                                .withPorts(new ContainerPortBuilder()
                                                        .withHostPort(80)
                                                        .build())
                                                .build())
                                        .build())
                                .build())
                        .build())
                .build();

        deployment.getSpec().getTemplate().getSpec().setAdditionalProperty("affinity", "some value");

        m.checking(new Expectations(){{
            allowing(clientParams).getNamespace(); will(returnValue("custom namespace"));
            allowing(image).getId(); will(returnValue("my image id"));
            allowing(image).getName(); will(returnValue("my image name"));
            allowing(image).getAgentNamePrefix(); will(returnValue("agent-name-prefix"));
            allowing(image).findInstanceById(with("agent-name-prefix-1")); will(returnValue(null));
            allowing(image).getSourceDeploymentName(); will(returnValue("deploymentFoo"));
            allowing(image).getAgentName(with("agent name")); will(returnValue("prefix agent name"));
            allowing(myDeploymentContentProvider).findDeployment(with(any(String.class)), with(any(KubeCloudClientParameters.class))); will(returnValue(deployment));
        }});

        Pod podTemplate = myPodTemplateProvider.getPodTemplate(myNameGenerator.generateNewVmName(image), instanceTag, image, clientParams);

        assertNotEmpty(podTemplate.getSpec().getAdditionalProperties().keySet());
    }

    private CloudInstanceUserData createInstanceTag() {
        return new CloudInstanceUserData("agent name", "auth token", "server address", null, "profile id", "profile description", Collections.emptyMap());
    }
}