# =============================================================================
# test-utils.R — Unit tests for package-wide internal helpers (R/utils.R)
# =============================================================================


# ===========================================================================
# .assert_scalar_string()
# ===========================================================================

test_that(".assert_scalar_string() accepts a valid scalar string", {
  expect_invisible(ukbflow:::.assert_scalar_string("abc"))
})

test_that(".assert_scalar_string() rejects an empty string", {
  expect_error(ukbflow:::.assert_scalar_string(""), "single non-empty string")
})

test_that(".assert_scalar_string() rejects NA_character_", {
  expect_error(ukbflow:::.assert_scalar_string(NA_character_), "single non-empty string")
})

test_that(".assert_scalar_string() rejects non-character input", {
  expect_error(ukbflow:::.assert_scalar_string(1), "single non-empty string")
})

test_that(".assert_scalar_string() rejects length > 1 vector", {
  expect_error(ukbflow:::.assert_scalar_string(c("a", "b")), "single non-empty string")
})

test_that(".assert_scalar_string() error message includes the argument name", {
  my_token <- NA_character_
  expect_error(ukbflow:::.assert_scalar_string(my_token), "`my_token`")
})


# ===========================================================================
# .assert_integer_ids()
# ===========================================================================

test_that(".assert_integer_ids() accepts a valid integer vector and returns integers", {
  result <- ukbflow:::.assert_integer_ids(c(31, 53, 22189))
  expect_equal(result, c(31L, 53L, 22189L))
})

test_that(".assert_integer_ids() deduplicates values", {
  result <- ukbflow:::.assert_integer_ids(c(31, 31, 53))
  expect_equal(result, c(31L, 53L))
})

test_that(".assert_integer_ids() rejects empty vector", {
  expect_error(ukbflow:::.assert_integer_ids(integer(0)), "non-empty numeric")
})

test_that(".assert_integer_ids() rejects non-numeric input", {
  expect_error(ukbflow:::.assert_integer_ids("p31"), "non-empty numeric")
})

test_that(".assert_integer_ids() rejects NA values", {
  expect_error(ukbflow:::.assert_integer_ids(c(31, NA)), "NA")
})

test_that(".assert_integer_ids() rejects Inf values", {
  expect_error(ukbflow:::.assert_integer_ids(c(31, Inf)), "Inf")
})

test_that(".assert_integer_ids() rejects NaN values", {
  expect_error(ukbflow:::.assert_integer_ids(c(31, NaN)), "NaN")
})

test_that(".assert_integer_ids() rejects decimal values", {
  expect_error(ukbflow:::.assert_integer_ids(31.7), "whole numbers")
})

test_that(".assert_integer_ids() error message includes the argument name", {
  bad_ids <- c(31, NA)
  expect_error(ukbflow:::.assert_integer_ids(bad_ids), "bad_ids")
})


# ===========================================================================
# .assert_job_id()
# ===========================================================================

test_that(".assert_job_id() accepts a valid job-XXXX string", {
  expect_invisible(ukbflow:::.assert_job_id("job-XXXX"))
})

test_that(".assert_job_id() returns the job_id invisibly", {
  result <- ukbflow:::.assert_job_id("job-ABC123")
  expect_equal(result, "job-ABC123")
})

test_that(".assert_job_id() rejects wrong prefix", {
  expect_error(ukbflow:::.assert_job_id("notajob"), "job-XXXX")
})

test_that(".assert_job_id() rejects NA_character_", {
  expect_error(ukbflow:::.assert_job_id(NA_character_))
})

test_that(".assert_job_id() rejects length > 1 vector", {
  expect_error(ukbflow:::.assert_job_id(c("job-AAA", "job-BBB")))
})

test_that(".assert_job_id() rejects non-character input", {
  expect_error(ukbflow:::.assert_job_id(123))
})

test_that(".assert_job_id() rejects empty string", {
  expect_error(ukbflow:::.assert_job_id(""))
})

test_that(".assert_job_id() error message includes the argument name", {
  bad_id <- "notajob"
  expect_error(ukbflow:::.assert_job_id(bad_id), "bad_id")
})


# ===========================================================================
# .assert_count()
# ===========================================================================

test_that(".assert_count() accepts a valid positive integer", {
  expect_invisible(ukbflow:::.assert_count(20))
})

test_that(".assert_count() returns as.integer invisibly", {
  result <- ukbflow:::.assert_count(5)
  expect_equal(result, 5L)
  expect_type(result, "integer")
})

test_that(".assert_count() accepts 1", {
  expect_invisible(ukbflow:::.assert_count(1))
})

test_that(".assert_count() rejects 0", {
  expect_error(ukbflow:::.assert_count(0), "positive integer")
})

test_that(".assert_count() rejects negative value", {
  expect_error(ukbflow:::.assert_count(-5), "positive integer")
})

test_that(".assert_count() rejects Inf", {
  expect_error(ukbflow:::.assert_count(Inf), "positive integer")
})

test_that(".assert_count() rejects NA", {
  expect_error(ukbflow:::.assert_count(NA_real_), "positive integer")
})

test_that(".assert_count() rejects decimal value", {
  expect_error(ukbflow:::.assert_count(1.5), "positive integer")
})

test_that(".assert_count() rejects character input", {
  expect_error(ukbflow:::.assert_count("five"), "positive integer")
})

test_that(".assert_count() rejects length > 1 vector", {
  expect_error(ukbflow:::.assert_count(c(5, 10)), "positive integer")
})

test_that(".assert_count() error message includes the argument name", {
  bad_n <- -1
  expect_error(ukbflow:::.assert_count(bad_n), "bad_n")
})


# ===========================================================================
# .assert_choices()
# ===========================================================================

test_that(".assert_choices() accepts NULL and returns NULL invisibly", {
  result <- ukbflow:::.assert_choices(NULL, c("a", "b"))
  expect_null(result)
})

test_that(".assert_choices() accepts a valid single choice", {
  expect_invisible(ukbflow:::.assert_choices("a", c("a", "b", "c")))
})

test_that(".assert_choices() accepts valid multiple choices", {
  expect_invisible(ukbflow:::.assert_choices(c("a", "b"), c("a", "b", "c")))
})

test_that(".assert_choices() accepts character(0)", {
  expect_invisible(ukbflow:::.assert_choices(character(0), c("a", "b")))
})

test_that(".assert_choices() rejects non-character input", {
  expect_error(ukbflow:::.assert_choices(TRUE, c("a", "b")), "character vector")
})

test_that(".assert_choices() rejects invalid value", {
  expect_error(ukbflow:::.assert_choices("oops", c("a", "b")), "oops")
})

test_that(".assert_choices() error message lists valid values", {
  expect_error(ukbflow:::.assert_choices("oops", c("a", "b")), "a")
})

test_that(".assert_choices() rejects mix of valid and invalid values", {
  expect_error(ukbflow:::.assert_choices(c("a", "oops"), c("a", "b")), "oops")
})

test_that(".assert_choices() error message includes the argument name", {
  bad_state <- "oops"
  expect_error(ukbflow:::.assert_choices(bad_state, c("a", "b")), "bad_state")
})


# ===========================================================================
# %||%  (null-coalescing operator)
# ===========================================================================

test_that("%||% returns lhs when lhs is not NULL", {
  expect_equal("hello" %||% "fallback", "hello")
})

test_that("%||% returns rhs when lhs is NULL", {
  expect_equal(NULL %||% "fallback", "fallback")
})

test_that("%||% returns rhs when lhs is NULL, rhs is numeric", {
  expect_equal(NULL %||% 42L, 42L)
})

test_that("%||% returns lhs FALSE (not NULL) unchanged", {
  expect_equal(FALSE %||% TRUE, FALSE)
})

test_that("%||% returns lhs NA (not NULL) unchanged", {
  expect_equal(NA %||% "fallback", NA)
})


# ===========================================================================
# .assert_count_min()
# ===========================================================================

test_that(".assert_count_min() accepts integer equal to min", {
  expect_invisible(ukbflow:::.assert_count_min(2L, min = 2L))
})

test_that(".assert_count_min() returns as.integer invisibly", {
  result <- ukbflow:::.assert_count_min(3, min = 2L)
  expect_equal(result, 3L)
  expect_type(result, "integer")
})

test_that(".assert_count_min() accepts integer greater than min", {
  expect_invisible(ukbflow:::.assert_count_min(5L, min = 2L))
})

test_that(".assert_count_min() rejects value below min", {
  expect_error(ukbflow:::.assert_count_min(1L, min = 2L), ">= 2")
})

test_that(".assert_count_min() rejects decimal value", {
  expect_error(ukbflow:::.assert_count_min(2.5, min = 2L), ">= 2")
})

test_that(".assert_count_min() rejects NA", {
  expect_error(ukbflow:::.assert_count_min(NA_real_, min = 1L), ">= 1")
})

test_that(".assert_count_min() rejects Inf", {
  expect_error(ukbflow:::.assert_count_min(Inf, min = 1L), ">= 1")
})

test_that(".assert_count_min() rejects length > 1 vector", {
  expect_error(ukbflow:::.assert_count_min(c(2L, 3L), min = 2L), ">= 2")
})

test_that(".assert_count_min() error message includes the argument name", {
  bad_n <- 1L
  expect_error(ukbflow:::.assert_count_min(bad_n, min = 2L), "bad_n")
})


# ===========================================================================
# .assert_character()
# ===========================================================================

test_that(".assert_character() accepts a character scalar", {
  expect_invisible(ukbflow:::.assert_character("hello"))
})

test_that(".assert_character() accepts a character vector", {
  expect_invisible(ukbflow:::.assert_character(c("a", "b", "c")))
})

test_that(".assert_character() accepts character(0)", {
  expect_invisible(ukbflow:::.assert_character(character(0)))
})

test_that(".assert_character() rejects numeric input", {
  expect_error(ukbflow:::.assert_character(1L), "character vector")
})

test_that(".assert_character() rejects logical input", {
  expect_error(ukbflow:::.assert_character(TRUE), "character vector")
})

test_that(".assert_character() rejects NULL", {
  expect_error(ukbflow:::.assert_character(NULL), "character vector")
})

test_that(".assert_character() error message includes the argument name", {
  bad_labels <- 123L
  expect_error(ukbflow:::.assert_character(bad_labels), "bad_labels")
})


# ===========================================================================
# .assert_data_frame()
# ===========================================================================

test_that(".assert_data_frame() accepts a data.frame", {
  expect_invisible(ukbflow:::.assert_data_frame(data.frame(x = 1)))
})

test_that(".assert_data_frame() accepts a data.table (is also a data.frame)", {
  expect_invisible(
    ukbflow:::.assert_data_frame(data.table::data.table(x = 1))
  )
})

test_that(".assert_data_frame() rejects a plain list", {
  expect_error(ukbflow:::.assert_data_frame(list(x = 1)), "data.frame")
})

test_that(".assert_data_frame() rejects a character string", {
  expect_error(ukbflow:::.assert_data_frame("not a df"), "data.frame")
})

test_that(".assert_data_frame() rejects NULL", {
  expect_error(ukbflow:::.assert_data_frame(NULL), "data.frame")
})

test_that(".assert_data_frame() error message includes the argument name", {
  bad_data <- "oops"
  expect_error(ukbflow:::.assert_data_frame(bad_data), "bad_data")
})


# ===========================================================================
# .assert_data_table()
# ===========================================================================

test_that(".assert_data_table() accepts a data.table", {
  expect_invisible(
    ukbflow:::.assert_data_table(data.table::data.table(x = 1))
  )
})

test_that(".assert_data_table() rejects a plain data.frame", {
  expect_error(
    ukbflow:::.assert_data_table(data.frame(x = 1)), "data.table"
  )
})

test_that(".assert_data_table() rejects a list", {
  expect_error(ukbflow:::.assert_data_table(list(x = 1)), "data.table")
})

test_that(".assert_data_table() rejects NULL", {
  expect_error(ukbflow:::.assert_data_table(NULL), "data.table")
})

test_that(".assert_data_table() error message includes the argument name", {
  bad_dt <- data.frame(x = 1)
  expect_error(ukbflow:::.assert_data_table(bad_dt), "bad_dt")
})


# ===========================================================================
# .assert_has_cols()
# ===========================================================================

test_that(".assert_has_cols() passes when all columns present", {
  dt <- data.table::data.table(a = 1, b = 2, c = 3)
  expect_invisible(ukbflow:::.assert_has_cols(dt, c("a", "b")))
})

test_that(".assert_has_cols() returns data invisibly on success", {
  dt     <- data.table::data.table(a = 1, b = 2)
  result <- ukbflow:::.assert_has_cols(dt, "a")
  expect_identical(result, dt)
})

test_that(".assert_has_cols() aborts with single missing column name", {
  dt <- data.table::data.table(a = 1, b = 2)
  expect_error(ukbflow:::.assert_has_cols(dt, "missing_col"), "missing_col")
})

test_that(".assert_has_cols() reports all missing columns in one error", {
  dt  <- data.table::data.table(a = 1)
  err <- tryCatch(
    ukbflow:::.assert_has_cols(dt, c("b", "c")),
    error = function(e) conditionMessage(e)
  )
  expect_true(grepl("b", err))
  expect_true(grepl("c", err))
})

test_that(".assert_has_cols() passes with zero columns to check", {
  dt <- data.table::data.table(a = 1)
  expect_invisible(ukbflow:::.assert_has_cols(dt, character(0)))
})

test_that(".assert_has_cols() error message includes the data argument name", {
  my_data <- data.frame(x = 1)
  expect_error(ukbflow:::.assert_has_cols(my_data, "missing"), "my_data")
})


# ===========================================================================
# .assert_logical()
# ===========================================================================

test_that(".assert_logical() accepts TRUE", {
  expect_invisible(ukbflow:::.assert_logical(TRUE))
})

test_that(".assert_logical() accepts FALSE", {
  expect_invisible(ukbflow:::.assert_logical(FALSE))
})

test_that(".assert_logical() accepts a logical vector", {
  expect_invisible(ukbflow:::.assert_logical(c(TRUE, FALSE, TRUE)))
})

test_that(".assert_logical() accepts logical(0)", {
  expect_invisible(ukbflow:::.assert_logical(logical(0)))
})

test_that(".assert_logical() rejects character input", {
  expect_error(ukbflow:::.assert_logical("yes"), "logical")
})

test_that(".assert_logical() rejects numeric input", {
  expect_error(ukbflow:::.assert_logical(1L), "logical")
})

test_that(".assert_logical() rejects NULL", {
  expect_error(ukbflow:::.assert_logical(NULL), "logical")
})

test_that(".assert_logical() error message includes the argument name", {
  bad_flag <- "yes"
  expect_error(ukbflow:::.assert_logical(bad_flag), "bad_flag")
})


# ===========================================================================
# .assert_length_n()
# ===========================================================================

test_that(".assert_length_n() accepts vector of correct length", {
  expect_invisible(ukbflow:::.assert_length_n(c(1, 2, 3), 3L))
})

test_that(".assert_length_n() returns input invisibly", {
  x      <- c("a", "b")
  result <- ukbflow:::.assert_length_n(x, 2L)
  expect_identical(result, x)
})

test_that(".assert_length_n() accepts length-1 vector when n = 1", {
  expect_invisible(ukbflow:::.assert_length_n("only", 1L))
})

test_that(".assert_length_n() accepts empty vector when n = 0", {
  expect_invisible(ukbflow:::.assert_length_n(character(0), 0L))
})

test_that(".assert_length_n() rejects vector that is too short", {
  expect_error(ukbflow:::.assert_length_n(c(1, 2), 3L), "length 3")
})

test_that(".assert_length_n() rejects vector that is too long", {
  expect_error(ukbflow:::.assert_length_n(c(1, 2, 3, 4), 3L), "length 3")
})

test_that(".assert_length_n() error message includes the argument name", {
  bad_vec <- c(1, 2)
  expect_error(ukbflow:::.assert_length_n(bad_vec, 5L), "bad_vec")
})


# ===========================================================================
# .assert_flag()
# ===========================================================================

test_that(".assert_flag() accepts TRUE", {
  expect_invisible(ukbflow:::.assert_flag(TRUE))
})

test_that(".assert_flag() accepts FALSE", {
  expect_invisible(ukbflow:::.assert_flag(FALSE))
})

test_that(".assert_flag() rejects NA", {
  expect_error(ukbflow:::.assert_flag(NA), "TRUE or FALSE")
})

test_that(".assert_flag() rejects character", {
  expect_error(ukbflow:::.assert_flag("yes"), "TRUE or FALSE")
})

test_that(".assert_flag() rejects numeric", {
  expect_error(ukbflow:::.assert_flag(1L), "TRUE or FALSE")
})

test_that(".assert_flag() rejects length-2 logical", {
  expect_error(ukbflow:::.assert_flag(c(TRUE, FALSE)), "TRUE or FALSE")
})

test_that(".assert_flag() error message includes argument name", {
  my_flag <- "bad"
  expect_error(ukbflow:::.assert_flag(my_flag), "my_flag")
})


# ===========================================================================
# .assert_file_exists()
# ===========================================================================

test_that(".assert_file_exists() accepts an existing file", {
  tmp <- tempfile()
  writeLines("x", tmp)
  on.exit(unlink(tmp))
  expect_invisible(ukbflow:::.assert_file_exists(tmp))
})

test_that(".assert_file_exists() returns path invisibly", {
  tmp <- tempfile()
  writeLines("x", tmp)
  on.exit(unlink(tmp))
  result <- ukbflow:::.assert_file_exists(tmp)
  expect_equal(result, tmp)
})

test_that(".assert_file_exists() rejects a non-existent path", {
  expect_error(
    ukbflow:::.assert_file_exists("/no/such/file.csv"),
    "not found|File"
  )
})

test_that(".assert_file_exists() error message includes the path", {
  expect_error(
    ukbflow:::.assert_file_exists("/no/such/file.csv"),
    "/no/such/file.csv"
  )
})


# ===========================================================================
# .assert_not_null_if_false()
# ===========================================================================

test_that(".assert_not_null_if_false() passes when flag is TRUE and value is NULL", {
  expect_invisible(ukbflow:::.assert_not_null_if_false(TRUE, NULL))
})

test_that(".assert_not_null_if_false() passes when flag is FALSE and value is non-NULL", {
  expect_invisible(ukbflow:::.assert_not_null_if_false(FALSE, "covariates"))
})

test_that(".assert_not_null_if_false() returns value invisibly", {
  result <- ukbflow:::.assert_not_null_if_false(FALSE, "covariates")
  expect_equal(result, "covariates")
})

test_that(".assert_not_null_if_false() aborts when flag is FALSE and value is NULL", {
  expect_error(
    ukbflow:::.assert_not_null_if_false(FALSE, NULL),
    "must be supplied"
  )
})

test_that(".assert_not_null_if_false() error message includes flag and value names", {
  use_base <- FALSE
  covariates <- NULL
  expect_error(
    ukbflow:::.assert_not_null_if_false(use_base, covariates),
    "use_base"
  )
  expect_error(
    ukbflow:::.assert_not_null_if_false(use_base, covariates),
    "covariates"
  )
})


# ===========================================================================
# .assert_proportion()
# ===========================================================================

test_that(".assert_proportion() accepts a valid proportion", {
  expect_invisible(ukbflow:::.assert_proportion(0.05))
})

test_that(".assert_proportion() returns value invisibly", {
  result <- ukbflow:::.assert_proportion(0.5)
  expect_equal(result, 0.5)
})

test_that(".assert_proportion() rejects 0 (boundary, not strictly > 0)", {
  expect_error(ukbflow:::.assert_proportion(0), "strictly between 0 and 1")
})

test_that(".assert_proportion() rejects 1 (boundary, not strictly < 1)", {
  expect_error(ukbflow:::.assert_proportion(1), "strictly between 0 and 1")
})

test_that(".assert_proportion() rejects negative value", {
  expect_error(ukbflow:::.assert_proportion(-0.1), "strictly between 0 and 1")
})

test_that(".assert_proportion() rejects value > 1", {
  expect_error(ukbflow:::.assert_proportion(1.5), "strictly between 0 and 1")
})

test_that(".assert_proportion() rejects NA", {
  expect_error(ukbflow:::.assert_proportion(NA_real_), "strictly between 0 and 1")
})

test_that(".assert_proportion() rejects Inf", {
  expect_error(ukbflow:::.assert_proportion(Inf), "strictly between 0 and 1")
})

test_that(".assert_proportion() rejects character input", {
  expect_error(ukbflow:::.assert_proportion("0.05"), "strictly between 0 and 1")
})

test_that(".assert_proportion() rejects length > 1 vector", {
  expect_error(ukbflow:::.assert_proportion(c(0.1, 0.2)), "strictly between 0 and 1")
})

test_that(".assert_proportion() error message includes the argument name", {
  bad_p <- 1.5
  expect_error(ukbflow:::.assert_proportion(bad_p), "bad_p")
})
