skip_if_no_torch <- function() {
  if (!requireNamespace("torch", quietly = TRUE)) {
    skip("torch not available for training tests")
  }
  runtime_ok <- tryCatch({
    torch::torch_manual_seed(1)
    invisible(torch::torch_tensor(1))
    TRUE
  }, error = function(e) FALSE)
  if (!runtime_ok) {
    skip("torch runtime/Lantern not available for training tests")
  }
}

fake_codez_result <- function() {
  pred <- data.frame(min = 1:3,
                     `10%` = 1:3,
                     `25%` = 1:3,
                     `50%` = 2:4,
                     `75%` = 3:5,
                     `90%` = 3:5,
                     max = 3:5,
                     mean = 2:4,
                     check.names = FALSE)
  rownames(pred) <- as.character(as.Date("2026-01-01") + 0:2)
  structure(list(history = data.frame(seq_len = 3, mae = 1),
                 best_model = list(predictions = list(value = pred),
                                   errors = matrix(1, nrow = 1, ncol = 1),
                                   plot = list(value = codez:::codez_plot(data.frame(epoch = 1, loss = 1), type = "training"))),
                 time_log = "0S"),
            backend = "torch",
            class = "list")
}

test_that("codez 2.0 controls are compact and typed", {
  control <- codez_control(batch_size = 8, validation_split = 0.2, n_sim = 50)
  expect_s3_class(control, "codez_control")
  expect_equal(control$backend, "torch")
  expect_false("epochs" %in% names(control))
})

test_that("tidy forecast extraction creates one row per feature and horizon", {
  tidy <- codez_forecast_tidy(fake_codez_result())
  expect_s3_class(tidy, "data.frame")
  expect_equal(nrow(tidy), 3)
  expect_equal(tidy$feature, rep("value", 3))
  expect_true(all(!is.na(tidy$date)))
  expect_true("50%" %in% names(tidy))
})

test_that("baseline backtests return tidy metric rows for numeric data", {
  df <- data.frame(a = 1:40, b = 41:80)
  baselines <- codez:::codez_backtest_baselines(df, seq_len = 3, n_windows = 2)
  expect_s3_class(baselines, "data.frame")
  expect_true(all(c("window", "model", "feature", "metric", "value") %in% names(baselines)))
  expect_true(all(c("naive", "average") %in% baselines$model))
})

test_that("mixed feature inputs are rejected before training", {
  skip_if_no_torch()
  expect_error(codez(data.frame(a = 1:30, b = letters[1:30]), seq_len = 3, n_samp = 1, n_windows = 2),
               "mixed numeric and categorical")
})

test_that("codez torch backend returns the legacy result shape", {
  skip_if_no_torch()
  outcome <- codez(amzn_aapl_fb[, 2, drop = FALSE],
                   seq_len = 5,
                   latent = 2,
                   n_samp = 1,
                   n_windows = 2,
                   autoencoder_layers_n = 1,
                   autoencoder_layers_size = 8,
                   autoencoder_activ = "relu",
                   forward_net_layers_n = 1,
                   forward_net_layers_size = 8,
                   forward_net_activ = "relu",
                   forward_net_reg_L1 = 0,
                   forward_net_reg_L2 = 0,
                   forward_net_drop = 0,
                   epochs = 2,
                   patience = 1,
                   batch_size = 8,
                   n_sim = 20)
  expect_equal(names(outcome), c("history", "best_model", "time_log"))
  expect_equal(attr(outcome, "backend"), "torch")
  expect_equal(names(outcome$best_model), c("predictions", "errors", "plot"))
  expect_equal(length(outcome$best_model$predictions), 1)
  expect_s3_class(outcome$best_model$predictions$AMZN, "data.frame")
})

test_that("fit_codez adds v2 model conveniences", {
  skip_if_no_torch()
  model <- fit_codez(amzn_aapl_fb[, 2, drop = FALSE],
                     seq_len = 5,
                     latent = 2,
                     n_samp = 1,
                     n_windows = 2,
                     autoencoder_layers_n = 1,
                     autoencoder_layers_size = 8,
                     autoencoder_activ = "relu",
                     forward_net_layers_n = 1,
                     forward_net_layers_size = 8,
                     forward_net_activ = "relu",
                     forward_net_reg_L1 = 0,
                     forward_net_reg_L2 = 0,
                     forward_net_drop = 0,
                     control = codez_control(epochs = 2, patience = 1, batch_size = 8, n_sim = 20))
  expect_s3_class(model, "codez_model")
  expect_s3_class(predict(model), "data.frame")
  expect_true(nrow(model$benchmarks) > 0)
})
