library(testthat)
library(DataAudit)

#==========================================================
# Basic output
#==========================================================

test_that("nzv_check returns NZVCheck object", {

  result <- nzv_check(iris)

  expect_s3_class(
    result,
    "NZVCheck"
  )

})

test_that("nzv_check returns data.frame", {

  result <- nzv_check(iris)

  expect_s3_class(
    result,
    "data.frame"
  )

})

test_that("nzv_check contains expected columns", {

  result <- nzv_check(iris)

  expect_equal(
    names(result),
    c(
      "Variable",
      "Observed",
      "Missing",
      "UniqueValues",
      "PercentUnique",
      "FrequencyRatio",
      "ZeroVariance",
      "NearZeroVariance"
    )
  )

})

test_that("nzv_check returns one row per variable", {

  result <- nzv_check(iris)

  expect_equal(
    nrow(result),
    ncol(iris)
  )

})


#==========================================================
# Normal variation
#==========================================================

test_that("nzv_check identifies variable with adequate variation", {

  dat <- data.frame(
    x = 1:100
  )

  result <- nzv_check(dat)

  expect_false(
    result$ZeroVariance
  )

  expect_false(
    result$NearZeroVariance
  )

  expect_equal(
    result$UniqueValues,
    100L
  )

  expect_equal(
    result$PercentUnique,
    100
  )

  expect_equal(
    result$FrequencyRatio,
    1
  )

})


#==========================================================
# Zero variance
#==========================================================

test_that("nzv_check identifies constant numeric variable", {

  dat <- data.frame(
    x = rep(10, 100)
  )

  result <- nzv_check(dat)

  expect_true(
    result$ZeroVariance
  )

  expect_true(
    result$NearZeroVariance
  )

  expect_equal(
    result$UniqueValues,
    1L
  )

  expect_true(
    is.infinite(result$FrequencyRatio)
  )

})

test_that("nzv_check identifies constant character variable", {

  dat <- data.frame(
    x = rep("A", 50),
    stringsAsFactors = FALSE
  )

  result <- nzv_check(dat)

  expect_true(
    result$ZeroVariance
  )

  expect_true(
    result$NearZeroVariance
  )

})


#==========================================================
# Near-zero variance
#==========================================================

test_that("nzv_check detects near-zero variance variable", {

  dat <- data.frame(
    x = c(
      rep(0, 99),
      1
    )
  )

  result <- nzv_check(dat)

  expect_false(
    result$ZeroVariance
  )

  expect_true(
    result$NearZeroVariance
  )

  expect_equal(
    result$UniqueValues,
    2L
  )

  expect_equal(
    result$PercentUnique,
    2
  )

  expect_equal(
    result$FrequencyRatio,
    99
  )

})

test_that("nzv_check does not flag balanced binary variable", {

  dat <- data.frame(
    x = rep(
      c(0, 1),
      each = 50
    )
  )

  result <- nzv_check(dat)

  expect_false(
    result$ZeroVariance
  )

  expect_false(
    result$NearZeroVariance
  )

  expect_equal(
    result$FrequencyRatio,
    1
  )

})


#==========================================================
# Threshold boundaries
#==========================================================

test_that("nzv_check uses greater than frequency cutoff", {

  dat <- data.frame(
    x = c(
      rep(0, 19),
      1
    )
  )

  result <- nzv_check(
    dat,
    unique_cut = 10,
    freq_cut = 19
  )

  expect_false(
    result$NearZeroVariance
  )

})

test_that("nzv_check responds to custom frequency cutoff", {

  dat <- data.frame(
    x = c(
      rep(0, 19),
      1
    )
  )

  result <- nzv_check(
    dat,
    unique_cut = 10,
    freq_cut = 10
  )

  expect_true(
    result$NearZeroVariance
  )

})

test_that("nzv_check responds to custom unique cutoff", {

  dat <- data.frame(
    x = c(
      rep(0, 39),
      1
    )
  )

  result1 <- nzv_check(
    dat,
    unique_cut = 4,
    freq_cut = 19
  )

  result2 <- nzv_check(
    dat,
    unique_cut = 5,
    freq_cut = 19
  )

  expect_false(
    result1$NearZeroVariance
  )

  expect_true(
    result2$NearZeroVariance
  )

})


#==========================================================
# Missing values
#==========================================================

test_that("nzv_check excludes missing values from calculations", {

  dat <- data.frame(
    x = c(
      rep(0, 99),
      1,
      rep(NA_real_, 10)
    )
  )

  result <- nzv_check(dat)

  expect_equal(
    result$Observed,
    100L
  )

  expect_equal(
    result$Missing,
    10L
  )

  expect_equal(
    result$UniqueValues,
    2L
  )

  expect_equal(
    result$PercentUnique,
    2
  )

  expect_equal(
    result$FrequencyRatio,
    99
  )

  expect_true(
    result$NearZeroVariance
  )

})


#==========================================================
# All missing
#==========================================================

test_that("nzv_check handles all-NA variable", {

  dat <- data.frame(
    x = rep(
      NA_real_,
      10
    )
  )

  result <- nzv_check(dat)

  expect_equal(
    result$Observed,
    0L
  )

  expect_equal(
    result$Missing,
    10L
  )

  expect_equal(
    result$UniqueValues,
    0L
  )

  expect_true(
    is.na(result$PercentUnique)
  )

  expect_true(
    is.infinite(result$FrequencyRatio)
  )

  expect_true(
    result$ZeroVariance
  )

  expect_true(
    result$NearZeroVariance
  )

})


#==========================================================
# Factor
#==========================================================

test_that("nzv_check handles factors", {

  dat <- data.frame(
    x = factor(
      c(
        rep("A", 99),
        "B"
      )
    )
  )

  result <- nzv_check(dat)

  expect_equal(
    result$UniqueValues,
    2L
  )

  expect_equal(
    result$FrequencyRatio,
    99
  )

  expect_true(
    result$NearZeroVariance
  )

})


#==========================================================
# Character
#==========================================================

test_that("nzv_check handles character variables", {

  dat <- data.frame(
    x = c(
      rep("Dog", 99),
      "Cat"
    ),
    stringsAsFactors = FALSE
  )

  result <- nzv_check(dat)

  expect_true(
    result$NearZeroVariance
  )

})


#==========================================================
# Logical
#==========================================================

test_that("nzv_check handles logical variables", {

  dat <- data.frame(
    x = c(
      rep(TRUE, 99),
      FALSE
    )
  )

  result <- nzv_check(dat)

  expect_true(
    result$NearZeroVariance
  )

})


#==========================================================
# Date
#==========================================================

test_that("nzv_check handles Date variables", {

  dat <- data.frame(
    x = c(
      rep(
        as.Date("2026-01-01"),
        99
      ),
      as.Date("2026-01-02")
    )
  )

  result <- nzv_check(dat)

  expect_equal(
    result$UniqueValues,
    2L
  )

  expect_true(
    result$NearZeroVariance
  )

})


#==========================================================
# POSIXct
#==========================================================

test_that("nzv_check handles POSIXct variables", {

  a <- as.POSIXct(
    "2026-01-01 10:00:00",
    tz = "UTC"
  )

  b <- as.POSIXct(
    "2026-01-02 10:00:00",
    tz = "UTC"
  )

  dat <- data.frame(
    x = c(
      rep(a, 99),
      b
    )
  )

  result <- nzv_check(dat)

  expect_equal(
    result$UniqueValues,
    2L
  )

  expect_true(
    result$NearZeroVariance
  )

})


#==========================================================
# Multiple variables
#==========================================================

test_that("nzv_check correctly classifies multiple variables", {

  dat <- data.frame(
    ID = 1:100,
    Constant = rep(5, 100),
    Rare = c(
      rep(0, 99),
      1
    ),
    Balanced = rep(
      c(0, 1),
      each = 50
    )
  )

  result <- nzv_check(dat)

  expect_equal(
    result$Variable,
    c(
      "ID",
      "Constant",
      "Rare",
      "Balanced"
    )
  )

  expect_equal(
    result$ZeroVariance,
    c(
      FALSE,
      TRUE,
      FALSE,
      FALSE
    )
  )

  expect_equal(
    result$NearZeroVariance,
    c(
      FALSE,
      TRUE,
      TRUE,
      FALSE
    )
  )

})


#==========================================================
# One-row data
#==========================================================

test_that("nzv_check handles one-row data", {

  dat <- data.frame(
    x = 1,
    y = "A",
    stringsAsFactors = FALSE
  )

  result <- nzv_check(dat)

  expect_true(
    all(result$ZeroVariance)
  )

  expect_true(
    all(result$NearZeroVariance)
  )

})


#==========================================================
# Zero-row data
#==========================================================

test_that("nzv_check handles zero-row data", {

  dat <- data.frame(
    x = numeric(0),
    y = character(0)
  )

  result <- nzv_check(dat)

  expect_equal(
    result$Observed,
    c(0L, 0L)
  )

  expect_equal(
    result$Missing,
    c(0L, 0L)
  )

  expect_equal(
    result$UniqueValues,
    c(0L, 0L)
  )

  expect_true(
    all(is.na(result$PercentUnique))
  )

  expect_true(
    all(is.infinite(result$FrequencyRatio))
  )

  expect_true(
    all(result$ZeroVariance)
  )

  expect_true(
    all(result$NearZeroVariance)
  )

})


#==========================================================
# Tibble
#==========================================================

test_that("nzv_check handles tibble", {

  skip_if_not_installed("tibble")

  dat <- tibble::tibble(
    x = 1:100,
    y = c(
      rep(0, 99),
      1
    )
  )

  result <- nzv_check(dat)

  expect_s3_class(
    result,
    "NZVCheck"
  )

  expect_equal(
    result$NearZeroVariance,
    c(FALSE, TRUE)
  )

})


#==========================================================
# unique_cut validation
#==========================================================

test_that("nzv_check rejects non-numeric unique_cut", {

  expect_error(
    nzv_check(
      iris,
      unique_cut = "10"
    ),
    "'unique_cut' must be a single finite numeric value"
  )

})

test_that("nzv_check rejects vector unique_cut", {

  expect_error(
    nzv_check(
      iris,
      unique_cut = c(5, 10)
    ),
    "'unique_cut' must be a single finite numeric value"
  )

})

test_that("nzv_check rejects NA unique_cut", {

  expect_error(
    nzv_check(
      iris,
      unique_cut = NA_real_
    ),
    "'unique_cut' must be a single finite numeric value"
  )

})

test_that("nzv_check rejects infinite unique_cut", {

  expect_error(
    nzv_check(
      iris,
      unique_cut = Inf
    ),
    "'unique_cut' must be a single finite numeric value"
  )

})

test_that("nzv_check rejects negative unique_cut", {

  expect_error(
    nzv_check(
      iris,
      unique_cut = -1
    ),
    "'unique_cut' must be between 0 and 100"
  )

})

test_that("nzv_check rejects unique_cut above 100", {

  expect_error(
    nzv_check(
      iris,
      unique_cut = 101
    ),
    "'unique_cut' must be between 0 and 100"
  )

})


#==========================================================
# freq_cut validation
#==========================================================

test_that("nzv_check rejects non-numeric freq_cut", {

  expect_error(
    nzv_check(
      iris,
      freq_cut = "19"
    ),
    "'freq_cut' must be a single finite numeric value"
  )

})

test_that("nzv_check rejects vector freq_cut", {

  expect_error(
    nzv_check(
      iris,
      freq_cut = c(10, 19)
    ),
    "'freq_cut' must be a single finite numeric value"
  )

})

test_that("nzv_check rejects NA freq_cut", {

  expect_error(
    nzv_check(
      iris,
      freq_cut = NA_real_
    ),
    "'freq_cut' must be a single finite numeric value"
  )

})

test_that("nzv_check rejects infinite freq_cut", {

  expect_error(
    nzv_check(
      iris,
      freq_cut = Inf
    ),
    "'freq_cut' must be a single finite numeric value"
  )

})

test_that("nzv_check rejects negative freq_cut", {

  expect_error(
    nzv_check(
      iris,
      freq_cut = -1
    ),
    "'freq_cut' must be non-negative"
  )

})


#==========================================================
# Invalid data
#==========================================================

test_that("nzv_check rejects NULL", {

  expect_error(
    nzv_check(NULL),
    "'data' cannot be NULL"
  )

})

test_that("nzv_check rejects numeric vector", {

  expect_error(
    nzv_check(1:10),
    "'data' must be a data.frame"
  )

})

test_that("nzv_check rejects character vector", {

  expect_error(
    nzv_check(letters),
    "'data' must be a data.frame"
  )

})

test_that("nzv_check rejects list", {

  expect_error(
    nzv_check(
      list(
        x = 1:10
      )
    ),
    "'data' must be a data.frame"
  )

})

test_that("nzv_check rejects data.frame without variables", {

  expect_error(
    nzv_check(
      data.frame()
    ),
    "at least one variable"
  )

})
