// Copyright (C) 2021 The Android Open Source Project
//
// 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 com.google.gerrit.server.account.externalids;

import com.google.common.flogger.FluentLogger;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.index.ReindexerAlreadyRunningException;
import com.google.gerrit.server.index.VersionManager;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.TextProgressMonitor;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;

@Singleton
public class OnlineExternalIdCaseSensivityMigrator {
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();

  private Executor executor;
  private ExternalIdCaseSensitivityMigrator.Factory migratorFactory;
  private ExternalIds externalIds;
  private VersionManager versionManager;
  private Config globalConfig;
  private Path sitePath;
  private final TextProgressMonitor monitor = new TextProgressMonitor();
  private boolean isUserNameCaseInsensitive;
  private boolean isUserNameCaseInsensitiveMigrationMode;

  @Inject
  public OnlineExternalIdCaseSensivityMigrator(
      @OnlineExternalIdCaseSensivityMigratiorExecutor ExecutorService executor,
      ExternalIdCaseSensitivityMigrator.Factory migratorFactory,
      ExternalIds externalIds,
      VersionManager versionManager,
      @GerritServerConfig Config globalConfig,
      @SitePath Path sitePath) {
    this.migratorFactory = migratorFactory;
    this.externalIds = externalIds;
    this.versionManager = versionManager;
    this.globalConfig = globalConfig;
    this.sitePath = sitePath;
    this.executor = executor;
    this.isUserNameCaseInsensitiveMigrationMode =
        globalConfig.getBoolean("auth", "userNameCaseInsensitiveMigrationMode", false);
    this.isUserNameCaseInsensitive =
        globalConfig.getBoolean("auth", "userNameCaseInsensitive", false);
  }

  public void migrate() {
    if (!isUserNameCaseInsensitive || !isUserNameCaseInsensitiveMigrationMode) {
      logger.atSevere().log(
          "External IDs online migration requires auth.userNameCaseInsensitive and"
              + " auth.userNameCaseInsensitiveMigrationMode to be set to true. Skipping"
              + " migration!");
      return;
    }
    executor.execute(
        () -> {
          try {
            Collection<ExternalId> todo = externalIds.all();
            try {
              monitor.beginTask("Converting external ID note names", todo.size());
              migratorFactory
                  .create(isUserNameCaseInsensitive, false)
                  .migrate(todo, () -> monitor.update(1));
            } finally {
              monitor.endTask();
            }
            try {
              updateGerritConfig();
              monitor.beginTask("Reindex accounts", ProgressMonitor.UNKNOWN);
              versionManager.startReindexer("accounts", true);
            } finally {
              monitor.endTask();
            }
            logger.atInfo().log("External IDs migration completed!");
          } catch (IOException | ConfigInvalidException e) {
            logger.atSevere().withCause(e).log(
                "Exception during the external ids migration, cause %s", e.getMessage());
          } catch (ReindexerAlreadyRunningException e) {
            logger.atSevere().log("Failed to reindex external ids: %s", e.getMessage());
          }
        });
  }

  private void updateGerritConfig() throws IOException, ConfigInvalidException {
    logger.atInfo().log(
        "Setting auth.userNameCaseInsensitiveMigrationMode to false in gerrit.config.");

    FileBasedConfig config =
        new FileBasedConfig(
            globalConfig, sitePath.resolve("etc/gerrit.config").toFile(), FS.DETECTED);
    config.load();
    config.setBoolean("auth", null, "userNameCaseInsensitiveMigrationMode", false);

    config.save();
  }
}
