// Copyright (C) 2012 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.mail.send;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Address;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.NotifyConfig.NotifyType;
import com.google.gerrit.exceptions.EmailException;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.RecipientType;
import com.google.gerrit.mail.MailHeader;
import com.google.gerrit.server.mail.send.ProjectWatch.Watchers;
import com.google.gerrit.server.mail.send.ProjectWatch.Watchers.WatcherList;
import java.util.HashMap;
import java.util.Map;

/** Common class for notifications that are related to a project and branch */
public abstract class NotificationEmail extends OutgoingEmail {
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();

  protected BranchNameKey branch;

  protected NotificationEmail(EmailArguments args, String messageClass, BranchNameKey branch) {
    super(args, messageClass);
    this.branch = branch;
  }

  @Override
  protected void init() throws EmailException {
    super.init();
    setListIdHeader();
  }

  private void setListIdHeader() {
    // Set a reasonable list id so that filters can be used to sort messages
    setHeader(
        "List-Id",
        "<gerrit-" + branch.project().get().replace('/', '-') + "." + getGerritHost() + ">");
    if (getSettingsUrl() != null) {
      setHeader("List-Unsubscribe", "<" + getSettingsUrl() + ">");
    }
  }

  /** Include users and groups that want notification of events. */
  protected void includeWatchers(NotifyType type) {
    includeWatchers(type, true);
  }

  /** Include users and groups that want notification of events. */
  protected void includeWatchers(NotifyType type, boolean includeWatchersFromNotifyConfig) {
    try {
      Watchers matching = getWatchers(type, includeWatchersFromNotifyConfig);
      add(RecipientType.TO, matching.to);
      add(RecipientType.CC, matching.cc);
      add(RecipientType.BCC, matching.bcc);
    } catch (StorageException err) {
      // Just don't CC everyone. Better to send a partial message to those
      // we already have queued up then to fail deliver entirely to people
      // who have a lower interest in the change.
      logger.atWarning().withCause(err).log("Cannot BCC watchers for %s", type);
    }
  }

  /** Returns all watchers that are relevant */
  protected abstract Watchers getWatchers(NotifyType type, boolean includeWatchersFromNotifyConfig);

  /** Add users or email addresses to the TO, CC, or BCC list. */
  protected void add(RecipientType type, WatcherList watcherList) {
    for (Account.Id user : watcherList.accounts) {
      addWatcher(type, user);
    }
    for (Address addr : watcherList.emails) {
      add(type, addr);
    }
  }

  protected abstract void addWatcher(RecipientType type, Account.Id to);

  public String getSshHost() {
    String host = Iterables.getFirst(args.sshAddresses, null);
    if (host == null) {
      return null;
    }
    if (host.startsWith("*:")) {
      return getGerritHost() + host.substring(1);
    }
    return host;
  }

  @Override
  protected void setupSoyContext() {
    super.setupSoyContext();

    String projectName = branch.project().get();
    soyContext.put("projectName", projectName);
    // shortProjectName is the project name with the path abbreviated.
    soyContext.put("shortProjectName", getShortProjectName(projectName));

    // instanceAndProjectName is the instance's name followed by the abbreviated project path
    soyContext.put(
        "instanceAndProjectName",
        getInstanceAndProjectName(args.instanceNameProvider.get(), projectName));
    soyContext.put("addInstanceNameInSubject", args.addInstanceNameInSubject);

    soyContextEmailData.put("sshHost", getSshHost());

    Map<String, String> branchData = new HashMap<>();
    branchData.put("shortName", branch.shortName());
    soyContext.put("branch", branchData);

    footers.add(MailHeader.PROJECT.withDelimiter() + branch.project().get());
    footers.add(MailHeader.BRANCH.withDelimiter() + branch.shortName());
  }

  @VisibleForTesting
  protected static String getShortProjectName(String projectName) {
    int lastIndexSlash = projectName.lastIndexOf('/');
    if (lastIndexSlash == 0) {
      return projectName.substring(1); // Remove the first slash
    }
    if (lastIndexSlash == -1) { // No slash in the project name
      return projectName;
    }

    return "..." + projectName.substring(lastIndexSlash + 1);
  }

  @VisibleForTesting
  protected static String getInstanceAndProjectName(String instanceName, String projectName) {
    if (instanceName == null || instanceName.isEmpty()) {
      return getShortProjectName(projectName);
    }
    // Extract the project name (everything after the last slash) and prepends it with gerrit's
    // instance name
    return instanceName + "/" + projectName.substring(projectName.lastIndexOf('/') + 1);
  }
}
