/*
 * 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.Arrays;
import java.util.List;

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

import nz.org.riskscape.engine.cli.BaseCliIntegrationTest;
import nz.org.riskscape.engine.cli.GeoServerIntegrationTestRunner;
import nz.org.riskscape.engine.cli.RunWithGeoServerIntegrationTest;
import nz.org.riskscape.test.SkipTestOnWindows;

@RunWith(GeoServerIntegrationTestRunner.class)
public class WFSInputTest extends BaseCliIntegrationTest {

  public static final String BASE_URL = RunWithGeoServerIntegrationTest.BASE_URL;

  @Before
  public void setup() throws Exception {
    super.setup();
    populateRiskscapeHomeEnv = false;
  }

  @Test
  @Category(SkipTestOnWindows.class)
  public void canQueryWfsDataStore() {
    ExecResult result = execute(params.clear()
        .add("bookmark")
        .add("info")
        .add("points"));

    assertEquals(0, result.retCode.intValue());

    assertThat(result.stdout, hasItems(
      containsString(""),
      containsString("points"),
      containsString("  Description :"),
      containsString("  Location    : http://riskscape-test-geoserver:8080/geoserver/riskscape/wfs"),
      containsString("   the_geom"),
      containsString("    id[Integer]"),
      containsString("    construct[Text]"),
      containsString("    stories[Integer]"),
      containsString("  CRS (full)  : PROJCS[\"NZGD2000 / New Zealand Transverse Mercator 2000")));

  }

  @Test
  @Category(SkipTestOnWindows.class)
  public void canSeeOtherLayerNamesWhenBadLayerName() {
    ExecResult result = execute(params.clear()
        .add("bookmark")
        .add("info")
        .add("wfs-missing"));

    assertEquals(1, result.retCode.intValue());

    assertThat(result.stderr, hasItem(
      containsString("Requested layer 'riskscape:not-here' does not exist.  "
          + "Available: [riskscape:has-null-geom, riskscape:assetsAsPoints1]")));

  }

  @Test
  public void canReadFromWfs() {
    ExecResult result = execute(params.clear()
        .add("model")
        .add("run")
        .add("read")
        .add("--output=" + mappedOutputDir.toString())
    );

    List<List<String>> results = readColumnsFromCsv(outputDir.resolve("results.csv"),
        "the_geom", "id", "construct", "stories");

    assertThat(results, contains(
        Arrays.asList("POINT (5475355.6113882 1580157.78787681)", "1", "timber", "1"),
        Arrays.asList("POINT (5475243.22362934 1580434.42791458)", "2", "steel", "2")
    ));
  }

  @Test
  public void canReadFromWfsAndSetAttributes() {
    ExecResult result = execute(params.clear()
        .add("model")
        .add("run")
        .add("read")
        .add("--output=" + mappedOutputDir.toString())
        .add("--parameter=bookmark='points-mapped'")
    );

    List<List<String>> results = readColumnsFromCsv(outputDir.resolve("results.csv"),
        "the_geom", "id", "construct", "stories", "construction");

    assertThat(results, contains(
        Arrays.asList("POINT (5475355.6113882 1580157.78787681)", "1", "timber", "1", "timber"),
        Arrays.asList("POINT (5475243.22362934 1580434.42791458)", "2", "steel", "2", "steel")
    ));
  }

  @Test
  public void canReadFromWfsThenReprojectGeometries() {
    ExecResult result = execute(params.clear()
        .add("model")
        .add("run")
        .add("read-and-reproject")
        .add("--output=" + mappedOutputDir.toString())
    );

    List<List<String>> results = readColumnsFromCsv(outputDir.resolve("results.csv"),
        "the_geom", "id", "construction");

    // we've reproject the NZ points to lat/long
    assertThat(results, contains(
        contains(matchesPattern("POINT \\(-40.87262793405\\d* 172.76452118970\\d*\\)"), is("1"), is("timber")),
        contains(matchesPattern("POINT \\(-40.87364701943\\d* 172.76780067125\\d*\\)"), is("2"), is("steel"))
    ));
  }

  @Test
  public void canReadFromWfsWithCrsNameThenReprojectGeometries() {
    ExecResult result = execute(params.clear()
        .add("model")
        .add("run")
        .add("read-and-reproject")
        .add("--output=" + mappedOutputDir.toString())
        .add("--parameter=bookmark='points-shifted'")
    );

    List<List<String>> results = readColumnsFromCsv(outputDir.resolve("results.csv"),
        "the_geom", "id", "construction");

    // because the NZ points had crs-name set for north america, that is where there lat/long
    // coordinates now place them.
    assertThat(results, contains(
        contains(matchesPattern("POINT \\(43.30529158502\\d* -57.03819639385\\d*\\)"), is("1"), is("timber")),
        contains(matchesPattern("POINT \\(43.30609495735\\d* -57.03841053368\\d*\\)"), is("2"), is("steel"))
    ));
  }

  @Test
  @Category(SkipTestOnWindows.class)
  public void willSkipInvalidTuples() {
    ExecResult result = execute(params.clear()
        .add("pipeline")
        .add("eval")
        .add("input('has-null-geom')"));
    assertEquals(0, result.retCode.intValue());

    assertThat(result.stderr, allOf(
        hasItem(containsString("An invalid row of input data has been skipped")),
        hasItem(containsString("The 'the_geom' geometry attribute for object ID 'has-null-geom.18' is null"))
    ));
  }

  @Test
  @Category(SkipTestOnWindows.class)
  public void willQueryWithUserSpecifiedPageSize() {
    ExecResult result = execute(params.clear()
        .add("model")
        .add("run")
        .add("read")
        .add("--output=" + mappedOutputDir.toString())
        .add("--parameter=bookmark='has-null-geom-small-pages'"));
    assertEquals(0, result.retCode.intValue());

    List<List<String>> results = readColumnsFromCsv(outputDir.resolve("results.csv"),
        "the_geom", "building", "stories");

    List<String> buildings = results.stream()
        .map(l -> l.get(1))
        .toList();

    assertThat(buildings, containsInAnyOrder(
        "Central Perk",
        "The Hayden Planetarium",
        "That Cafe From Seinfeld",
        "The Met",
        "Statue of Liberty",
        "Ed Sullivan Theatre",
        "NYSE",
        "Tiffany and Co",
        "Natural History Museum",
        "Chase Bank",
        "Federal Reserve",
        "Police Plaza",
        "City Hall",
        "Flat Iron Building",
        "United Nations",
        "Madison Square Garden",
        "Empire State Building",
        //"Trump Tower", # skipped due to null geometry
        "New York Public Library",
        "Union Square",
        "Monica's Apartment",
        "Radio City",
        "The Plaza Hotel",
        "Central Park Zoo",
        "Times Square",
        "Rockefeller Center"
    ));
  }

  @Test
  @Category(SkipTestOnWindows.class)
  public void willFailOnInvalidTuples() {
    ExecResult result = execute(params.clear()
        .add("pipeline")
        .add("eval")
        .add("input('has-null-geom-no-skip')"));

    // with skip-invalid false we expect the pipeline to fail
    assertEquals(1, result.retCode.intValue());

    assertThat(result.stderr,
        hasItem(containsString("The 'the_geom' geometry attribute for object ID 'has-null-geom.18' is null"))
    );
  }

  @Test
  @Category(SkipTestOnWindows.class)
  public void connectionFailureShowsUsefulError() {
    ExecResult result = execute(params.clear()
        .add("pipeline")
        .add("eval")
        .add("input('wfs-wrong-port')"));

    // with skip-invalid false we expect the pipeline to fail
    assertEquals(1, result.retCode.intValue());

    assertThat(result.stderr,
        hasItem(allOf(
            containsString("Connect to riskscape-test-geoserver:8880"),
            containsString("failed: Connection refused")
        ))
    );
  }

}
