# --- Test Suite for AddiVortes Class Methods Syntax Issues ---
# Tests for checking input validation, error handling, and syntax issues
# for all AddiVortes class methods

# --- Helper: Create a simple test AddiVortes object ---
create_test_object <- function() {
  withr::local_seed(42)
  X <- matrix(rnorm(50), 10, 5)
  Y <- rnorm(10)

  AddiVortes(Y, X, m = 3, totalMCMCIter = 20, mcmcBurnIn = 5, showProgress = FALSE)
}

# --- Tests for new_AddiVortes() constructor ---

test_that("new_AddiVortes requires all arguments", {
  # Test that constructor fails without required arguments
  expect_error(
    new_AddiVortes(
      posteriorTess = list(),
      posteriorDim = list(),
      posteriorSigma = numeric()
      # Missing arguments
    ),
    "argument .* is missing"
  )
})

test_that("new_AddiVortes creates correct class", {
  # Test that constructor creates object of correct class
  obj <- new_AddiVortes(
    posteriorTess = list(),
    posteriorDim = list(),
    posteriorSigma = numeric(),
    posteriorPred = list(),
    xCentres = c(0, 0),
    xRanges = c(1, 1),
    yCentre = 0,
    yRange = 1,
    inSampleRmse = 0.5
  )

  expect_s3_class(obj, "AddiVortes")
  expect_true(inherits(obj, "AddiVortes"))
})

# --- Tests for print.AddiVortes() ---

test_that("print.AddiVortes requires AddiVortes object", {
  # Test that print fails with non-AddiVortes object
  not_addivortes <- list(a = 1, b = 2)

  expect_error(
    print.AddiVortes(not_addivortes),
    "must be an object of class 'AddiVortes'"
  )
})

test_that("print.AddiVortes works with valid object", {
  skip_on_cran()
  # Test that print works with a valid AddiVortes object
  obj <- create_test_object()

  expect_output(print(obj), "AddiVortes Model")
  expect_output(print(obj), "Model Formula:")
  expect_output(print(obj), "In-sample RMSE:")
})

test_that("print.AddiVortes handles empty posterior samples", {
  # Test print with object containing no posterior samples
  obj <- new_AddiVortes(
    posteriorTess = list(),
    posteriorDim = list(),
    posteriorSigma = numeric(),
    posteriorPred = list(),
    xCentres = c(0),
    xRanges = c(1),
    yCentre = 0,
    yRange = 1,
    inSampleRmse = 0.5
  )

  expect_output(print(obj), "No posterior samples available")
})

# --- Tests for summary.AddiVortes() ---

test_that("summary.AddiVortes requires AddiVortes object", {
  # Test that summary fails with non-AddiVortes object
  not_addivortes <- list(a = 1, b = 2)

  expect_error(
    summary.AddiVortes(not_addivortes),
    "`object` must be an object of class 'AddiVortes'"
  )
})

test_that("summary.AddiVortes works with valid object", {
  skip_on_cran()
  # Test that summary works with a valid AddiVortes object
  obj <- create_test_object()

  expect_output(summary(obj), "AddiVortes Model")
  expect_output(summary(obj), "Model Information:")
})

test_that("covariate summary helper reports counts and categorical encoding", {
  x <- data.frame(
    cont = c(1, 2, 3),
    sph = c(4, 5, 6),
    grp = factor(c("A", "B", "C"))
  )

  summary_lines <- AddiVortes:::formatCovariateSummary_internal(
    x,
    metric = c("E", "S", "E"),
    catEncoding = list(encodedBinaryCols = 1:2)
  )

  expect_equal(
    summary_lines,
    c(
      "Covariate summary: 1 continuous, 1 spherical, 1 categorical.",
      paste0(
        "Categorical covariates are expanded to 2 one-hot encoded binary ",
        "columns, with the first level of each categorical variable used as ",
        "the reference category."
      )
    )
  )
})

test_that("AddiVortes startup output reports categorical encoding", {
  skip_on_cran()
  withr::local_seed(123)

  x <- data.frame(
    cont1 = rnorm(8),
    grp = factor(sample(c("A", "B", "C"), 8, replace = TRUE)),
    cont2 = runif(8)
  )
  y <- rnorm(8)

  output <- capture.output(
    fit <- AddiVortes(y, x, m = 2, totalMCMCIter = 2, mcmcBurnIn = 0, showProgress = TRUE)
  )

  expect_s3_class(fit, "AddiVortes")
  expect_match(
    paste(output, collapse = "\n"),
    "Covariate summary: 2 continuous, 1 categorical\\."
  )
  expect_match(
    paste(output, collapse = "\n"),
    "Categorical covariates are expanded to"
  )
})

# --- Tests for predict.AddiVortes() ---

test_that("predict.AddiVortes requires AddiVortes object", {
  # Test that predict fails with non-AddiVortes object
  not_addivortes <- list(a = 1, b = 2)
  X_new <- matrix(rnorm(25), 5, 5)

  expect_error(
    predict.AddiVortes(not_addivortes, X_new),
    "must be of class 'AddiVortes'"
  )
})

test_that("predict.AddiVortes requires matrix newdata", {
  skip_on_cran()
  # Test that predict fails if newdata is not a matrix
  obj <- create_test_object()

  expect_error(
    predict(obj, newdata = c(1, 2, 3, 4, 5)),
    "must be a matrix"
  )
})

test_that("predict.AddiVortes checks newdata dimensions", {
  skip_on_cran()
  # Test that predict fails if newdata has wrong number of columns
  obj <- create_test_object()
  X_wrong <- matrix(rnorm(30), 10, 3) # Wrong number of columns

  expect_error(
    predict(obj, X_wrong),
    "Number of columns.*does not match"
  )
})

test_that("predict.AddiVortes works with valid inputs", {
  skip_on_cran()
  # Test that predict works with valid inputs
  obj <- create_test_object()
  X_new <- matrix(rnorm(25), 5, 5)

  preds <- predict(obj, X_new, showProgress = FALSE)

  expect_type(preds, "double")
  expect_length(preds, 5)
  expect_false(any(is.na(preds)))
})

test_that("predict.AddiVortes type argument works", {
  skip_on_cran()
  # Test different type arguments
  obj <- create_test_object()
  X_new <- matrix(rnorm(25), 5, 5)

  # Test response type
  preds_response <- predict(obj, X_new, type = "response", showProgress = FALSE)
  expect_type(preds_response, "double")
  expect_length(preds_response, 5)

  # Test quantile type
  preds_quantile <- predict(obj, X_new,
    type = "quantile",
    quantiles = c(0.025, 0.975), showProgress = FALSE
  )
  expect_true(is.matrix(preds_quantile))
  expect_equal(nrow(preds_quantile), 5)
  expect_equal(ncol(preds_quantile), 2)
})

test_that("predict.AddiVortes interval argument works", {
  skip_on_cran()
  # Test different interval arguments
  obj <- create_test_object()
  X_new <- matrix(rnorm(25), 5, 5)

  # Test credible intervals
  preds_credible <- predict(obj, X_new,
    type = "quantile",
    interval = "credible", showProgress = FALSE
  )
  expect_true(is.matrix(preds_credible))

  # Test prediction intervals
  preds_prediction <- predict(obj, X_new,
    type = "quantile",
    interval = "prediction", showProgress = FALSE
  )
  expect_true(is.matrix(preds_prediction))
})

test_that("predict.AddiVortes handles empty posterior samples", {
  # Test predict with object containing no posterior samples
  obj <- new_AddiVortes(
    posteriorTess = list(),
    posteriorDim = list(),
    posteriorSigma = numeric(),
    posteriorPred = list(),
    xCentres = c(0, 0, 0, 0, 0),
    xRanges = c(1, 1, 1, 1, 1),
    yCentre = 0,
    yRange = 1,
    inSampleRmse = 0.5
  )
  X_new <- matrix(rnorm(25), 5, 5)

  expect_warning(
    predict(obj, X_new, showProgress = FALSE),
    "contains no posterior samples"
  )
})

test_that("In- and out-of-sample predict methods match", {
  # Test that in-sample and out-of-sample predictions match for training data
  obj <- create_test_object()
  withr::local_seed(42)
  X_train <- matrix(rnorm(50), 10, 5)
  Y_train <- rnorm(10)

  preds_in_sample <- obj$inSampleRmse
  preds_out_of_sample <- predict(obj, X_train, showProgress = FALSE)
  preds_out_of_sample <- sqrt(mean((Y_train - preds_out_of_sample)^2))

  expect_equal(preds_in_sample, preds_out_of_sample)
})

# --- Tests for plot.AddiVortes() ---

test_that("plot.AddiVortes requires AddiVortes object", {
  # Test that plot fails with non-AddiVortes object
  not_addivortes <- list(a = 1, b = 2)
  X <- matrix(rnorm(50), 10, 5)
  Y <- rnorm(10)

  expect_error(
    plot.AddiVortes(not_addivortes, X, Y),
    "must be an object of class 'AddiVortes'"
  )
})

test_that("plot.AddiVortes requires x_train and y_train", {
  skip_on_cran()
  # Test that plot fails without required training data
  obj <- create_test_object()

  expect_error(
    plot(obj),
    "x_train.*and.*y_train.*must be provided"
  )
})

test_that("plot.AddiVortes checks x_train is a matrix", {
  skip_on_cran()
  # Test that plot fails if x_train is not a matrix
  obj <- create_test_object()
  Y <- rnorm(10)

  expect_error(
    plot(obj, x_train = c(1, 2, 3), y_train = Y),
    "must be a matrix"
  )
})

test_that("plot.AddiVortes checks y_train is numeric", {
  skip_on_cran()
  # Test that plot fails if y_train is not numeric
  obj <- create_test_object()
  X <- matrix(rnorm(50), 10, 5)

  expect_error(
    plot(obj, x_train = X, y_train = c("a", "b", "c")),
    "must be a numeric vector"
  )
})

test_that("plot.AddiVortes checks dimensions match", {
  skip_on_cran()
  # Test that plot fails if dimensions don't match
  obj <- create_test_object()
  X <- matrix(rnorm(50), 10, 5)
  Y <- rnorm(5) # Wrong length

  expect_error(
    plot(obj, x_train = X, y_train = Y),
    "number of rows.*must match"
  )
})

test_that("plot.AddiVortes handles empty posterior samples", {
  # Test plot with object containing no posterior samples
  obj <- new_AddiVortes(
    posteriorTess = list(),
    posteriorDim = list(),
    posteriorSigma = numeric(),
    posteriorPred = list(),
    xCentres = c(0, 0, 0, 0, 0),
    xRanges = c(1, 1, 1, 1, 1),
    yCentre = 0,
    yRange = 1,
    inSampleRmse = 0.5
  )
  X <- matrix(rnorm(50), 10, 5)
  Y <- rnorm(10)

  expect_error(
    plot(obj, x_train = X, y_train = Y),
    "No posterior samples available"
  )
})

test_that("plot.AddiVortes validates which parameter", {
  skip_on_cran()
  # Test that plot validates the which parameter
  obj <- create_test_object()
  X <- matrix(rnorm(50), 10, 5)
  Y <- rnorm(10)

  # Invalid which values should be filtered out
  expect_error(
    plot(obj, x_train = X, y_train = Y, which = c(5, 6, 7)),
    "which.*must contain values between 1 and 4"
  )
})

# --- Tests for traceplots.AddiVortes() ---

create_traceplot_test_object <- function() {
  new_AddiVortes(
    posteriorTess = list(
      list(
        matrix(0, nrow = 2, ncol = 1),
        matrix(0, nrow = 4, ncol = 2)
      ),
      list(
        matrix(0, nrow = 3, ncol = 1),
        matrix(0, nrow = 7, ncol = 2)
      )
    ),
    posteriorDim = list(
      list(1L, c(1L, 2L, 3L)),
      list(c(1L, 2L), c(2L, 3L))
    ),
    posteriorSigma = c(4, 9),
    posteriorPred = list(),
    xCentres = c(0, 0, 0),
    xRanges = c(1, 1, 1),
    yCentre = 0,
    yRange = 1,
    inSampleRmse = 0.5,
    traceStats = data.frame(
      iteration = 1:4,
      isBurnIn = c(TRUE, TRUE, FALSE, FALSE),
      averageCentresPerTessellation = c(2, 2.5, 3, 5),
      sdCentresPerTessellation = c(
        0,
        0.5,
        stats::sd(c(2, 4)),
        stats::sd(c(3, 7))
      ),
      averageDimensionsPerTessellation = c(1, 1.5, 2, 2),
      logLikelihood = c(-0.25, -0.1, 0.05, 0.2)
    )
  )
}

test_that("traceplotData_internal computes requested traces", {
  obj <- create_traceplot_test_object()

  trace_data <- AddiVortes:::traceplotData_internal(obj)

  expect_equal(trace_data$iteration, 1:4)
  expect_equal(trace_data$isBurnIn, c(TRUE, TRUE, FALSE, FALSE))
  expect_equal(trace_data$averageCentresPerTessellation, c(2, 2.5, 3, 5))
  expect_equal(
    trace_data$sdCentresPerTessellation,
    c(0, 0.5, stats::sd(c(2, 4)), stats::sd(c(3, 7)))
  )
  expect_equal(trace_data$averageDimensionsPerTessellation, c(1, 1.5, 2, 2))
  expect_equal(trace_data$logLikelihood, c(-0.25, -0.1, 0.05, 0.2))
})

test_that("traceplotData_internal falls back for objects without traceStats", {
  obj <- create_traceplot_test_object()
  obj$traceStats <- NULL

  trace_data <- AddiVortes:::traceplotData_internal(obj)

  expect_equal(trace_data$iteration, 1:2)
  expect_equal(trace_data$isBurnIn, c(FALSE, FALSE))
  expect_equal(trace_data$averageCentresPerTessellation, c(3, 5))
  expect_equal(
    trace_data$sdCentresPerTessellation,
    c(stats::sd(c(2, 4)), stats::sd(c(3, 7)))
  )
  expect_equal(trace_data$averageDimensionsPerTessellation, c(2, 2))
  expect_true(all(is.na(trace_data$logLikelihood)))
})

test_that("traceplots.AddiVortes requires AddiVortes object", {
  expect_error(
    traceplots.AddiVortes(list()),
    "must be an object of class 'AddiVortes'"
  )
})

test_that("traceplots.AddiVortes handles empty posterior samples", {
  obj <- new_AddiVortes(
    posteriorTess = list(),
    posteriorDim = list(),
    posteriorSigma = numeric(),
    posteriorPred = list(),
    xCentres = c(0),
    xRanges = c(1),
    yCentre = 0,
    yRange = 1,
    inSampleRmse = 0.5
  )

  expect_error(
    traceplots(obj),
    "No posterior samples available"
  )
})

test_that("traceplots.AddiVortes creates trace plots", {
  obj <- create_traceplot_test_object()
  trace_file <- tempfile(fileext = ".pdf")
  grDevices::pdf(trace_file)
  on.exit({
    grDevices::dev.off()
    unlink(trace_file)
  }, add = TRUE)

  expect_silent(result <- traceplots(obj))
  expect_null(result)
})
