/*
 * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
 * (the "License"). You may not use this work except in compliance with the License, which is
 * available at www.apache.org/licenses/LICENSE-2.0
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied, as more fully set forth in the License.
 *
 * See the NOTICE file distributed with this work for information regarding copyright ownership.
 */

package alluxio.master.file;

import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;

import alluxio.AlluxioURI;
import alluxio.conf.Configuration;
import alluxio.conf.PropertyKey;
import alluxio.master.CoreMasterContext;
import alluxio.master.MasterRegistry;
import alluxio.master.MasterTestUtils;
import alluxio.master.block.BlockMaster;
import alluxio.master.block.BlockMasterFactory;
import alluxio.master.file.meta.InodeTree;
import alluxio.master.journal.JournalSystem;
import alluxio.master.journal.JournalTestUtils;
import alluxio.master.journal.JournalType;
import alluxio.master.metrics.MetricsMasterFactory;
import alluxio.metrics.MetricsSystem;
import alluxio.underfs.UfsFileStatus;
import alluxio.underfs.UfsStatus;
import alluxio.underfs.UnderFileSystem;
import alluxio.underfs.UnderFileSystemConfiguration;
import alluxio.underfs.local.LocalUnderFileSystem;
import alluxio.underfs.options.DeleteOptions;
import alluxio.underfs.options.GetStatusOptions;
import alluxio.util.ThreadFactoryUtils;
import alluxio.util.executor.ExecutorServiceFactories;
import alluxio.util.io.PathUtils;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;

import java.io.IOException;
import java.io.OutputStream;
import java.time.Clock;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FileSystemMasterSyncMetadataTestBase {
  protected static final AlluxioURI ROOT = new AlluxioURI("/");
  protected static final String TEST_DIR_PREFIX = "/dir";
  protected static final String TEST_FILE_PREFIX = "/file";
  @Rule
  public TemporaryFolder mTempDir = new TemporaryFolder();
  protected String mUfsUri;
  protected FlakyLocalUnderFileSystem mUfs;
  protected ExecutorService mFileSystemExecutorService;
  protected ExecutorService mUfsStateCacheExecutorService;
  protected MasterRegistry mRegistry;
  protected DefaultFileSystemMaster mFileSystemMaster;

  protected InodeTree mInodeTree;

  protected static UfsStatus createUfsStatusWithName(String name) {
    return new UfsFileStatus(name, "hash", 0, 0L, "owner", "group", (short) 0, null, 0);
  }

  @Before
  public void before() throws Exception {
    Configuration.reloadProperties();

    mTempDir.create();
    mUfsUri = mTempDir.newFolder().getAbsolutePath();

    mUfs = new FlakyLocalUnderFileSystem(new AlluxioURI(mUfsUri),
        UnderFileSystemConfiguration.defaults(Configuration.global()));
    PowerMockito.mockStatic(UnderFileSystem.Factory.class);
    Mockito.when(UnderFileSystem.Factory.createWithRecorder(anyString(), any(), any()))
        .thenReturn(mUfs);

    Configuration.set(PropertyKey.MASTER_JOURNAL_TYPE, JournalType.UFS);
    Configuration.set(PropertyKey.MASTER_MOUNT_TABLE_ROOT_UFS, mUfsUri);
    Configuration.set(PropertyKey.MASTER_METADATA_SYNC_INSTRUMENT_EXECUTOR, true);
    String journalFolderUri = mTempDir.newFolder().getAbsolutePath();

    mFileSystemExecutorService = Executors
        .newFixedThreadPool(4, ThreadFactoryUtils.build("FileSystemMaster-%d", true));
    mUfsStateCacheExecutorService = Executors
        .newFixedThreadPool(4, ThreadFactoryUtils.build("UfsStateCache-%d", true));
    mRegistry = new MasterRegistry();
    JournalSystem journalSystem =
        JournalTestUtils.createJournalSystem(journalFolderUri);
    CoreMasterContext context = MasterTestUtils.testMasterContext(journalSystem);
    new MetricsMasterFactory().create(mRegistry, context);
    BlockMaster blockMaster = new BlockMasterFactory().create(mRegistry, context);
    mFileSystemMaster = new DefaultFileSystemMaster(blockMaster, context,
        ExecutorServiceFactories.constantExecutorServiceFactory(mFileSystemExecutorService),
        Clock.systemUTC());
    mInodeTree = mFileSystemMaster.getInodeTree();
    mRegistry.add(FileSystemMaster.class, mFileSystemMaster);
    journalSystem.start();
    journalSystem.gainPrimacy();
    mRegistry.start(true);

    MetricsSystem.resetAllMetrics();
  }

  @After
  public void after() throws Exception {
    mRegistry.stop();
    mFileSystemExecutorService.shutdown();
    mUfsStateCacheExecutorService.shutdown();
  }

  protected void createUfsDir(String path) throws IOException {
    mUfs.mkdirs(PathUtils.concatPath(mUfsUri, path));
  }

  protected OutputStream createUfsFile(String path) throws IOException {
    return mUfs.create(PathUtils.concatPath(mUfsUri, path));
  }

  protected void cleanupUfs() throws IOException {
    assertTrue(mUfs.deleteDirectory(mUfsUri, DeleteOptions.defaults().setRecursive(true)));
    assertTrue(mUfs.mkdirs(mUfsUri));
  }

  protected static class FlakyLocalUnderFileSystem extends LocalUnderFileSystem {
    public boolean mThrowIOException = false;
    public boolean mThrowRuntimeException = false;
    public boolean mIsSlow = false;
    public long mSlowTimeMs = 2000L;

    public List<String> mFailedPaths = new ArrayList<>();

    public FlakyLocalUnderFileSystem(AlluxioURI uri, UnderFileSystemConfiguration conf) {
      super(uri, conf);
    }

    @Override
    public UfsStatus getStatus(String path, GetStatusOptions options) throws IOException {
      for (String failedPathsString: mFailedPaths) {
        if (path.contains(failedPathsString)) {
          throw new RuntimeException();
        }
      }

      if (mThrowRuntimeException) {
        throw new RuntimeException();
      }
      if (mThrowIOException) {
        throw new IOException();
      }
      if (mIsSlow) {
        try {
          Thread.sleep(mSlowTimeMs);
        } catch (InterruptedException e) {
          throw new RuntimeException(e);
        }
      }
      return super.getStatus(path, options);
    }

    @Override
    public UfsStatus[] listStatus(String path) throws IOException {
      for (String failedPathsString: mFailedPaths) {
        if (path.contains(failedPathsString)) {
          throw new RuntimeException();
        }
      }

      if (mThrowRuntimeException) {
        throw new RuntimeException();
      }
      if (mThrowIOException) {
        throw new IOException();
      }
      if (mIsSlow) {
        try {
          Thread.sleep(mSlowTimeMs);
        } catch (InterruptedException e) {
          throw new RuntimeException(e);
        }
      }
      return super.listStatus(path);
    }
  }

  protected void createUfsHierarchy(int level, int maxLevel, String prefix, int numPerLevel)
      throws IOException {
    if (level >= maxLevel) {
      return;
    }
    for (int i = 0; i < numPerLevel; ++i) {
      String dirPath = prefix + "/" + level + "_" + i;
      if (level < maxLevel - 1) {
        createUfsDir(dirPath);
      } else {
        createUfsFile(dirPath).close();
      }
      createUfsHierarchy(level + 1, maxLevel, dirPath, numPerLevel);
    }
  }
}

