// Copyright (C) 2009 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 static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;

import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Address;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.util.time.TimeUtil;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent;
import org.junit.Before;
import org.junit.Test;

public class FromAddressGeneratorProviderTest {
  private Config config;
  private PersonIdent ident;
  private AccountCache accountCache;

  @Before
  public void setUp() throws Exception {
    config = new Config();
    ident = new PersonIdent("NAME", "e@email", 0, 0);
    accountCache = mock(AccountCache.class);
  }

  private FromAddressGenerator create() {
    return new FromAddressGeneratorProvider(config, "Anonymous Coward", ident, accountCache).get();
  }

  private void setFrom(String newFrom) {
    config.setString("sendemail", null, "from", newFrom);
  }

  private void setDomains(List<String> domains) {
    config.setStringList("sendemail", null, "allowedDomain", domains);
  }

  @Test
  public void defaultIsMIXED() {
    assertThat(create()).isInstanceOf(FromAddressGeneratorProvider.PatternGen.class);
  }

  @Test
  public void selectUSER() {
    setFrom("USER");
    assertThat(create()).isInstanceOf(FromAddressGeneratorProvider.UserGen.class);

    setFrom("user");
    assertThat(create()).isInstanceOf(FromAddressGeneratorProvider.UserGen.class);

    setFrom("uSeR");
    assertThat(create()).isInstanceOf(FromAddressGeneratorProvider.UserGen.class);
  }

  @Test
  public void USER_FullyConfiguredUser() {
    setFrom("USER");

    final String name = "A U. Thor";
    final String email = "a.u.thor@test.example.com";
    final Account.Id user = user(name, email);

    final Address r = create().from(user);
    assertThat(r).isNotNull();
    assertThat(r.name()).isEqualTo(name);
    assertThat(r.email()).isEqualTo(email);
    verifyAccountCacheGet(user);
  }

  @Test
  public void USER_NoFullNameUser() {
    setFrom("USER");

    final String email = "a.u.thor@test.example.com";
    final Account.Id user = user(null, email);

    final Address r = create().from(user);
    assertThat(r).isNotNull();
    assertThat(r.name()).isNull();
    assertThat(r.email()).isEqualTo(email);
    verifyAccountCacheGet(user);
  }

  @Test
  public void USER_NoPreferredEmailUser() {
    setFrom("USER");

    final String name = "A U. Thor";
    final Account.Id user = user(name, null);

    final Address r = create().from(user);
    assertThat(r).isNotNull();
    assertThat(r.name()).isEqualTo(name + " (Code Review)");
    assertThat(r.email()).isEqualTo(ident.getEmailAddress());
    verifyAccountCacheGet(user);
  }

  @Test
  public void USER_NullUser() {
    setFrom("USER");
    final Address r = create().from(null);
    assertThat(r).isNotNull();
    assertThat(r.name()).isEqualTo(ident.getName());
    assertThat(r.email()).isEqualTo(ident.getEmailAddress());
    verifyZeroInteractions(accountCache);
  }

  @Test
  public void USERAllowDomain() {
    setFrom("USER");
    setDomains(Arrays.asList("*.example.com"));
    final String name = "A U. Thor";
    final String email = "a.u.thor@test.example.com";
    final Account.Id user = user(name, email);

    final Address r = create().from(user);
    assertThat(r).isNotNull();
    assertThat(r.name()).isEqualTo(name);
    assertThat(r.email()).isEqualTo(email);
    verifyAccountCacheGet(user);
  }

  @Test
  public void USERNoAllowDomain() {
    setFrom("USER");
    setDomains(Arrays.asList("example.net"));
    final String name = "A U. Thor";
    final String email = "a.u.thor@example.com";
    final Account.Id user = user(name, email);

    final Address r = create().from(user);
    assertThat(r).isNotNull();
    assertThat(r.name()).isEqualTo(name + " (Code Review)");
    assertThat(r.email()).isEqualTo(ident.getEmailAddress());
    verifyAccountCacheGet(user);
  }

  @Test
  public void USERAllowDomainTwice() {
    setFrom("USER");
    setDomains(Arrays.asList("example.net"));
    setDomains(Arrays.asList("example.com"));
    final String name = "A U. Thor";
    final String email = "a.u.thor@example.com";
    final Account.Id user = user(name, email);

    final Address r = create().from(user);
    assertThat(r).isNotNull();
    assertThat(r.name()).isEqualTo(name);
    assertThat(r.email()).isEqualTo(email);
    verifyAccountCacheGet(user);
  }

  @Test
  public void USERAllowDomainTwiceReverse() {
    setFrom("USER");
    setDomains(Arrays.asList("example.com"));
    setDomains(Arrays.asList("example.net"));
    final String name = "A U. Thor";
    final String email = "a.u.thor@example.com";
    final Account.Id user = user(name, email);

    final Address r = create().from(user);
    assertThat(r).isNotNull();
    assertThat(r.name()).isEqualTo(name + " (Code Review)");
    assertThat(r.email()).isEqualTo(ident.getEmailAddress());
    verifyAccountCacheGet(user);
  }

  @Test
  public void USERAllowTwoDomains() {
    setFrom("USER");
    setDomains(Arrays.asList("example.com", "example.net"));
    final String name = "A U. Thor";
    final String email = "a.u.thor@example.com";
    final Account.Id user = user(name, email);

    final Address r = create().from(user);
    assertThat(r).isNotNull();
    assertThat(r.name()).isEqualTo(name);
    assertThat(r.email()).isEqualTo(email);
    verifyAccountCacheGet(user);
  }

  @Test
  public void selectSERVER() {
    setFrom("SERVER");
    assertThat(create()).isInstanceOf(FromAddressGeneratorProvider.ServerGen.class);

    setFrom("server");
    assertThat(create()).isInstanceOf(FromAddressGeneratorProvider.ServerGen.class);

    setFrom("sErVeR");
    assertThat(create()).isInstanceOf(FromAddressGeneratorProvider.ServerGen.class);
  }

  @Test
  public void SERVER_FullyConfiguredUser() {
    setFrom("SERVER");

    final String name = "A U. Thor";
    final String email = "a.u.thor@test.example.com";
    final Account.Id user = userNoLookup(name, email);

    final Address r = create().from(user);
    assertThat(r).isNotNull();
    assertThat(r.name()).isEqualTo(ident.getName());
    assertThat(r.email()).isEqualTo(ident.getEmailAddress());
    verifyZeroInteractions(accountCache);
  }

  @Test
  public void SERVER_NullUser() {
    setFrom("SERVER");
    final Address r = create().from(null);
    assertThat(r).isNotNull();
    assertThat(r.name()).isEqualTo(ident.getName());
    assertThat(r.email()).isEqualTo(ident.getEmailAddress());
    verifyZeroInteractions(accountCache);
  }

  @Test
  public void selectMIXED() {
    setFrom("MIXED");
    assertThat(create()).isInstanceOf(FromAddressGeneratorProvider.PatternGen.class);

    setFrom("mixed");
    assertThat(create()).isInstanceOf(FromAddressGeneratorProvider.PatternGen.class);

    setFrom("mIxEd");
    assertThat(create()).isInstanceOf(FromAddressGeneratorProvider.PatternGen.class);
  }

  @Test
  public void MIXED_FullyConfiguredUser() {
    setFrom("MIXED");

    final String name = "A U. Thor";
    final String email = "a.u.thor@test.example.com";
    final Account.Id user = user(name, email);

    final Address r = create().from(user);
    assertThat(r).isNotNull();
    assertThat(r.name()).isEqualTo(name + " (Code Review)");
    assertThat(r.email()).isEqualTo(ident.getEmailAddress());
    verifyAccountCacheGet(user);
  }

  @Test
  public void MIXED_NoFullNameUser() {
    setFrom("MIXED");

    final String email = "a.u.thor@test.example.com";
    final Account.Id user = user(null, email);

    final Address r = create().from(user);
    assertThat(r).isNotNull();
    assertThat(r.name()).isEqualTo("Anonymous Coward (Code Review)");
    assertThat(r.email()).isEqualTo(ident.getEmailAddress());
    verifyAccountCacheGet(user);
  }

  @Test
  public void MIXED_NoPreferredEmailUser() {
    setFrom("MIXED");

    final String name = "A U. Thor";
    final Account.Id user = user(name, null);

    final Address r = create().from(user);
    assertThat(r).isNotNull();
    assertThat(r.name()).isEqualTo(name + " (Code Review)");
    assertThat(r.email()).isEqualTo(ident.getEmailAddress());
    verifyAccountCacheGet(user);
  }

  @Test
  public void MIXED_NullUser() {
    setFrom("MIXED");
    final Address r = create().from(null);
    assertThat(r).isNotNull();
    assertThat(r.name()).isEqualTo(ident.getName());
    assertThat(r.email()).isEqualTo(ident.getEmailAddress());
    verifyZeroInteractions(accountCache);
  }

  @Test
  public void CUSTOM_FullyConfiguredUser() {
    setFrom("A ${user} B <my.server@email.address>");

    final String name = "A U. Thor";
    final String email = "a.u.thor@test.example.com";
    final Account.Id user = user(name, email);

    final Address r = create().from(user);
    assertThat(r).isNotNull();
    assertThat(r.name()).isEqualTo("A " + name + " B");
    assertThat(r.email()).isEqualTo("my.server@email.address");
    verifyAccountCacheGet(user);
  }

  @Test
  public void CUSTOM_NoFullNameUser() {
    setFrom("A ${user} B <my.server@email.address>");

    final String email = "a.u.thor@test.example.com";
    final Account.Id user = user(null, email);

    final Address r = create().from(user);
    assertThat(r).isNotNull();
    assertThat(r.name()).isEqualTo("A Anonymous Coward B");
    assertThat(r.email()).isEqualTo("my.server@email.address");
  }

  @Test
  public void CUSTOM_NullUser() {
    setFrom("A ${user} B <my.server@email.address>");

    final Address r = create().from(null);
    assertThat(r).isNotNull();
    assertThat(r.name()).isEqualTo(ident.getName());
    assertThat(r.email()).isEqualTo("my.server@email.address");
  }

  private Account.Id user(String name, String email) {
    final AccountState s = makeUser(name, email);
    when(accountCache.get(eq(s.account().id()))).thenReturn(Optional.of(s));
    return s.account().id();
  }

  private void verifyAccountCacheGet(Account.Id id) {
    verify(accountCache).get(eq(id));
  }

  private Account.Id userNoLookup(String name, String email) {
    final AccountState s = makeUser(name, email);
    return s.account().id();
  }

  private AccountState makeUser(String name, String email) {
    final Account.Id userId = Account.id(42);
    final Account.Builder account = Account.builder(userId, TimeUtil.nowTs());
    account.setFullName(name);
    account.setPreferredEmail(email);
    return AccountState.forAccount(account.build());
  }
}
