/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.internal.cache.backup;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.entry;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.RestoreSystemProperties;
import org.junit.experimental.categories.Category;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import org.apache.geode.cache.DiskStore;
import org.apache.geode.distributed.DistributedSystem;
import org.apache.geode.internal.DeployedJar;
import org.apache.geode.internal.JarDeployer;
import org.apache.geode.internal.cache.DirectoryHolder;
import org.apache.geode.internal.cache.DiskInitFile;
import org.apache.geode.internal.cache.DiskStoreImpl;
import org.apache.geode.internal.cache.InternalCache;
import org.apache.geode.internal.cache.Oplog;
import org.apache.geode.test.junit.categories.IntegrationTest;

@PowerMockIgnore("*.IntegrationTest")
@PrepareForTest({BackupFileCopier.class, URL.class})
@RunWith(PowerMockRunner.class)
@Category(IntegrationTest.class)
public class BackupFileCopierIntegrationTest {
  private static final String CONFIG_DIRECTORY = "config";
  private static final String USER_FILES = "user";
  private static final String CACHE_XML = "cache.xml";
  private static final String PROPERTIES_FILE = "geode.properties";

  @Rule
  public TemporaryFolder tempFolder = new TemporaryFolder();

  @Rule
  public RestoreSystemProperties restoreProperties = new RestoreSystemProperties();

  private InternalCache cache;
  private TemporaryBackupFiles tempFiles;
  private Path tempFilesLocation;
  private BackupFileCopier fileCopier;

  @Before
  public void setup() throws IOException {
    cache = mock(InternalCache.class);
    tempFiles = mock(TemporaryBackupFiles.class);
    tempFilesLocation = tempFolder.newFolder("temporaryBackupFiles").toPath();
    when(tempFiles.getDirectory()).thenReturn(tempFilesLocation);
    fileCopier = new BackupFileCopier(cache, tempFiles);
  }

  @Test
  public void configFilesNotCreatedIfDoNotExist() throws IOException {
    System.setProperty(DistributedSystem.PROPERTIES_FILE_PROPERTY,
        tempFolder.getRoot().getAbsolutePath() + "/nonexistant");
    fileCopier.copyConfigFiles();
    Path cacheXmlDestination = tempFilesLocation.resolve(CONFIG_DIRECTORY).resolve(CACHE_XML);
    assertThat(cacheXmlDestination).doesNotExist();
    Path propertiesFileDestination =
        tempFilesLocation.resolve(CONFIG_DIRECTORY).resolve(PROPERTIES_FILE);
    assertThat(propertiesFileDestination).doesNotExist();
    assertThat(fileCopier.getBackupDefinition().getConfigFiles()).isEmpty();
  }

  @Test
  public void throwsIOExceptionIfConfigFileLocationInvalid() throws URISyntaxException {
    URL badUrl = PowerMockito.mock(URL.class);
    PowerMockito.doThrow(new URISyntaxException("", "")).when(badUrl).toURI();
    when(cache.getCacheXmlURL()).thenReturn(badUrl);

    assertThatThrownBy(() -> fileCopier.copyConfigFiles()).isInstanceOf(IOException.class);
  }

  @Test
  public void copiesConfigFilesToCorrectLocation() throws IOException {
    File propertiesFile = tempFolder.newFile(PROPERTIES_FILE);
    System.setProperty(DistributedSystem.PROPERTIES_FILE_PROPERTY,
        propertiesFile.getAbsolutePath());

    File cacheXml = tempFolder.newFile("cache.xml");
    URL cacheXmlURL = cacheXml.toURI().toURL();
    when(cache.getCacheXmlURL()).thenReturn(cacheXmlURL);

    fileCopier.copyConfigFiles();

    Path cacheXmlDestination = tempFilesLocation.resolve(CONFIG_DIRECTORY).resolve(CACHE_XML);
    assertThat(cacheXmlDestination).exists();
    Path propertiesFileDestination =
        tempFilesLocation.resolve(CONFIG_DIRECTORY).resolve(PROPERTIES_FILE);
    assertThat(propertiesFileDestination).exists();
    assertThat(fileCopier.getBackupDefinition().getConfigFiles())
        .containsExactlyInAnyOrder(cacheXmlDestination, propertiesFileDestination);
  }

  @Test
  public void noJarsInBackupIfNoneExist() throws IOException {
    fileCopier.copyDeployedJars();

    Path jarsDir = tempFilesLocation.resolve(USER_FILES);
    assertThat(Files.list(jarsDir)).isEmpty();
    assertThat(fileCopier.getBackupDefinition().getDeployedJars()).isEmpty();
  }

  @Test
  public void copiesDeployedJarsToCorrectLocation() throws IOException {
    File myJarFile = tempFolder.newFile("myJar.jar");
    JarDeployer deployer = mock(JarDeployer.class);
    DeployedJar deployedJar = mock(DeployedJar.class);
    when(deployedJar.getFileCanonicalPath()).thenReturn(myJarFile.getCanonicalPath());
    when(deployer.findDeployedJars()).thenReturn(Collections.singletonList(deployedJar));
    fileCopier = spy(fileCopier);
    doReturn(deployer).when(fileCopier).getJarDeployer();

    fileCopier.copyDeployedJars();

    Path expectedJar = tempFilesLocation.resolve(USER_FILES).resolve("myJar.jar");
    assertThat(expectedJar).exists();
    assertThat(fileCopier.getBackupDefinition().getDeployedJars().keySet())
        .containsExactly(expectedJar);
  }

  @Test
  public void userDirectoryEmptyIfNoUserFiles() throws IOException {
    fileCopier.copyUserFiles();

    Path userDir = tempFilesLocation.resolve(USER_FILES);
    assertThat(Files.list(userDir)).isEmpty();
    assertThat(fileCopier.getBackupDefinition().getUserFiles()).isEmpty();
  }

  @Test
  public void userDirectoryContainsCorrectFiles() throws IOException {
    File userFile = tempFolder.newFile("userFile");
    File userSubdir = tempFolder.newFolder("userSubfolder");
    List<File> userFiles = new ArrayList<>();
    userFiles.add(userFile);
    userFiles.add(userSubdir);
    when(cache.getBackupFiles()).thenReturn(userFiles);

    fileCopier.copyUserFiles();

    Path expectedUserFile = tempFilesLocation.resolve(USER_FILES).resolve("userFile");
    Path expectedUserSubdir = tempFilesLocation.resolve(USER_FILES).resolve("userSubfolder");
    assertThat(expectedUserFile).exists();
    assertThat(expectedUserSubdir).exists();
    assertThat(fileCopier.getBackupDefinition().getUserFiles().keySet())
        .containsExactlyInAnyOrder(expectedUserFile, expectedUserSubdir);
  }

  @Test
  public void containsCorrectDiskInitFile() throws IOException {
    File initFileToCopy = tempFolder.newFile("initFile");
    DiskStoreImpl diskStore = mock(DiskStoreImpl.class);
    DiskInitFile initFile = mock(DiskInitFile.class);
    when(diskStore.getDiskInitFile()).thenReturn(initFile);
    when(diskStore.getInforFileDirIndex()).thenReturn(13);
    when(initFile.getIFFile()).thenReturn(initFileToCopy);

    fileCopier.copyDiskInitFile(diskStore);

    Path expectedInitFile = tempFilesLocation.resolve("13").resolve(initFileToCopy.getName());
    assertThat(expectedInitFile).exists();
    assertThat(fileCopier.getBackupDefinition().getDiskInitFiles())
        .containsExactly(entry(diskStore, expectedInitFile));
  }

  @Test
  public void copiesAllFilesForOplog() throws IOException {
    File oplogDir = tempFolder.newFolder("oplogDir");
    File crfFile = new File(oplogDir, "crf");
    File drfFile = new File(oplogDir, "drf");
    File krfFile = new File(oplogDir, "krf");
    Files.createFile(crfFile.toPath());
    Files.createFile(drfFile.toPath());
    Files.createFile(krfFile.toPath());

    Oplog oplog = mock(Oplog.class);
    DirectoryHolder dirHolder = mock(DirectoryHolder.class);
    when(dirHolder.getDir()).thenReturn(oplogDir);
    when(oplog.getDirectoryHolder()).thenReturn(dirHolder);
    when(oplog.getCrfFile()).thenReturn(crfFile);
    when(oplog.getDrfFile()).thenReturn(drfFile);
    when(oplog.getKrfFile()).thenReturn(krfFile);
    DiskStore diskStore = mock(DiskStore.class);
    when(tempFiles.getDiskStoreDirectory(any(), any()))
        .thenReturn(tempFolder.newFolder("diskstores").toPath());

    fileCopier.copyOplog(diskStore, oplog);

    Path expectedCrfFile =
        tempFiles.getDiskStoreDirectory(diskStore, dirHolder).resolve(crfFile.getName());
    Path expectedDrfFile =
        tempFiles.getDiskStoreDirectory(diskStore, dirHolder).resolve(drfFile.getName());
    Path expectedKrfFile =
        tempFiles.getDiskStoreDirectory(diskStore, dirHolder).resolve(krfFile.getName());
    assertThat(expectedCrfFile).exists();
    assertThat(expectedDrfFile).exists();
    assertThat(expectedKrfFile).exists();
    assertThat(fileCopier.getBackupDefinition().getOplogFilesByDiskStore())
        .containsOnlyKeys(diskStore);
    assertThat(fileCopier.getBackupDefinition().getOplogFilesByDiskStore().get(diskStore))
        .containsExactlyInAnyOrder(expectedCrfFile, expectedDrfFile, expectedKrfFile);
  }

  @Test
  public void copiesOplogFilesIfHardlinksFail() throws IOException {
    File oplogDir = tempFolder.newFolder("oplogDir");
    File crfFile = new File(oplogDir, "crf");
    File drfFile = new File(oplogDir, "drf");
    File krfFile = new File(oplogDir, "krf");
    Files.createFile(crfFile.toPath());
    Files.createFile(drfFile.toPath());
    Files.createFile(krfFile.toPath());

    Oplog oplog = mock(Oplog.class);
    DirectoryHolder dirHolder = mock(DirectoryHolder.class);
    when(dirHolder.getDir()).thenReturn(oplogDir);
    when(oplog.getDirectoryHolder()).thenReturn(dirHolder);
    when(oplog.getCrfFile()).thenReturn(crfFile);
    when(oplog.getDrfFile()).thenReturn(drfFile);
    when(oplog.getKrfFile()).thenReturn(krfFile);
    DiskStore diskStore = mock(DiskStore.class);
    Path diskStoreDir = tempFolder.newFolder("diskstores").toPath();
    when(tempFiles.getDiskStoreDirectory(any(), any())).thenReturn(diskStoreDir);

    PowerMockito.mockStatic(Files.class);
    PowerMockito.doThrow(new IOException()).when(Files.class);
    Files.createLink(any(), any());

    fileCopier.copyOplog(diskStore, oplog);

    Path expectedCrfFile =
        tempFiles.getDiskStoreDirectory(diskStore, dirHolder).resolve(crfFile.getName());
    Path expectedDrfFile =
        tempFiles.getDiskStoreDirectory(diskStore, dirHolder).resolve(drfFile.getName());
    Path expectedKrfFile =
        tempFiles.getDiskStoreDirectory(diskStore, dirHolder).resolve(krfFile.getName());
    assertThat(expectedCrfFile).exists();
    assertThat(expectedDrfFile).exists();
    assertThat(expectedKrfFile).exists();
    assertThat(fileCopier.getBackupDefinition().getOplogFilesByDiskStore())
        .containsOnlyKeys(diskStore);
    assertThat(fileCopier.getBackupDefinition().getOplogFilesByDiskStore().get(diskStore))
        .containsExactlyInAnyOrder(expectedCrfFile, expectedDrfFile, expectedKrfFile);
  }
}
