/*
 * 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.postgis;


import static nz.org.riskscape.engine.cli.BaseCliIntegrationTest.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

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

import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;

import nz.org.riskscape.engine.cli.BaseCliIntegrationTest2;
import nz.org.riskscape.engine.cli.CliIntegrationTest;
import nz.org.riskscape.engine.cli.CommandResult;
import nz.org.riskscape.engine.cli.PostgisIntegrationTestRunner;

@RunWith(PostgisIntegrationTestRunner.class)
@Category({CliIntegrationTest.class, PostGISIntegrationTestMarker.class})
public class PostGISOutputTest extends BaseCliIntegrationTest2 {

  private static final String POSTGIS_OUTPUT_WARNING = "[WARNING] Problems found with --output";

  @Test
  public void canSaveToUserSpecifiedPostGis() {
    CommandResult result = runner.execute(defaultCommand()
        .option("--project", homeDir().resolve("project.ini"))
        .subcommands("model", "run", "cities")
        .option("--parameter", "output_name='canSaveToUserSpecifiedPostGis'")
        .option("--output", "postgis://riskscape@riskscape-postgis/riskscape")
        .environmentVariable("PGPASSWORD", "riskscape")
    );
    assertThat(result, exitsWith(0));
    // the last entry in stdout is the postgis URI to the save location
    String savedTo = result.stdout.get(result.stdout.size() - 1);
    assertThat(savedTo, startsWith("postgis://riskscape"));
    fetchFromPostgisAndAssert(savedTo);

    assertThat(result.stderr, not(hasItem(is(POSTGIS_OUTPUT_WARNING))));
  }

  @Test
  public void canSaveToUserSpecifiedPostGisWithPasswordWarning() {
    CommandResult result = runner.execute(defaultCommand()
        .option("--project", homeDir().resolve("project.ini"))
        .subcommands("model", "run", "cities")
        .option("--parameter", "output_name='canSaveToUserSpecifiedPostGis'")
        .option("--output", "postgis://riskscape:riskscape@riskscape-postgis/riskscape")
    );
    assertThat(result, exitsWith(0));
    // the last entry in stdout is the postgis URI to the save location
    String savedTo = result.stdout.get(result.stdout.size() - 1);
    assertThat(savedTo, startsWith("postgis://riskscape"));
    fetchFromPostgisAndAssert(savedTo);

    assertThat(result.stderr,
        hasItem(containsString("Supplying postgis passwords in clear text is insecure"))
    );
  }

  @Test
  public void canSaveToProjectDefaultOutputPostGis() {
    CommandResult result = runner.execute(defaultCommand()
        .option("--project", homeDir().resolve("project-with-output-base.ini"))
        .subcommands("model", "run", "cities")
        .option("--parameter", "output_name='canSaveToUserSpecifiedPostGis'")
        .environmentVariable("PGPASSWORD", "riskscape")
    );
    assertThat(result, exitsWith(0));
    // the last entry in stdout is the postgis URI to the save location
    String savedTo = result.stdout.get(result.stdout.size() - 1);
    assertThat(savedTo, startsWith("postgis://riskscape"));
    fetchFromPostgisAndAssert(savedTo);

    assertThat(result.stderr, not(hasItem(is(POSTGIS_OUTPUT_WARNING))));
  }

  @Test
  public void willWarnIfSaveFormatIsIgnored() {
    CommandResult result = runner.execute(defaultCommand()
        .option("--project", homeDir().resolve("project.ini"))
        .subcommands("model", "run", "cities")
        .option("--parameter", "output_name='willWarnIfSaveFormatIsIgnored'")
        .option("--output", "postgis://riskscape@riskscape-postgis/riskscape")
        .option("--format", "csv")
        .environmentVariable("PGPASSWORD", "riskscape")
    );
    assertThat(result, exitsWith(0));
    // the last entry in stdout is the postgis URI to the save location
    String savedTo = result.stdout.get(result.stdout.size() - 1);
    assertThat(result.stderr, contains(
        containsString("The 'beta' plugin is enabled"),
        // we should see the PostGIS warning this time
        is(POSTGIS_OUTPUT_WARNING),
        containsString("Ignoring the user-specified 'csv' format")
    ));
    assertThat(savedTo, startsWith("postgis://riskscape"));
    fetchFromPostgisAndAssert(savedTo);
  }

  @Test
  public void willDrainWarningsFromSinkTask() {
    // this time the format is in the Save step itself because we want to check that
    // SinkTask will drain warnings (PostGIS is one of a few things that will
    // generate a warning in the save step)
    CommandResult result = runner.execute(defaultCommand()
        .option("--project", homeDir().resolve("project.ini"))
        .subcommands("model", "run", "cities-save-to-csv-format")
        .option("--parameter", "output_name='willWarnIfSaveFormatIsIgnored'")
        .option("--output", "postgis://riskscape@riskscape-postgis/riskscape")
        .environmentVariable("PGPASSWORD", "riskscape")
    );
    assertThat(result, exitsWith(0));
    // the last entry in stdout is the postgis URI to the save location
    String savedTo = result.stdout.get(result.stdout.size() - 1);
    assertThat(result.stderr, contains(
        containsString("The 'beta' plugin is enabled"),
        containsString("Problems found with `save` step on line"),
        containsString("Ignoring the user-specified 'csv' format")
    ));
    assertThat(savedTo, startsWith("postgis://riskscape"));
    fetchFromPostgisAndAssert(savedTo);

    assertThat(result.stderr, not(hasItem(is(POSTGIS_OUTPUT_WARNING))));
  }

  private void fetchFromPostgisAndAssert(String location) {
    CommandResult result = runner.execute(defaultCommand()
        .option("--project", homeDir().resolve("project.ini"))
        .subcommands("model", "run", "read-to-csv")
        .option("--parameter", String.format("input='%s'", location))
        .option("--output", "output")
        .option("--replace")
        .environmentVariable("PGPASSWORD", "riskscape")
    );
    assertThat(result, exitsWith(0));
    String savedTo = result.stdout.get(result.stdout.size() - 1);
    assertThat(savedTo, endsWith("output/results.csv"));

    List<List<String>> results = readColumnsFromCsv(
        environment().localSourceTree.resolve("output").resolve("results.csv"),
        "geom", "name");

    // The model reprojects geometries to lat/long
    assertThat(results, containsInAnyOrder(
        Arrays.asList("POINT (-33 151)", "Sydney"),
        Arrays.asList("POINT (-36 174)", "Auckland"),
        Arrays.asList("POINT (-41 174)", "Wellington")
    ));
  }

}
