make_small_pilot <- function(n = 40, stratified = FALSE) {
  set.seed(777)
  dat <- data.frame(
    time = stats::rexp(n, 0.2) + 0.1,
    status = stats::rbinom(n, 1, 0.7),
    arm = rep(0:1, length.out = n),
    x1 = stats::rnorm(n)
  )
  if (stratified) {
    dat$region <- factor(rep(c("A", "A", "B", "B"), length.out = n))
  }
  dat
}

test_that("run_app covers env override and missing app path branches", {
  testthat::skip_if_not_installed("shiny")

  shiny_ns <- asNamespace("shiny")
  orig_run_app <- get("runApp", envir = shiny_ns)
  called <- NULL
  assignInNamespace("runApp", function(appDir, ...) {
    called <<- appDir
    appDir
  }, ns = "shiny")
  on.exit(assignInNamespace("runApp", orig_run_app, ns = "shiny"), add = TRUE)

  td <- tempfile("appdir_")
  dir.create(td, recursive = TRUE, showWarnings = FALSE)
  old <- Sys.getenv("RMSTPOWERBOOST_APP_DIR", unset = NA_character_)
  on.exit({
    if (is.na(old)) Sys.unsetenv("RMSTPOWERBOOST_APP_DIR") else Sys.setenv(RMSTPOWERBOOST_APP_DIR = old)
  }, add = TRUE)

  Sys.setenv(RMSTPOWERBOOST_APP_DIR = td)
  expect_identical(
    testthat::with_mocked_bindings(
      RMSTpowerBoost::run_app(),
      .ensure_app_dependencies = function(...) invisible(TRUE),
      .package = "RMSTpowerBoost"
    ),
    td
  )
  expect_identical(called, td)

  Sys.setenv(RMSTPOWERBOOST_APP_DIR = file.path(tempdir(), "does-not-exist-rmst-app"))
  expect_error(
    testthat::with_mocked_bindings(
      RMSTpowerBoost::run_app(),
      .ensure_app_dependencies = function(...) invisible(TRUE),
      .package = "RMSTpowerBoost"
    ),
    "Could not find the app directory"
  )
})

test_that("describe_generation covers model labels, treatment modes, and effects branches", {
  dat <- make_small_pilot(20, stratified = TRUE)
  attr(dat, "tau") <- 2.5

  model_keys <- c("cox_pwexp", "ph_exponential", "ph_weibull", "ph_gompertz", "aft_lognormal", "aft_weibull", "aft_loglogistic")
  for (mk in model_keys) {
    set <- list(
      data = dat,
      meta = list(
        model = mk,
        baseline = list(rate = c(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7), nested = list(a = 1)),
        effects = list(intercept = 0.1, treatment = -0.2, covariates = list(x1 = 0.5)),
        treatment = list(assignment = "stratified", allocation = "1:1", stratify_by = c("region")),
        censoring = list(mode = "target_overall", target = 0.2, admin_time = 5),
        covariates = list(list(name = "x1", type = "continuous", dist = "normal", params = list(mean = 0, sd = 1), transform = c("center(0)"))
        )
      )
    )
    out <- RMSTpowerBoost::describe_generation(set)
    expect_true(is.data.frame(out$header))
    expect_true(is.data.frame(out$treatment))
    expect_true(any(grepl("stratified", out$treatment$assignment)))
    expect_true(any(grepl("\\[7 vals\\]|\\[list\\]", out$baseline$baseline)))
  }

  set_logit <- list(
    data = dat,
    meta = list(
      model = "aft_lognormal",
      baseline = list(mu = 2, sigma = 0.5),
      effects = list(intercept = 0, treatment = 0),
      treatment = list(assignment = "logistic_ps", allocation = "1:1", ps_model = list(formula = ~x1, beta = c(0.1, 0.2))),
      censoring = list(mode = "explicit")
    )
  )
  out_logit <- RMSTpowerBoost::describe_generation(set_logit)
  expect_true("ps_formula" %in% names(out_logit$treatment))
  expect_true("ps_beta" %in% names(out_logit$treatment))

  # effects$formula + beta mismatch branch
  set_bad <- set_logit
  set_bad$meta$effects <- list(intercept = 0, treatment = 0, formula = "~ x1", beta = c(0.1, 0.2, 0.3))
  expect_error(RMSTpowerBoost::describe_generation(set_bad), "effects\\$beta length mismatch")
})

test_that("load/rebuild manifest covers csv branch, no-path branch, and vary token branch", {
  td <- tempfile("manifest_cov_")
  dir.create(td, recursive = TRUE, showWarnings = FALSE)

  dat <- make_small_pilot(20, stratified = FALSE)
  csv_path <- file.path(td, "d.csv")
  utils::write.csv(dat, csv_path, row.names = FALSE)

  man <- data.frame(
    scenario_id = 1, rep = 1, seed = 1L,
    file_rds = NA_character_, file_rdata = NA_character_, file_csv = csv_path, file_txt = NA_character_,
    stringsAsFactors = FALSE
  )
  saveRDS(man, file.path(td, "manifest.rds"))
  sets <- RMSTpowerBoost::load_recipe_sets(file.path(td, "manifest.rds"))
  expect_equal(length(sets), 1)
  expect_true(is.data.frame(sets[[1]]$data))

  bad <- man
  bad$file_csv <- NA_character_
  saveRDS(bad, file.path(td, "manifest.rds"))
  expect_error(RMSTpowerBoost::load_recipe_sets(file.path(td, "manifest.rds")), "No data file path")

  empty <- man[0, ]
  saveRDS(empty, file.path(td, "manifest.rds"))
  expect_error(RMSTpowerBoost::load_recipe_sets(file.path(td, "manifest.rds")), "manifest has 0 rows")

  rec <- RMSTpowerBoost::recipe_quick_aft(
    n = 10,
    model = "aft_lognormal",
    baseline = list(mu = 2, sigma = 0.4),
    treat_effect = -0.1,
    covariates = list(list(name = "x1", type = "continuous", dist = "normal", params = list(mean = 0, sd = 1))),
    target_censoring = 0.1,
    allocation = "1:1"
  )
  set.seed(1)
  d1 <- RMSTpowerBoost::simulate_from_recipe(rec)
  saveRDS(d1, file.path(td, "sc1_r1_n10.rds"))
  rb <- RMSTpowerBoost::rebuild_manifest(
    rec,
    vary = list(n = c(10, 20)),
    out_dir = td,
    filename_template = "sc{scenario_id}_r{rep}_n{n}"
  )
  expect_true(nrow(rb) >= 1)
  expect_true(any(grepl("^p__", names(rb))))
})

test_that("rebuild_manifest covers missing-dir, no-match, and file-format branches", {
  rec <- RMSTpowerBoost::recipe_quick_aft(
    n = 12,
    model = "aft_lognormal",
    baseline = list(mu = 2, sigma = 0.4),
    treat_effect = -0.1,
    covariates = list(list(name = "x1", type = "continuous", dist = "normal", params = list(mean = 0, sd = 1))),
    target_censoring = 0.1,
    allocation = "1:1"
  )

  missing_dir <- file.path(tempdir(), "rmst_missing_dir_cov")
  expect_error(
    RMSTpowerBoost::rebuild_manifest(rec, vary = list(), out_dir = missing_dir),
    "out_dir does not exist"
  )

  empty_dir <- tempfile("rmst_empty_rebuild_")
  dir.create(empty_dir, recursive = TRUE, showWarnings = FALSE)
  expect_error(
    RMSTpowerBoost::rebuild_manifest(rec, vary = list(), out_dir = empty_dir),
    "no files matched"
  )

  set.seed(5)
  dat <- RMSTpowerBoost::simulate_from_recipe(rec)
  fmt_dir <- tempfile("rmst_fmt_rebuild_")
  dir.create(fmt_dir, recursive = TRUE, showWarnings = FALSE)

  utils::write.csv(dat, file.path(fmt_dir, "sc1_r1.csv"), row.names = FALSE)
  rb_csv <- RMSTpowerBoost::rebuild_manifest(rec, vary = list(), out_dir = fmt_dir)
  expect_true(nrow(rb_csv) >= 1)

  unlink(file.path(fmt_dir, "manifest.rds"), force = TRUE)
  dat_obj <- dat
  save(dat_obj, file = file.path(fmt_dir, "sc1_r1.RData"))
  rb_rdata <- RMSTpowerBoost::rebuild_manifest(rec, vary = list(), out_dir = fmt_dir)
  expect_true(nrow(rb_rdata) >= 1)

  unlink(file.path(fmt_dir, "manifest.rds"), force = TRUE)
  utils::write.table(dat, file.path(fmt_dir, "sc1_r1.txt"), sep = "\t", row.names = FALSE, quote = FALSE)
  rb_txt <- RMSTpowerBoost::rebuild_manifest(rec, vary = list(), out_dir = fmt_dir)
  expect_true(nrow(rb_txt) >= 1)

  # Vary with zero-length value can produce empty grid and triggers fallback to base recipe.
  rb_empty_grid <- RMSTpowerBoost::rebuild_manifest(
    rec,
    vary = list("event_time.effects.treatment" = numeric(0)),
    out_dir = fmt_dir
  )
  expect_true(nrow(rb_empty_grid) >= 1)
})

test_that("linear boot functions cover validation and no-estimate summary branches", {
  pilot <- make_small_pilot(20, stratified = FALSE)
  expect_error(RMSTpowerBoost::linear.power.boot(pilot, "time", "status", "arm", sample_sizes = NULL, L = 2), "sample_sizes")
  expect_error(RMSTpowerBoost::linear.ss.boot(pilot, "time", "status", "arm", target_power = c(0.8, 0.9), L = 2), "single numeric value")

  # Force null-estimate branches by using nonnumeric event times that make survfit fail.
  bad <- pilot
  bad$time <- as.character(round(bad$time, 3))
  p <- RMSTpowerBoost::linear.power.boot(bad, "time", "status", "arm", sample_sizes = c(6), L = 2, n_sim = 2, alpha = 0.1)
  expect_true(is.null(p$results_summary))

  s <- suppressWarnings(RMSTpowerBoost::linear.ss.boot(
    bad, "time", "status", "arm",
    target_power = 0.95, L = 2, n_sim = 2, alpha = 0.1,
    patience = 1, n_start = 4, n_step = 2, max_n_per_arm = 6
  ))
  expect_true(is.null(s$results_summary))
})

test_that("multiplicative analytical and bootstrap cover additional warning/error branches", {
  dat <- make_small_pilot(60, stratified = TRUE)
  expect_warning(
    RMSTpowerBoost::MS.ss.analytical(
      dat, "time", "status", "arm", "region",
      target_power = 0.999, L = 2, n_start = 4, n_step = 2, max_n_per_arm = 6
    ),
    "not achieved by max N"
  )

  # low-data guard in analytical power
  tiny <- dat[1:6, ]
  tiny$status <- c(1, 0, 0, 0, 0, 0)
  expect_error(
    RMSTpowerBoost::MS.power.analytical(tiny, "time", "status", "arm", "region", sample_sizes = c(4), L = 2),
    "Not enough data points"
  )

  expect_error(RMSTpowerBoost::MS.power.boot(dat, "time", "status", "arm", "region", sample_sizes = NULL, L = 2), "sample_sizes")
  expect_error(RMSTpowerBoost::MS.ss.boot(dat, "time", "status", "arm", "region", target_power = c(0.8, 0.9), L = 2), "single numeric value")

  # Trigger max_n warning branch rather than stagnation.
  expect_warning(
    RMSTpowerBoost::MS.ss.boot(
      dat, "time", "status", "arm", "region",
      target_power = 0.999, L = 2, n_sim = 2, alpha = 0.1,
      parallel.cores = 1, patience = 99, n_start = 4, n_step = 2, max_n_per_arm = 6
    ),
    "not achieved by max N"
  )
})

test_that("GAM bootstrap functions cover validation, parallel, and search terminal branches", {
  dat <- make_small_pilot(30, stratified = TRUE)

  expect_error(
    RMSTpowerBoost::GAM.power.boot(dat, "time", "status", "arm", sample_sizes = NULL, L = 2),
    "sample_sizes"
  )
  expect_error(
    RMSTpowerBoost::GAM.ss.boot(dat, "time", "status", "arm", target_power = NULL, L = 2),
    "target_power"
  )

  # Parallel dependency guard branch.
  testthat::with_mocked_bindings(
    expect_error(
      RMSTpowerBoost::GAM.power.boot(
        dat, "time", "status", "arm",
        sample_sizes = c(4), L = 2, parallel.cores = 2
      ),
      "required for parallel processing"
    ),
    requireNamespace = function(...) FALSE,
    .package = "base"
  )
  testthat::with_mocked_bindings(
    expect_error(
      RMSTpowerBoost::GAM.ss.boot(
        dat, "time", "status", "arm",
        target_power = 0.8, L = 2, parallel.cores = 2
      ),
      "required for parallel processing"
    ),
    requireNamespace = function(...) FALSE,
    .package = "base"
  )

  # Exercise small-sample validation branches.
  expect_error(
    RMSTpowerBoost::GAM.power.boot(
      dat, "time", "status", "arm", strata_var = "region",
      sample_sizes = c(1), linear_terms = "x1",
      L = 2, n_sim = 1, alpha = 0.1, parallel.cores = 1
    ),
    "greater than or equal to 2"
  )
  expect_error(
    RMSTpowerBoost::GAM.power.boot(
      dat, "time", "status", "arm", strata_var = "region",
      sample_sizes = c(0), linear_terms = "x1",
      L = 2, n_sim = 1, alpha = 0.1, parallel.cores = 1
    ),
    "greater than or equal to 2"
  )

  # Exercise successful execution path with a stable small sample size.
  gp <- RMSTpowerBoost::GAM.power.boot(
    dat, "time", "status", "arm", strata_var = "region",
    sample_sizes = c(4), linear_terms = "x1",
    L = 2, n_sim = 1, alpha = 0.1, parallel.cores = 1
  )
  expect_true(is.list(gp))
  expect_named(gp, c("results_data", "results_plot", "results_summary", "model_output"))

  # Stagnation branch.
  expect_warning(
    RMSTpowerBoost::GAM.ss.boot(
      dat, "time", "status", "arm", strata_var = "region",
      target_power = 0.99, linear_terms = "x1",
      L = 2, n_sim = 1, alpha = 0.1, parallel.cores = 1,
      patience = 1, n_start = 4, n_step = 1, max_n_per_arm = 8
    ),
    "stagnation"
  )

  # max_n_per_arm terminal warning branch.
  expect_warning(
    RMSTpowerBoost::GAM.ss.boot(
      dat, "time", "status", "arm", strata_var = "region",
      target_power = 0.999, linear_terms = "x1",
      L = 2, n_sim = 1, alpha = 0.1, parallel.cores = 1,
      patience = 99, n_start = 4, n_step = 1, max_n_per_arm = 5
    ),
    "not achieved by max N"
  )

  expect_error(
    RMSTpowerBoost::GAM.ss.boot(
      dat, "time", "status", "arm", strata_var = "region",
      target_power = 0.9, linear_terms = "x1",
      L = 2, n_sim = 1, alpha = 0.1, parallel.cores = 1,
      patience = 1, n_start = 1, n_step = 1, max_n_per_arm = 2
    ),
    "greater than or equal to 2"
  )
  expect_error(
    RMSTpowerBoost::GAM.ss.boot(
      dat, "time", "status", "arm", strata_var = "region",
      target_power = 0.9, linear_terms = "x1",
      L = 2, n_sim = 1, alpha = 0.1, parallel.cores = 1,
      patience = 1, n_start = 0, n_step = 1, max_n_per_arm = 1
    ),
    "greater than or equal to 2"
  )
})
