// Copyright (C) 2016 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.schema;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountSshKey;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.VersionedAuthorizedKeys;
import com.google.gerrit.server.account.VersionedAuthorizedKeys.SimpleSshKeyCreator;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;

import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;

import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collection;
import java.util.Map;

public class Schema_124 extends SchemaVersion {
  private final GitRepositoryManager repoManager;
  private final AllUsersName allUsersName;
  private final PersonIdent serverUser;

  @Inject
  Schema_124(Provider<Schema_123> prior,
      GitRepositoryManager repoManager,
      AllUsersName allUsersName,
      @GerritPersonIdent PersonIdent serverUser) {
    super(prior);
    this.repoManager = repoManager;
    this.allUsersName = allUsersName;
    this.serverUser = serverUser;
  }

  @Override
  protected void migrateData(ReviewDb db, UpdateUI ui)
      throws OrmException, SQLException {
    Multimap<Account.Id, AccountSshKey> imports = ArrayListMultimap.create();
    try (Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
      ResultSet rs = stmt.executeQuery(
          "SELECT "
          + "account_id, "
          + "seq, "
          + "ssh_public_key, "
          + "valid "
          + "FROM account_ssh_keys")) {
      while (rs.next()) {
        Account.Id accountId = new Account.Id(rs.getInt(1));
        int seq = rs.getInt(2);
        String sshPublicKey = rs.getString(3);
        AccountSshKey key = new AccountSshKey(
            new AccountSshKey.Id(accountId, seq), sshPublicKey);
        boolean valid = rs.getBoolean(4);
        if (!valid) {
          key.setInvalid();
        }
        imports.put(accountId, key);
      }
    }

    if (imports.isEmpty()) {
      return;
    }

    try (Repository git = repoManager.openRepository(allUsersName);
        RevWalk rw = new RevWalk(git)) {
      BatchRefUpdate bru = git.getRefDatabase().newBatchUpdate();

      for (Map.Entry<Account.Id, Collection<AccountSshKey>> e : imports.asMap()
          .entrySet()) {
        try (MetaDataUpdate md = new MetaDataUpdate(
                 GitReferenceUpdated.DISABLED, allUsersName, git, bru);
             VersionedAuthorizedKeys authorizedKeys =
                 new VersionedAuthorizedKeys(
                     new SimpleSshKeyCreator(), e.getKey())) {
          md.getCommitBuilder().setAuthor(serverUser);
          md.getCommitBuilder().setCommitter(serverUser);

          authorizedKeys.load(md);
          authorizedKeys.setKeys(e.getValue());
          authorizedKeys.commit(md);
        }
      }

      bru.execute(rw, NullProgressMonitor.INSTANCE);
    } catch (ConfigInvalidException | IOException ex) {
      throw new OrmException(ex);
    }
  }
}

@@ -644,16 +644,56 @@
 chema;%0A%0A
+import com.google.common.base.Function;%0A
 import c
@@ -771,32 +771,75 @@
 llect.Multimap;%0A
+import com.google.common.collect.Ordering;%0A
 import com.googl
@@ -1950,24 +1950,52 @@
 .Statement;%0A
+import java.util.ArrayList;%0A
 import java.
@@ -2007,24 +2007,77 @@
 Collection;%0A
+import java.util.Collections;%0Aimport java.util.List;%0A
 import java.
@@ -4273,16 +4273,42 @@
 setKeys(
+fixInvalidSequenceNumbers(
 e.getVal
@@ -4312,16 +4312,17 @@
 Value())
+)
 ;%0A      
@@ -4523,11 +4523,841 @@
    %7D%0A  %7D
+%0A%0A  private Collection%3CAccountSshKey%3E fixInvalidSequenceNumbers(%0A      Collection%3CAccountSshKey%3E keys) %7B%0A    Ordering%3CAccountSshKey%3E o =%0A        Ordering.natural().onResultOf(new Function%3CAccountSshKey, Integer%3E() %7B%0A          @Override%0A          public Integer apply(AccountSshKey sshKey) %7B%0A            return sshKey.getKey().get();%0A          %7D%0A        %7D);%0A    List%3CAccountSshKey%3E fixedKeys = new ArrayList%3C%3E(keys);%0A    AccountSshKey minKey = o.min(keys);%0A    while (minKey.getKey().get() %3C= 0) %7B%0A      AccountSshKey fixedKey = new AccountSshKey(%0A          new AccountSshKey.Id(minKey.getKey().getParentKey(),%0A              Math.max(o.max(keys).getKey().get() + 1, 1)),%0A          minKey.getSshPublicKey());%0A      Collections.replaceAll(fixedKeys, minKey, fixedKey);%0A      minKey = o.min(fixedKeys);%0A    %7D%0A    return fixedKeys;%0A  %7D
 %0A%7D%0A

Fix migration of SSH keys with invalid sequence numbers (<= 0)