/*
 * SonarQube
 * Copyright (C) 2009-2024 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.scanner.report;

import com.google.common.collect.ImmutableMap;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.slf4j.event.Level;
import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.batch.fs.internal.DefaultInputModule;
import org.sonar.api.testfixtures.log.LogTester;
import org.sonar.api.utils.System2;
import org.sonar.core.platform.PluginInfo;
import org.sonar.scanner.bootstrap.GlobalServerSettings;
import org.sonar.scanner.bootstrap.ScannerPluginRepository;
import org.sonar.scanner.fs.InputModuleHierarchy;
import org.sonar.scanner.protocol.output.FileStructure;
import org.sonar.scanner.protocol.output.ScannerReportWriter;
import org.sonar.scanner.scan.ProjectServerSettings;
import org.sonar.scanner.scan.filesystem.InputComponentStore;
import org.sonar.updatecenter.common.Version;

import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;

public class AnalysisContextReportPublisherTest {

  private static final String SONAR_SKIP = "sonar.skip";
  private static final String COM_FOO = "com.foo";

  @Rule
  public LogTester logTester = new LogTester();

  @Rule
  public TemporaryFolder temp = new TemporaryFolder();

  private final ScannerPluginRepository pluginRepo = mock(ScannerPluginRepository.class);
  private final System2 system2 = mock(System2.class);
  private final GlobalServerSettings globalServerSettings = mock(GlobalServerSettings.class);
  private final InputModuleHierarchy hierarchy = mock(InputModuleHierarchy.class);
  private final InputComponentStore store = mock(InputComponentStore.class);
  private final ProjectServerSettings projectServerSettings = mock(ProjectServerSettings.class);
  private final AnalysisContextReportPublisher publisher = new AnalysisContextReportPublisher(projectServerSettings, pluginRepo, system2, globalServerSettings, hierarchy, store);
  private ScannerReportWriter writer;

  @Before
  public void prepare() throws IOException {
    logTester.setLevel(Level.INFO);
    FileStructure fileStructure = new FileStructure(temp.newFolder());
    writer = new ScannerReportWriter(fileStructure);
    when(system2.properties()).thenReturn(new Properties());
  }

  @Test
  public void shouldOnlyDumpPluginsByDefault() throws Exception {
    when(pluginRepo.getExternalPluginsInfos()).thenReturn(singletonList(new PluginInfo("xoo").setName("Xoo").setVersion(Version.create("1.0"))));

    DefaultInputModule rootModule = new DefaultInputModule(ProjectDefinition.create()
      .setBaseDir(temp.newFolder())
      .setWorkDir(temp.newFolder()));
    when(store.allModules()).thenReturn(singletonList(rootModule));
    when(hierarchy.root()).thenReturn(rootModule);
    publisher.init(writer);

    assertThat(writer.getFileStructure().analysisLog()).exists();
    assertThat(FileUtils.readFileToString(writer.getFileStructure().analysisLog(), StandardCharsets.UTF_8)).contains("Xoo 1.0 (xoo)");

    verifyNoInteractions(system2);
  }

  @Test
  public void dumpServerSideGlobalProps() throws Exception {
    logTester.setLevel(Level.DEBUG);
    when(globalServerSettings.properties()).thenReturn(ImmutableMap.of(COM_FOO, "bar", SONAR_SKIP, "true"));
    DefaultInputModule rootModule = new DefaultInputModule(ProjectDefinition.create()
      .setBaseDir(temp.newFolder())
      .setWorkDir(temp.newFolder())
      .setProperty("sonar.projectKey", "foo"));
    when(store.allModules()).thenReturn(singletonList(rootModule));
    when(hierarchy.root()).thenReturn(rootModule);
    publisher.init(writer);

    String content = FileUtils.readFileToString(writer.getFileStructure().analysisLog(), StandardCharsets.UTF_8);
    assertThat(content)
      .containsOnlyOnce(COM_FOO)
      .containsOnlyOnce(SONAR_SKIP);
  }

  @Test
  public void dumpServerSideProjectProps() throws Exception {
    logTester.setLevel(Level.DEBUG);

    DefaultInputModule rootModule = new DefaultInputModule(ProjectDefinition.create()
      .setBaseDir(temp.newFolder())
      .setWorkDir(temp.newFolder())
      .setProperty("sonar.projectKey", "foo"));
    when(store.allModules()).thenReturn(singletonList(rootModule));
    when(hierarchy.root()).thenReturn(rootModule);

    when(projectServerSettings.properties()).thenReturn(ImmutableMap.of(COM_FOO, "bar", SONAR_SKIP, "true"));

    publisher.init(writer);

    List<String> lines = FileUtils.readLines(writer.getFileStructure().analysisLog(), StandardCharsets.UTF_8);
    assertThat(lines).containsExactly(
      "Plugins:",
      "Bundled analyzers:",
      "Global server settings:",
      "Project server settings:",
      "  - com.foo=bar",
      "  - sonar.skip=true",
      "Project scanner properties:",
      "  - sonar.projectKey=foo");
  }

  @Test
  public void shouldNotDumpSensitiveModuleProperties() throws Exception {
    DefaultInputModule rootModule = new DefaultInputModule(ProjectDefinition.create()
      .setBaseDir(temp.newFolder())
      .setWorkDir(temp.newFolder())
      .setProperty("sonar.projectKey", "foo")
      .setProperty("sonar.projectKey", "foo")
      .setProperty("sonar.login", "my_token")
      .setProperty("sonar.password", "azerty")
      .setProperty("sonar.cpp.license.secured", "AZERTY"));
    when(store.allModules()).thenReturn(singletonList(rootModule));
    when(hierarchy.root()).thenReturn(rootModule);
    publisher.init(writer);

    assertThat(writer.getFileStructure().analysisLog()).exists();

    assertThat(FileUtils.readFileToString(writer.getFileStructure().analysisLog(), StandardCharsets.UTF_8)).containsSubsequence(
      "sonar.cpp.license.secured=******",
      "sonar.login=******",
      "sonar.password=******",
      "sonar.projectKey=foo");
  }

  @Test
  public void shouldShortenModuleProperties() throws Exception {
    File baseDir = temp.newFolder();
    DefaultInputModule rootModule = new DefaultInputModule(ProjectDefinition.create()
      .setBaseDir(baseDir)
      .setWorkDir(temp.newFolder())
      .setProperty("sonar.projectKey", "foo")
      .setProperty("sonar.projectBaseDir", baseDir.toString())
      .setProperty("sonar.aVeryLongProp", StringUtils.repeat("abcde", 1000)));
    when(store.allModules()).thenReturn(singletonList(rootModule));
    when(hierarchy.root()).thenReturn(rootModule);
    publisher.init(writer);

    assertThat(writer.getFileStructure().analysisLog()).exists();

    assertThat(FileUtils.readFileToString(writer.getFileStructure().analysisLog(), StandardCharsets.UTF_8)).containsSubsequence(
      "sonar.aVeryLongProp=" + StringUtils.repeat("abcde", 199) + "ab...",
      "sonar.projectBaseDir=" + baseDir,
      "sonar.projectKey=foo");
  }

  // SONAR-7598
  @Test
  public void shouldNotDumpSensitiveGlobalProperties() throws Exception {
    when(globalServerSettings.properties()).thenReturn(ImmutableMap.of("sonar.login", "my_token", "sonar.password", "azerty", "sonar.cpp.license.secured", "AZERTY"));
    DefaultInputModule rootModule = new DefaultInputModule(ProjectDefinition.create()
      .setBaseDir(temp.newFolder())
      .setWorkDir(temp.newFolder())
      .setProperty("sonar.projectKey", "foo"));
    when(store.allModules()).thenReturn(singletonList(rootModule));
    when(hierarchy.root()).thenReturn(rootModule);
    publisher.init(writer);

    assertThat(FileUtils.readFileToString(writer.getFileStructure().analysisLog(), StandardCharsets.UTF_8)).containsSubsequence(
      "sonar.cpp.license.secured=******",
      "sonar.login=******",
      "sonar.password=******");
  }

  // SONAR-7371
  @Test
  public void dontDumpParentProps() throws Exception {
    logTester.setLevel(Level.DEBUG);

    DefaultInputModule module = new DefaultInputModule(ProjectDefinition.create()
      .setBaseDir(temp.newFolder())
      .setWorkDir(temp.newFolder())
      .setProperty("sonar.projectKey", "foo")
      .setProperty(SONAR_SKIP, "true"));

    DefaultInputModule parent = new DefaultInputModule(ProjectDefinition.create()
      .setBaseDir(temp.newFolder())
      .setWorkDir(temp.newFolder())
      .setProperty("sonar.projectKey", "parent")
      .setProperty(SONAR_SKIP, "true"));

    when(hierarchy.parent(module)).thenReturn(parent);
    when(store.allModules()).thenReturn(Arrays.asList(parent, module));
    when(hierarchy.root()).thenReturn(parent);

    publisher.init(writer);

    List<String> lines = FileUtils.readLines(writer.getFileStructure().analysisLog(), StandardCharsets.UTF_8);
    assertThat(lines).containsExactly(
      "Plugins:",
      "Bundled analyzers:",
      "Global server settings:",
      "Project server settings:",
      "Project scanner properties:",
      "  - sonar.projectKey=parent",
      "  - sonar.skip=true",
      "Scanner properties of module: foo",
      "  - sonar.projectKey=foo");
  }

  @Test
  public void init_splitsPluginsByTypeInTheFile() throws IOException {
    DefaultInputModule parent = new DefaultInputModule(ProjectDefinition.create()
      .setBaseDir(temp.newFolder())
      .setWorkDir(temp.newFolder())
      .setProperty("sonar.projectKey", "parent")
      .setProperty(SONAR_SKIP, "true"));
    when(hierarchy.root()).thenReturn(parent);

    when(pluginRepo.getExternalPluginsInfos()).thenReturn(List.of(new PluginInfo("xoo").setName("Xoo").setVersion(Version.create("1.0"))));
    when(pluginRepo.getBundledPluginsInfos()).thenReturn(List.of(new PluginInfo("java").setName("Java").setVersion(Version.create("9.7"))));

    publisher.init(writer);

    List<String> lines = FileUtils.readLines(writer.getFileStructure().analysisLog(), StandardCharsets.UTF_8);

    System.out.println(lines);

    assertThat(lines).contains("Plugins:",
      "  - Xoo 1.0 (xoo)",
      "Bundled analyzers:",
      "  - Java 9.7 (java)");
  }
}
