test_that("geodist samples points at the center of raster cells", {
  raster <- terra::rast(res = 20, xmin = 0, xmax = 1000, ymin = 0, ymax = 1000, crs = "EPSG:3035", vals=1:2500)
  pts <- terra::as.points(raster) |> sf::st_as_sf()
  gd <- geodist(pts, raster)
  gd_sel <- subset(gd, what == "sample-to-sample")
  mean_dist <- mean(gd_sel$dist)
  expect_equal(mean_dist, 20)

})

test_that("geodist handles CRS mismatch between x and modeldomain", {
  aoi <- sf::st_as_sfc("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", crs = 25832)
  pts <- sf::st_as_sfc("MULTIPOINT ((500000 5700000), (500100 5700100))", crs = 3035) |> 
    sf::st_cast("POINT")

  dist <- geodist(pts, aoi, dist_space = "geographical")
  expect_true(all(dist$dist > 0))
})

test_that("geodist warns on missing CRS", {
  pts <- sf::st_as_sfc("POINT (1 1)")
  dist <- expect_warning(
    geodist(sf::st_as_sf(pts), preddata = sf::st_as_sf(pts), dist_space = "geographical"),
    "Missing CRS of the modeldomain or prediction points. Assuming projected CRS."
  )
})

test_that("geodist supports Fibonacci sampling", {
  skip_if_not_installed("rnaturalearth")
  data(splotdata)
  studyArea <- rnaturalearth::ne_countries(continent = "South America", returnclass = "sf")
  expect_message(
    geodist(splotdata, studyArea, sampling = "Fibonacci", dist_space = "geographical", dist_fun = "great_circle"),
    "Sampling 2000 prediction locations from the modeldomain vector."
  )
})


test_that("geodist works with points and polygon in geographic space", {
  skip_if_not_installed("rnaturalearth")
  data(splotdata)
  studyArea <- rnaturalearth::ne_countries(continent = "South America", returnclass = "sf")
  set.seed(1)
  folds <- data.frame("folds"=sample(1:3, nrow(splotdata), replace=TRUE))
  folds <- CreateSpacetimeFolds(folds, spacevar="folds", k=3)

  dist_geo <- geodist(x=splotdata,
                      modeldomain=studyArea,
                      CVtest=folds$indexOut,
                      dist_space = "geographical",
                      dist_fun = "great_circle")

  mean_sample2sample <- round(mean(dist_geo[dist_geo$what=="sample-to-sample","dist"]))
  mean_CV_distances <- round(mean(dist_geo[dist_geo$what=="CV-distances","dist"]))
  nrow_dist <- nrow(dist_geo)

  expect_equal(mean_sample2sample, 20321)
  expect_equal(mean_CV_distances, 25616)
  expect_equal(nrow_dist, 3405)


})

test_that("geodist works with points and polygon in feature space", {
  skip_if_not_installed("rnaturalearth")
  data(splotdata)
  studyArea <- rnaturalearth::ne_countries(continent = "South America", returnclass = "sf")
  set.seed(1)
  folds <- data.frame("folds"=sample(1:3, nrow(splotdata), replace=TRUE))
  folds <- CreateSpacetimeFolds(folds, spacevar="folds", k=3)
  predictors <- terra::rast(system.file("extdata","predictors_chile.tif", package="CAST"))

  dist_fspace <- geodist(x = splotdata,
                         modeldomain = predictors,
                         CVtest=folds$indexOut,
                         dist_space = "feature",
                         variables = c("bio_1","bio_12", "elev"))

  mean_sample2sample <- round(mean(dist_fspace[dist_fspace$what=="sample-to-sample","dist"]), 4)
  mean_CV_distances <- round(mean(dist_fspace[dist_fspace$what=="CV-distances","dist"]), 4)

  expect_equal(mean_sample2sample, 0.0843)
  expect_equal(mean_CV_distances, 0.1035)

})

test_that("geodist works with points and polygon in feature space using Mahalanobis distances", {
  skip_if_not_installed("rnaturalearth")
  data(splotdata)
  studyArea <- rnaturalearth::ne_countries(continent = "South America", returnclass = "sf")
  set.seed(1)
  folds <- data.frame("folds"=sample(1:3, nrow(splotdata), replace=TRUE))
  folds <- CreateSpacetimeFolds(folds, spacevar="folds", k=3)
  predictors <- terra::rast(system.file("extdata","predictors_chile.tif", package="CAST"))

  dist_fspace <- geodist(x = splotdata,
                         modeldomain = predictors,
                         CVtest=folds$indexOut,
                         dist_space = "feature",
                         variables = c("bio_1","bio_12", "elev"), 
                         dist_fun = "mahalanobis")

  mean_sample2sample <- round(mean(dist_fspace[dist_fspace$what=="sample-to-sample","dist"]), 4)
  mean_CV_distances <- round(mean(dist_fspace[dist_fspace$what=="CV-distances","dist"]), 4)

  expect_equal(mean_sample2sample, 0.0900)
  expect_equal(mean_CV_distances, 0.1104)

})


test_that("geodist works space with points and preddata in geographic space", {
  aoi <- sf::st_as_sfc("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", crs="epsg:25832")
  tpoints <- sf::st_as_sfc("MULTIPOINT ((1 1), (1 2), (2 2), (2 3), (1 4), (5 4))", crs="epsg:25832") |>
    sf::st_cast("POINT")
  set.seed(1)
  ppoints <- suppressWarnings(sf::st_sample(aoi, 20, type="regular")) |>
    sf::st_set_crs("epsg:25832")

  set.seed(1)
  folds <- data.frame("folds"=sample(1:3, length(tpoints), replace=TRUE))
  folds <- CreateSpacetimeFolds(folds, spacevar="folds", k=3)

  dist_geo <- geodist(x=tpoints,
                      preddata=ppoints,
                      dist_space = "geographical")

  mean_sample2sample <- round(mean(dist_geo[dist_geo$what=="sample-to-sample","dist"]), 4)
  mean_prediction_to_sample <- round(mean(dist_geo[dist_geo$what=="prediction-to-sample","dist"]), 4)

  expect_equal(mean_sample2sample, 1.4294)
  expect_equal(mean_prediction_to_sample, 2.9401)


})


test_that("geodist works with points and preddata in feature space", {
  aoi <- sf::st_as_sfc("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", crs="epsg:25832")
  tpoints <- sf::st_as_sfc("MULTIPOINT ((1.5 1.5), (1.5 2.5), (2.5 2.5), (2.5 3.5), (1.5 4.5), (5.5 4.5))", crs="epsg:25832") |>
    sf::st_cast("POINT")
  set.seed(1)
  ppoints <- suppressWarnings(sf::st_sample(aoi, 20, type="regular")) |>
    sf::st_set_crs("epsg:25832")

  raster <- terra::rast(nrows=10, ncols=10, nlyrs=1, xmin=0, xmax=10,
                        ymin=0, ymax=10, crs="epsg:25832", vals=1:100)

  dist <- geodist(x=tpoints,
                  modeldomain=raster,
                  preddata=ppoints,
                  dist_space = "feature")

  mean_sample2sample <- round(mean(dist[dist$what=="sample-to-sample","dist"]), 4)
  mean_prediction_to_sample <- round(mean(dist[dist$what=="prediction-to-sample","dist"]), 4)

  expect_equal(mean_sample2sample, 0.3814)
  expect_equal(mean_prediction_to_sample, 1.0783)


})


test_that("geodist works with points and raster in geographic space", {
  tpoints <- sf::st_as_sfc("MULTIPOINT ((1 1), (1 2), (2 2), (2 3), (1 4), (5 4))", crs="epsg:25832") |>
    sf::st_cast("POINT")

  raster <- terra::rast(nrows=10, ncols=10, nlyrs=1, xmin=0, xmax=10,
                        ymin=0, ymax=10, crs="epsg:25832", vals=1:100)

  dist <- geodist(x=tpoints,
                  modeldomain=raster,
                  dist_space = "geographical")

  mean_sample2sample <- round(mean(dist[dist$what=="sample-to-sample","dist"]), 4)
  expect_equal(mean_sample2sample, 1.4294)


})


test_that("geodist works with points and raster in feature space", {
  tpoints <- sf::st_as_sfc("MULTIPOINT ((1.5 1.5), (1.5 2.5), (2.5 2.5), (2.5 3.5), (1.5 4.5), (5.5 4.5))", crs="epsg:25832") |>
    sf::st_cast("POINT")

  raster <- terra::rast(nrows=10, ncols=10, nlyrs=1, xmin=0, xmax=10,
                        ymin=0, ymax=10, crs="epsg:25832", vals=1:100)

  dist <- geodist(x=tpoints,
                  modeldomain=raster,
                  dist_space = "feature")

  mean_sample2sample <- round(mean(dist[dist$what=="sample-to-sample","dist"]), 4)
  expect_equal(mean_sample2sample, 0.3814)


})


test_that("geodist works with points and stars raster in geographic space", {
  skip_if_not_installed("stars")
  tpoints <- sf::st_as_sfc("MULTIPOINT ((1.5 1.5), (1.5 2.5), (2.5 2.5), (2.5 3.5), (1.5 4.5), (5.5 4.5))", crs="epsg:25832") |>
    sf::st_cast("POINT")

  raster <- terra::rast(nrows=10, ncols=10, nlyrs=1, xmin=0, xmax=10,
                        ymin=0, ymax=10, crs="epsg:25832", vals=1:100) |>
    stars::st_as_stars()

  dist <- geodist(x=tpoints,
                  modeldomain=raster,
                  dist_space = "feature")

  mean_sample2sample <- round(mean(dist[dist$what=="sample-to-sample","dist"]), 4)
  expect_equal(mean_sample2sample, 0.3814)


})



test_that("geodist works with points and test data in geographic space", {
  aoi <- sf::st_as_sfc("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", crs="epsg:25832")
  tpoints <- sf::st_as_sfc("MULTIPOINT ((1 1), (1 2), (2 2), (2 3), (1 4), (5 4))", crs="epsg:25832") |>
    sf::st_cast("POINT")

  set.seed(1)
  test_point <- suppressWarnings(sf::st_sample(aoi, 20, type="regular")) |>
    sf::st_set_crs("epsg:25832")

  dist <- geodist(x=tpoints,
                  modeldomain=aoi,
                  testdata=test_point,
                  dist_space = "geographical")

  mean_sample2sample <- round(mean(dist[dist$what=="sample-to-sample","dist"]), 4)
  mean_test_to_sample <- round(mean(dist[dist$what=="test-to-sample","dist"]), 4)

  expect_equal(mean_sample2sample, 1.4294)
  expect_equal(mean_test_to_sample, 2.9401)



})


test_that("geodist works with points and test data in feature space", {
  aoi <- sf::st_as_sfc("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", crs="epsg:25832")

  raster <- terra::rast(nrows=10, ncols=10, nlyrs=1, xmin=0, xmax=10,
                        ymin=0, ymax=10, crs="epsg:25832", vals=1:100)

  tpoints <- sf::st_as_sfc("MULTIPOINT ((1 1), (1 2), (2 2), (2 3), (1 4), (5 4))", crs="epsg:25832") |>
    sf::st_cast("POINT")

  set.seed(1)
  test_points <- suppressWarnings(sf::st_sample(aoi, 20, type="random")) |>
    sf::st_set_crs("epsg:25832")

  dist <- geodist(x=tpoints,
                  modeldomain=raster,
                  testdata = test_points,
                  dist_space = "feature")

  mean_sample2sample <- round(mean(dist[dist$what=="sample-to-sample","dist"]), 4)
  mean_test_to_sample <- round(mean(dist[dist$what=="test-to-sample","dist"]), 4)

  expect_equal(mean_sample2sample, 0.3814)
  expect_equal(mean_test_to_sample, 1.4524)


})


test_that("geodist works with categorical variables in feature space using Gower distances", {
  set.seed(1234)
  predictor_stack <- terra::rast(system.file("extdata","predictors_2012-03-25.tif",package="CAST"))
  predictors <- c("DEM","TWI", "NDRE.M", "Easting", "Northing", "fct")
  predictor_stack$fct <- factor(c(rep(LETTERS[1], terra::ncell(predictor_stack)/2),
                                  rep(LETTERS[2], terra::ncell(predictor_stack)/2)))

  predictor_stack <- predictor_stack[[predictors]]
  studyArea <- predictor_stack
  studyArea[!is.na(studyArea)] <- 1
  studyArea <- terra::as.polygons(studyArea, values = FALSE, na.all = TRUE) |>
    sf::st_as_sf() |>
    sf::st_union()

  pts <- clustered_sample(studyArea, 30, 5, 60)
  pts <- sf::st_transform(pts, crs = sf::st_crs(studyArea))
  pts <- terra::extract(predictor_stack, terra::vect(pts), ID=FALSE, bind=TRUE) |>
    sf::st_as_sf()

  test_pts <- clustered_sample(studyArea, 50, 5, 20)

  folds <- data.frame("folds"=sample(1:3, nrow(pts), replace=TRUE))
  folds <- CreateSpacetimeFolds(folds, spacevar="folds", k=3)

  sf::st_as_sf(terra::extract(predictor_stack,terra::vect(pts$geometry),bind=TRUE))

  dist <- geodist(x=pts,
                  modeldomain=predictor_stack,
                  dist_space = "feature",
                  dist_fun = "gower",
                  testdata = test_pts,
                  CVtest = folds$indexOut,
                  scale_vars = FALSE)

  mean_sample2sample <- round(mean(dist[dist$what=="sample-to-sample","dist"]), 4)
  mean_prediction2sample <- round(mean(dist[dist$what=="prediction-to-sample","dist"]), 4)
  mean_test2sample <- round(mean(dist[dist$what=="test-to-sample","dist"]), 4)
  mean_CV_distance <- round(mean(dist[dist$what=="CV-distances","dist"]), 4)

  expect_equal(mean_sample2sample, 0.0459)
  expect_equal(mean_prediction2sample, 0.0931)
  expect_equal(mean_test2sample, 0.2128)
  expect_equal(mean_CV_distance, 0.0271)
})


test_that("geodist works in temporal space", {
  data(cookfarm)
  dat <- sf::st_as_sf(cookfarm,coords=c("Easting","Northing"))
  sf::st_crs(dat) <- 26911
  trainDat <- dat[dat$altitude==-0.3&lubridate::year(dat$Date)==2010,]
  predictionDat <- dat[dat$altitude==-0.3&lubridate::year(dat$Date)==2011,]
  dist <- CAST::geodist(trainDat, preddata = predictionDat, dist_space = "time", dist_fun = "abs_time", time_unit = "days")

  mean_sample2sample <- round(mean(dist[dist$what=="sample-to-sample","dist"]), 4)
  mean_prediction_to_sample <- round(mean(dist[dist$what=="prediction-to-sample","dist"]), 4)

  expect_equal(mean_sample2sample, 0.02)
  expect_equal(mean_prediction_to_sample, 194.7656)

  dist <- CAST::geodist(trainDat, preddata = predictionDat, dist_space = "time", dist_fun = "abs_time", time_unit = "hours")
  mean_prediction_to_sample <- round(mean(dist[dist$what=="prediction-to-sample","dist"]), 2)
  expect_equal(mean_prediction_to_sample, 4674.37)

})

test_that("geodist works in temporal space and with CV", {
  data(cookfarm)
  dat <- sf::st_as_sf(cookfarm,coords=c("Easting","Northing"))
  sf::st_crs(dat) <- 26911
  trainDat <- dat[dat$altitude==-0.3&lubridate::year(dat$Date)==2010,]
  predictionDat <- dat[dat$altitude==-0.3&lubridate::year(dat$Date)==2011,]
  trainDat$week <- lubridate::week(trainDat$Date)
  set.seed(100)
  CVtest <- CreateSpacetimeFolds(trainDat,timevar = "week")

  dist <- CAST::geodist(trainDat,preddata = predictionDat,CVtest = CVtest$indexOut,
                        dist_space = "time", dist_fun = "abs_time", time_unit = "days")

  mean_cv <- round(mean(dist[dist$what=="CV-distances","dist"]), 3)

  expect_equal(mean_cv,  2.446)
})

test_that("geodist works with buffered CV", {

  # Simulate 100 clustered training points in a 100x100 square
  set.seed(123)
  poly <- list(matrix(c(0,0,0,100,100,100,100,0,0,0), ncol=2, byrow=TRUE))
  sample_poly <- sf::st_polygon(poly)
  train_points <- clustered_sample(sample_poly, 100, 10, 5)
  pred_points <- sf::st_sample(sample_poly, 100, type = "regular")
  nndm_pred <- nndm(train_points, predpoints=pred_points)

  geod <- suppressWarnings(geodist(train_points, preddata = pred_points, CVtest = nndm_pred$indx_test, 
    CVtrain = nndm_pred$indx_train, dist_fun = "euclidean"))

  geod_dist_cv_mean <- round(mean(geod[geod$what == "CV-distances",]$dist), 3)
  nndm_dist_cv_mean <- round(mean(nndm_pred$Gjstar), 3)
  
  expect_equal(geod_dist_cv_mean, nndm_dist_cv_mean)

})

test_that("plot for geodist runs and returns ggplot", {
  raster <- terra::rast(res = 20, xmin = 0, xmax = 1000, ymin = 0, ymax = 1000, crs = "EPSG:3035", vals=1:2500)
  pts <- terra::as.points(raster) |> sf::st_as_sf()
  gd <- geodist(pts, raster)

  expect_s3_class(plot(gd), "ggplot")
})
