/*
 * 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.vmware;

import com.intellij.util.WaitFor;
import com.vmware.vim25.mo.Datacenter;
import com.vmware.vim25.mo.Task;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.rmi.RemoteException;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import jetbrains.buildServer.BaseTestCase;
import jetbrains.buildServer.clouds.CloudImageParameters;
import jetbrains.buildServer.clouds.CloudInstanceUserData;
import jetbrains.buildServer.clouds.CloudProfile;
import jetbrains.buildServer.clouds.InstanceStatus;
import jetbrains.buildServer.clouds.base.connector.CloudAsyncTaskExecutor;
import jetbrains.buildServer.clouds.base.tasks.UpdateInstancesTask;
import jetbrains.buildServer.clouds.base.types.CloneBehaviour;
import jetbrains.buildServer.clouds.server.impl.profile.CloudClientParametersImpl;
import jetbrains.buildServer.clouds.server.impl.profile.CloudImageDataImpl;
import jetbrains.buildServer.clouds.server.impl.profile.CloudImageParametersImpl;
import jetbrains.buildServer.clouds.vmware.connector.VMWareApiConnector;
import jetbrains.buildServer.clouds.vmware.errors.VmwareCheckedCloudException;
import jetbrains.buildServer.clouds.vmware.stubs.FakeApiConnector;
import jetbrains.buildServer.clouds.vmware.stubs.FakeModel;
import jetbrains.buildServer.clouds.vmware.stubs.FakeVirtualMachine;
import jetbrains.buildServer.clouds.vmware.tasks.VmwareUpdateTaskManager;
import jetbrains.buildServer.util.FileUtil;
import jetbrains.buildServer.util.TestFor;
import jetbrains.buildServer.util.ThreadUtil;
import org.jetbrains.annotations.NotNull;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

/**
 * @author Sergey.Pak
 *         Date: 5/20/2014
 *         Time: 3:16 PM
 */
@Test
public class VmwareCloudImageTest extends BaseTestCase {

  private CloudAsyncTaskExecutor myTaskExecutor;
  private VMWareApiConnector myApiConnector;
  private VmwareCloudImage myImage;
  private VmwareCloudImageDetails myImageDetails;
  private File myIdxStorage;
  private UpdateInstancesTask<VmwareCloudInstance, VmwareCloudImage, VMWareCloudClient> myUpdateTask;
  private VMWareCloudClient myCloudClient;
  private CloudProfile myProfile;


  @BeforeMethod
  public void setUp() throws Exception {
    super.setUp();
    FakeModel.instance().clear();
    myTaskExecutor = new CloudAsyncTaskExecutor("Test-vmware");
    myApiConnector = new FakeApiConnector(VmwareCloudIntegrationTest.TEST_SERVER_UUID, VmwareCloudIntegrationTest.PROFILE_ID);
    myIdxStorage = createTempDir();

    myProfile = VmwareTestUtils.createProfileFromProps(new CloudClientParametersImpl(Collections.emptyMap(), Collections.emptyList()));

    Map<String, String> params = new HashMap<>();
    params.put("nickname", "imageNickname");
    params.put("sourceVmName", "srcVM");
    params.put("snapshot", "srcVMSnap");
    params.put("folder", "folderId");
    params.put("pool", "rpId");
    params.put("behaviour", CloneBehaviour.FRESH_CLONE.toString());
    params.put("maxInstances", "5");
    CloudImageParameters imageParameters = new CloudImageParametersImpl(new CloudImageDataImpl(params), myProfile.getProjectId(), UUID.randomUUID().toString());

    myImageDetails = new VmwareCloudImageDetails(imageParameters);

    FakeModel.instance().addDatacenter("dc2");
    FakeModel.instance().addFolder("folderId").setParent("dc2", Datacenter.class);
    FakeModel.instance().addVM("srcVM").setParentFolder("folderId");
    FakeModel.instance().addResourcePool("rpId").setParentFolder("folderId");

    FakeModel.instance().addVMSnapshot("srcVM", "srcVMSnap");

    myImage = new VmwareCloudImage(myApiConnector, myImageDetails, myTaskExecutor, myIdxStorage, myProfile);

    myCloudClient = new VMWareCloudClient(myProfile, myApiConnector, new VmwareUpdateTaskManager(), myIdxStorage);
    myCloudClient.populateImagesData(Collections.singletonList(myImageDetails));
    myUpdateTask = new UpdateInstancesTask<VmwareCloudInstance, VmwareCloudImage, VMWareCloudClient>(myApiConnector, myCloudClient, 10*1000, false);
  }

  public void check_can_start_new_instance_limits() throws RemoteException, InterruptedException {
    final CloudInstanceUserData data = new CloudInstanceUserData("aaa", "bbbb", "localhost", 10000l, "profileDescr", Collections.<String, String>emptyMap());
    assertTrue(myImage.canStartNewInstance());
    myImage.startNewInstance(data);
    assertTrue(myImage.canStartNewInstance());
    myImage.startNewInstance(data);
    assertTrue(myImage.canStartNewInstance());
    myImage.startNewInstance(data);
    assertTrue(myImage.canStartNewInstance());
    myImage.startNewInstance(data);
    assertTrue(myImage.canStartNewInstance());
    final VmwareCloudInstance instance2Stop = myImage.startNewInstance(data);
    assertFalse(myImage.canStartNewInstance());
    new WaitFor(5*1000){

      @Override
      protected boolean condition() {
        return instance2Stop.getStatus() == InstanceStatus.RUNNING;
      }
    };
    final FakeVirtualMachine vm2Stop = FakeModel.instance().getVirtualMachine(instance2Stop.getName());
    final String result = vm2Stop.powerOffVM_Task().waitForTask();
    assertEquals(Task.SUCCESS, result);
    instance2Stop.setStatus(InstanceStatus.STOPPED);
    assertTrue(myImage.canStartNewInstance());
    System.setProperty(VmwareConstants.CONSIDER_STOPPED_VMS_LIMIT, "true");
    assertFalse(myImage.canStartNewInstance());
    System.getProperties().remove(VmwareConstants.CONSIDER_STOPPED_VMS_LIMIT);
    assertTrue(myImage.canStartNewInstance());
  }

  public void terminate_instance_if_cant_reconfigure() throws IOException {
    final CloudInstanceUserData data = new CloudInstanceUserData("aaa", "bbbb", "localhost", 10000l, "profileDescr", Collections.<String, String>emptyMap());
    final AtomicBoolean stopInstanceCalled = new AtomicBoolean();
    myApiConnector = new FakeApiConnector(VmwareCloudIntegrationTest.TEST_SERVER_UUID, VmwareCloudIntegrationTest.PROFILE_ID){
      @Override
      public Task reconfigureInstance(@NotNull final VmwareCloudInstance instance, @NotNull final String agentName, @NotNull final CloudInstanceUserData userData)
        throws VmwareCheckedCloudException {
        return FakeVirtualMachine.failureTask();
      }

      @Override
      public Task stopInstance(@NotNull final VmwareCloudInstance instance) {
        stopInstanceCalled.set(true);
        return super.stopInstance(instance);
      }
    };
    myImage = new VmwareCloudImage(myApiConnector, myImageDetails, myTaskExecutor, myIdxStorage, myProfile);

    myCloudClient = new VMWareCloudClient(myProfile, myApiConnector, new VmwareUpdateTaskManager(), createTempDir());
    myCloudClient.populateImagesData(Collections.singletonList(myImageDetails));

    myImage.startNewInstance(data);

    new WaitFor(1000){

      @Override
      protected boolean condition() {
        return stopInstanceCalled.get();
      }
    };

    assertTrue("Should have stopped if can't reconfigure", stopInstanceCalled.get());
  }

  @TestFor(issues = "TW-54729")
  public void check_name_generator_doesnt_use_disk() throws IOException, InterruptedException {
    final Set<String> generatedNames = new HashSet<>();
    Thread nameGenerator = new Thread(()->{
      for (int i=0; i<10000; i++){
        if (Thread.currentThread().isInterrupted())
          break;
        generatedNames.add(myImage.generateNewVmName());
      }
    });
    nameGenerator.start();
    new WaitFor(500){

      @Override
      protected boolean condition() {
        return generatedNames.size() == 10000;
      }
    };
    assertEquals(10000, generatedNames.size());
    nameGenerator.join();
    assertEquals("1", FileUtil.readText(new File(myIdxStorage, myImage.getImageDetails().getSourceId() + ".idx")));
    myImage.storeIdx();
    assertEquals("10001", FileUtil.readText(new File(myIdxStorage, myImage.getImageDetails().getSourceId() + ".idx")));
  }

  public void should_generate_unique_name() throws IOException {
    VmwareCloudImage sameImage = new VmwareCloudImage(myApiConnector, myImageDetails, myTaskExecutor, myIdxStorage, myProfile){
      @Override
      protected Set<String> getInstanceIds() {
        return createSet("imageNickname-1", "imageNickname-3");
      }
    };
    assertEquals("1", FileUtil.readText(new File(myIdxStorage, myImage.getImageDetails().getSourceId() + ".idx")));
    assertEquals("imageNickname-2", sameImage.generateNewVmName());
    assertEquals("imageNickname-4", sameImage.generateNewVmName());

  }

  public void should_store_idx_on_dispose() throws IOException {
    new WaitFor(500){

      @Override
      protected boolean condition() {
        return myCloudClient.isInitialized();
      }
    };
    VmwareCloudImage img = myCloudClient.getImages().iterator().next();
    assertEquals("imageNickname-1", img.generateNewVmName());
    File file = new File(myIdxStorage, img.getImageDetails().getSourceId() + ".idx");
    assertEquals("1", FileUtil.readText(file));
    myCloudClient.dispose();
    assertEquals( "2", FileUtil.readText(file));
  }

  public void reset_idx_when_file_unreadable() throws IOException {
    final File idxFile = new File(myIdxStorage, myImage.getImageDetails().getSourceId() + ".idx");
    FileUtil.writeFileAndReportErrors(idxFile, "aaaaaaaaaasdasfdfsgfsgeaweewq");
    VmwareCloudImage sameImage = new VmwareCloudImage(myApiConnector, myImageDetails, myTaskExecutor, myIdxStorage, myProfile){
      @Override
      protected Set<String> getInstanceIds() {
        return createSet("imageNickname-1", "imageNickname-3");
      }
    };
    assertEquals("imageNickname-2", sameImage.generateNewVmName());
    assertEquals("imageNickname-4", sameImage.generateNewVmName());

  }

  @AfterMethod
  public void tearDown() throws Exception {
    super.tearDown();
  }

}