/*
 * RiskScape™ Copyright New Zealand Institute for Earth Science Limited
 * (Earth Sciences New Zealand) is distributed for research purposes only
 * under the terms of AGPLv3.
 *
 * RiskScape™ Copyright 2026 New Zealand Institute for Earth Science
 * Limited (Earth Sciences New Zealand). All rights reserved. Source code
 * available under the AGPLv3.
 * 
 * This program is free software: you can redistribute it and/or modify it under
 *  the terms of the GNU Affero 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 for RESEARCH PURPOSES ONLY, in the hope that it will
 * be useful for research and education initiatives.
 * 
 * If you are not a researcher, or you are a researcher who wishes to use this
 * program on terms other than AGPLv3 (including those who wish to restrict the
 * distribution of any source code created using this program), please contact:
 * https://riskscape.org.nz
 * 
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Affero General Public License for more details.  You should have received a copy
 * of the GNU Affero General Public License along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 * 
 * By way of summary only, under the AGPLv3:
 *     • Permissions of this strongest copyleft license are conditioned
 *       on making available complete source code of licensed works and
 *       modifications, which include larger works using a licensed work,
 *       under the same license.
 *     • Copyright and license notices must be preserved.
 *     • Contributors provide an express grant of patent rights.
 *     • When a modified version is used to provide a service over a
 *       network, the complete source code of the modified version must be made
 *       available.
 */
package nz.org.riskscape.engine.cli.tests;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.junit.Test;

import nz.org.riskscape.engine.cli.FormatsCommand;
import nz.org.riskscape.engine.cli.FunctionCommand;
import nz.org.riskscape.engine.cli.PipelineCommand;
import nz.org.riskscape.engine.cli.ResourceCommand;
import nz.org.riskscape.engine.cli.Table;
import nz.org.riskscape.engine.cli.TypeRegistryCommand;
import nz.org.riskscape.engine.test.EngineCommandIntegrationTest;
import nz.org.riskscape.engine.util.Pair;
import nz.org.riskscape.picocli.CommandLine.Help.Ansi.Text;

/**
 * Exercises the EngineOnlyCommands that display help for (i.e. built-in
 * RiskScape types, functions, bookmark formats, etc), which don't get covered
 * much by other integration tests. Checks for silly mistakes like CLI commands
 * crashing RiskScape, missing i18n strings, etc.
 */
@SuppressWarnings("unchecked")
public class EngineOnlyCommandsTest extends EngineCommandIntegrationTest {

  /**
   * Asserts if one of the given columnNames in the table is empty, i.e. it's
   * missing a description. This generally means we've changed/forgotten an i18n
   * key and so the lookup has failed.
   */
  private void assertStringsPresentFor(Table table, String...columnNames) {
    List<String> columnsToCheck = Arrays.asList(columnNames);
    List<Text> header = table.getHeader();
    for (List<Text> row : table.getRows()) {
      int numColumnsChecked = 0;
      for (int i = 0; i < row.size(); i++) {
        String currentColumnHeading = header.get(i).plainString();
        if (columnsToCheck.contains(currentColumnHeading)) {
          String valueToCheck = row.get(i).plainString();
          assertNotEquals("Missing '" + currentColumnHeading + "' string for row: " + row,
              "", valueToCheck.trim());
          numColumnsChecked++;
        }
      }
      assertEquals(columnsToCheck.size(), numColumnsChecked);
    }
  }

  @Test
  public void canListBuiltinTypes() throws Exception {
    TypeRegistryCommand.ListT cmd = setupCommand(new TypeRegistryCommand.ListT());
    Table table = (Table) cmd.doCommand(engine);

    // sanity-check a value that we expect to be there
    assertThat(table, TableMatchers.hasRow(
            Pair.of("id", "boolean"), Pair.of("description", containsString("A truth value"))
    ));

    // sanity-check that rows are present and no strings are missing unexpectedly
    assertStringsPresentFor(table, "id", "description", "category");
  }

  @Test
  public void canListBookmarkFormats() throws Exception {
    FormatsCommand.FormatListCommand cmd = setupCommand(new FormatsCommand.FormatListCommand());
    Table table = (Table) cmd.doCommand(engine);
    List<Text> header = table.getHeader();

    assertThat(table, TableMatchers.hasRow(
        Pair.of("format", "geotiff"), Pair.of("file extension", containsString("tiff"))
    ));
    // only checks two columns as some formats (i.e. Directory) do not require an extension
    assertStringsPresentFor(table, "format", "supports");
  }

  private List<String> getFormats() {
    List<String> formats = new ArrayList<>();
    FormatsCommand.FormatListCommand cmd = setupCommand(new FormatsCommand.FormatListCommand());
    Table table = (Table) cmd.doCommand(engine);

    for (List<Text> row : table.getRows()) {
      // assume the 'id' is the first field
      formats.add(row.get(0).plainString());
    }
    return formats;
  }

  @Test
  public void canListFormatParameters() throws Exception {
    FormatsCommand.FormatInfo cmd = setupCommand(new FormatsCommand.FormatInfo());

    // loop through all the (default) formats and check each one's params
    for (String fmt : getFormats()) {
      cmd.setName(fmt);
      Table table = (Table) cmd.doCommand(engine);

      assertStringsPresentFor(table, "parameter", "Description");
    }
  }

  @Test
  public void canListResourceLoaders() throws Exception {
    ResourceCommand.ResourceListCommand cmd = setupCommand(new ResourceCommand.ResourceListCommand());
    Table table = (Table) cmd.doCommand(engine);

    assertThat(table, TableMatchers.hasRow(
        Pair.of("id", "file"), Pair.of("description", containsString("local filesystem"))
    ));
    assertStringsPresentFor(table, "id", "description");
  }

  @Test
  public void canListPipelineSteps() throws Exception {
    PipelineCommand.StepList cmd = setupCommand(new PipelineCommand.StepList());
    Table table = (Table) cmd.doCommand(engine);

    assertThat(table, TableMatchers.hasRow(
            Pair.of("id", "input"), Pair.of("description", containsString("Makes input available to the pipeline."))
    ));
    assertStringsPresentFor(table, "id", "description");
  }

  private List<String> getPipelineSteps() {
    List<String> stepNames = new ArrayList<>();
    PipelineCommand.StepList cmd = setupCommand(new PipelineCommand.StepList());
    Table table = (Table) cmd.doCommand(engine);

    for (List<Text> row : table.getRows()) {
      // assume the 'id' is the first field
      stepNames.add(row.get(0).plainString());
    }
    return stepNames;
  }

  @Test
  public void canListPipelineStepParams() throws Exception {
    PipelineCommand.StepInfo cmd = setupCommand(new PipelineCommand.StepInfo());

    // loop through all the pipeline steps and check each one's params
    for (String step : getPipelineSteps()) {
      cmd.stepId = step;
      Table table = (Table) cmd.doCommand(engine);

      assertStringsPresentFor(table, "Name", "Description");
    }
  }

  @Test
  public void canListBuiltinFunctions() throws Exception {
    // technically this is an ApplicationCommand, but we're only interested in
    // checking the builtin functions
    populateProject(EMPTY_PROJECT);
    FunctionCommand.FunctionListCommand cmd = setupCommand(new FunctionCommand.FunctionListCommand());
    cmd.all = true;

    Table table = (Table) cmd.run();

    assertThat(table, TableMatchers.hasRow(
        Pair.of("id", "abs"), Pair.of("description", containsString("absolute value of a number"))
    ));
    assertStringsPresentFor(table, "id", "description");
  }

  private List<String> getBuiltinFunctions() {
    List<String> functions = new ArrayList<>();
    FunctionCommand.FunctionListCommand cmd = setupCommand(new FunctionCommand.FunctionListCommand());
    cmd.all = true;
    Table table = (Table) cmd.run();

    for (List<Text> row : table.getRows()) {
      // assume the function 'id' is the first field
      functions.add(row.get(0).plainString());
    }
    return functions;
  }

  @Test
  public void canGetBuiltinFunctionInfo() throws Exception {
    // technically this is an ApplicationCommand, but we're only interested in
    // checking the builtin functions
    populateProject(EMPTY_PROJECT);
    FunctionCommand.FunctionInfoCommand cmd = setupCommand(new FunctionCommand.FunctionInfoCommand());

    // loop through all the functions and check each one's info
    for (String functionName : getBuiltinFunctions()) {
      cmd.functionId = functionName;
      Table table = (Table) cmd.run();

      assertStringsPresentFor(table, "name", functionName);
    }
  }
}
