// Copyright (C) 2017 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.group.db;

import static com.google.gerrit.extensions.common.testing.CommitInfoSubject.assertThat;

import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.GroupDescription;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.testing.InMemoryRepositoryManager;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.Optional;
import java.util.TimeZone;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;

@Ignore
public class AbstractGroupTest {
  protected static final TimeZone TZ = TimeZone.getTimeZone("America/Los_Angeles");
  protected static final String SERVER_ID = "server-id";
  protected static final String SERVER_NAME = "Gerrit Server";
  protected static final String SERVER_EMAIL = "noreply@gerritcodereview.com";
  protected static final int SERVER_ACCOUNT_NUMBER = 100000;
  protected static final int USER_ACCOUNT_NUMBER = 100001;

  protected AllUsersName allUsersName;
  protected InMemoryRepositoryManager repoManager;
  protected Repository allUsersRepo;
  protected Account.Id serverAccountId;
  protected PersonIdent serverIdent;
  protected Account.Id userId;
  protected PersonIdent userIdent;

  @Before
  public void abstractGroupTestSetUp() throws Exception {
    allUsersName = new AllUsersName(AllUsersNameProvider.DEFAULT);
    repoManager = new InMemoryRepositoryManager();
    allUsersRepo = repoManager.createRepository(allUsersName);
    serverAccountId = Account.id(SERVER_ACCOUNT_NUMBER);
    serverIdent = new PersonIdent(SERVER_NAME, SERVER_EMAIL, TimeUtil.nowTs(), TZ);
    userId = Account.id(USER_ACCOUNT_NUMBER);
    userIdent = newPersonIdent(userId, serverIdent);
  }

  @After
  public void abstractGroupTestTearDown() throws Exception {
    allUsersRepo.close();
  }

  protected Timestamp getTipTimestamp(AccountGroup.UUID uuid) throws Exception {
    try (RevWalk rw = new RevWalk(allUsersRepo)) {
      Ref ref = allUsersRepo.exactRef(RefNames.refsGroups(uuid));
      return ref == null
          ? null
          : new Timestamp(rw.parseCommit(ref.getObjectId()).getAuthorIdent().getWhen().getTime());
    }
  }

  protected static void assertServerCommit(CommitInfo commitInfo, String expectedMessage) {
    assertCommit(commitInfo, expectedMessage, SERVER_NAME, SERVER_EMAIL);
  }

  protected static void assertCommit(
      CommitInfo commitInfo, String expectedMessage, String expectedName, String expectedEmail) {
    assertThat(commitInfo).message().isEqualTo(expectedMessage);
    assertThat(commitInfo).author().name().isEqualTo(expectedName);
    assertThat(commitInfo).author().email().isEqualTo(expectedEmail);

    // Committer should always be the server, regardless of author.
    assertThat(commitInfo).committer().name().isEqualTo(SERVER_NAME);
    assertThat(commitInfo).committer().email().isEqualTo(SERVER_EMAIL);
    assertThat(commitInfo).committer().date().isEqualTo(commitInfo.author.date);
    assertThat(commitInfo).committer().tz().isEqualTo(commitInfo.author.tz);
  }

  protected MetaDataUpdate createMetaDataUpdate(PersonIdent authorIdent) {
    MetaDataUpdate md =
        new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsersName, allUsersRepo);
    md.getCommitBuilder().setAuthor(authorIdent);
    md.getCommitBuilder().setCommitter(serverIdent); // Committer is always the server identity.
    return md;
  }

  protected static PersonIdent newPersonIdent() {
    return new PersonIdent(SERVER_NAME, SERVER_EMAIL, TimeUtil.nowTs(), TZ);
  }

  protected static PersonIdent newPersonIdent(Account.Id id, PersonIdent ident) {
    return new PersonIdent(
        getAccountName(id), getAccountEmail(id), ident.getWhen(), ident.getTimeZone());
  }

  protected AuditLogFormatter getAuditLogFormatter() {
    return AuditLogFormatter.create(AbstractGroupTest::getAccount, this::getGroup, SERVER_ID);
  }

  private static Optional<Account> getAccount(Account.Id id) {
    Account.Builder account = Account.builder(id, TimeUtil.nowTs());
    account.setFullName("Account " + id);
    return Optional.of(account.build());
  }

  private Optional<GroupDescription.Basic> getGroup(AccountGroup.UUID uuid) {
    GroupDescription.Basic group =
        new GroupDescription.Basic() {
          @Override
          public AccountGroup.UUID getGroupUUID() {
            return uuid;
          }

          @Override
          public String getName() {
            try {
              return GroupConfig.loadForGroup(allUsersName, allUsersRepo, uuid)
                  .getLoadedGroup()
                  .map(InternalGroup::getName)
                  .orElse("Group " + uuid);
            } catch (IOException | ConfigInvalidException e) {
              return "Group " + uuid;
            }
          }

          @Nullable
          @Override
          public String getEmailAddress() {
            return null;
          }

          @Nullable
          @Override
          public String getUrl() {
            return null;
          }
        };
    return Optional.of(group);
  }

  protected static String getAccountName(Account.Id id) {
    return "Account " + id;
  }

  protected static String getAccountEmail(Account.Id id) {
    return String.format("%s@%s", id, SERVER_ID);
  }
}
