library(testthat)
library(DataAudit)

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

test_that("category_check returns CategoryCheck", {

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

  result <- category_check(
    dat,
    "x",
    c("A", "B")
  )

  expect_s3_class(
    result,
    "CategoryCheck"
  )

})

test_that("category_check returns data.frame", {

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

  result <- category_check(
    dat,
    "x",
    c("A", "B")
  )

  expect_s3_class(
    result,
    "data.frame"
  )

})

test_that("category_check returns expected columns", {

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

  result <- category_check(
    dat,
    "x",
    c("A", "B")
  )

  expect_equal(
    names(result),
    c(
      "Row",
      "Value",
      "Missing",
      "ValidCategory",
      "InvalidCategory"
    )
  )

})


#==========================================================
# Character variables
#==========================================================

test_that("category_check identifies valid categories", {

  dat <- data.frame(
    Species = c(
      "Dog",
      "Cat",
      "Cattle"
    ),
    stringsAsFactors = FALSE
  )

  result <- category_check(
    dat,
    "Species",
    c(
      "Dog",
      "Cat",
      "Cattle"
    )
  )

  expect_true(
    all(result$ValidCategory)
  )

  expect_false(
    any(result$InvalidCategory)
  )

})

test_that("category_check identifies invalid categories", {

  dat <- data.frame(
    Species = c(
      "Dog",
      "Horse",
      "Cat",
      "Goat"
    ),
    stringsAsFactors = FALSE
  )

  result <- category_check(
    dat,
    "Species",
    c(
      "Dog",
      "Cat",
      "Cattle"
    )
  )

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

})

test_that("category_check is case sensitive", {

  dat <- data.frame(
    Species = c(
      "Dog",
      "dog"
    ),
    stringsAsFactors = FALSE
  )

  result <- category_check(
    dat,
    "Species",
    "Dog"
  )

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

})


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

test_that("category_check identifies missing values", {

  dat <- data.frame(
    x = c(
      "A",
      NA,
      "B"
    ),
    stringsAsFactors = FALSE
  )

  result <- category_check(
    dat,
    "x",
    c(
      "A",
      "B"
    )
  )

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

})

test_that("missing values are not invalid categories", {

  dat <- data.frame(
    x = c(
      "A",
      NA,
      "C"
    ),
    stringsAsFactors = FALSE
  )

  result <- category_check(
    dat,
    "x",
    c(
      "A",
      "B"
    )
  )

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

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

})


#==========================================================
# Factor variables
#==========================================================

test_that("category_check handles factors", {

  dat <- data.frame(
    x = factor(
      c(
        "Dog",
        "Cat",
        "Horse"
      )
    )
  )

  result <- category_check(
    dat,
    "x",
    c(
      "Dog",
      "Cat"
    )
  )

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

  expect_true(
    is.factor(result$Value)
  )

})

test_that("category_check handles factor allowed values", {

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

  allowed <- factor(
    c(
      "A",
      "B"
    )
  )

  result <- category_check(
    dat,
    "x",
    allowed
  )

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

})


#==========================================================
# Numeric variables
#==========================================================

test_that("category_check handles numeric categories", {

  dat <- data.frame(
    Score = c(
      0,
      1,
      2,
      3
    )
  )

  result <- category_check(
    dat,
    "Score",
    c(
      0,
      1,
      2
    )
  )

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

})


#==========================================================
# Integer variables
#==========================================================

test_that("category_check handles integer categories", {

  dat <- data.frame(
    Code = c(
      1L,
      2L,
      3L,
      4L
    )
  )

  result <- category_check(
    dat,
    "Code",
    c(
      1L,
      2L,
      3L
    )
  )

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

})


#==========================================================
# Logical variables
#==========================================================

test_that("category_check handles logical categories", {

  dat <- data.frame(
    Positive = c(
      TRUE,
      FALSE,
      TRUE
    )
  )

  result <- category_check(
    dat,
    "Positive",
    TRUE
  )

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

})


#==========================================================
# All invalid
#==========================================================

test_that("category_check handles all invalid categories", {

  dat <- data.frame(
    x = c(
      "X",
      "Y",
      "Z"
    )
  )

  result <- category_check(
    dat,
    "x",
    c(
      "A",
      "B"
    )
  )

  expect_true(
    all(result$InvalidCategory)
  )

  expect_false(
    any(result$ValidCategory)
  )

})


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

test_that("category_check handles all missing character values", {

  dat <- data.frame(
    x = rep(
      NA_character_,
      4
    )
  )

  result <- category_check(
    dat,
    "x",
    c(
      "A",
      "B"
    )
  )

  expect_true(
    all(result$Missing)
  )

  expect_false(
    any(result$ValidCategory)
  )

  expect_false(
    any(result$InvalidCategory)
  )

})


#==========================================================
# Single allowed category
#==========================================================

test_that("category_check handles one allowed value", {

  dat <- data.frame(
    x = c(
      "Yes",
      "No",
      "Yes"
    )
  )

  result <- category_check(
    dat,
    "x",
    "Yes"
  )

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

})


#==========================================================
# Duplicate allowed values
#==========================================================

test_that("category_check handles duplicate allowed values", {

  dat <- data.frame(
    x = c(
      "A",
      "B",
      "C"
    )
  )

  result <- category_check(
    dat,
    "x",
    c(
      "A",
      "A",
      "B"
    )
  )

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

})


#==========================================================
# Zero rows
#==========================================================

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

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

  result <- category_check(
    dat,
    "x",
    c(
      "A",
      "B"
    )
  )

  expect_s3_class(
    result,
    "CategoryCheck"
  )

  expect_equal(
    nrow(result),
    0L
  )

})


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

test_that("category_check handles tibble", {

  skip_if_not_installed("tibble")

  dat <- tibble::tibble(
    Species = c(
      "Dog",
      "Cat",
      "Horse"
    )
  )

  result <- category_check(
    dat,
    "Species",
    c(
      "Dog",
      "Cat"
    )
  )

  expect_s3_class(
    result,
    "CategoryCheck"
  )

  expect_equal(
    sum(result$InvalidCategory),
    1L
  )

})


#==========================================================
# Row numbers
#==========================================================

test_that("category_check preserves row numbers", {

  dat <- data.frame(
    x = c(
      "A",
      "B",
      "C"
    )
  )

  result <- category_check(
    dat,
    "x",
    c(
      "A",
      "B"
    )
  )

  expect_equal(
    result$Row,
    1:3
  )

})


#==========================================================
# Variable validation
#==========================================================

test_that("category_check rejects missing variable", {

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

  expect_error(
    category_check(
      dat,
      allowed = c("A", "B")
    ),
    "'variable' must be a single non-empty character string"
  )

})

test_that("category_check rejects NULL variable", {

  expect_error(
    category_check(
      iris,
      NULL,
      c("A", "B")
    ),
    "'variable' must be a single non-empty character string"
  )

})

test_that("category_check rejects numeric variable argument", {

  expect_error(
    category_check(
      iris,
      1,
      c("A", "B")
    ),
    "'variable' must be a single non-empty character string"
  )

})

test_that("category_check rejects multiple variable names", {

  expect_error(
    category_check(
      iris,
      c(
        "Sepal.Length",
        "Sepal.Width"
      ),
      c(1, 2)
    ),
    "'variable' must be a single non-empty character string"
  )

})

test_that("category_check rejects NA variable", {

  expect_error(
    category_check(
      iris,
      NA_character_,
      c(1, 2)
    ),
    "'variable' must be a single non-empty character string"
  )

})

test_that("category_check rejects blank variable name", {

  expect_error(
    category_check(
      iris,
      "",
      c(1, 2)
    ),
    "'variable' must be a single non-empty character string"
  )

})

test_that("category_check rejects unknown variable", {

  expect_error(
    category_check(
      iris,
      "Unknown",
      c(1, 2)
    ),
    "Variable 'Unknown' was not found in 'data'"
  )

})


#==========================================================
# allowed validation
#==========================================================

test_that("category_check rejects missing allowed argument", {

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

  expect_error(
    category_check(
      dat,
      "x"
    ),
    "'allowed' must contain at least one value"
  )

})

test_that("category_check rejects NULL allowed", {

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

  expect_error(
    category_check(
      dat,
      "x",
      NULL
    ),
    "'allowed' must contain at least one value"
  )

})

test_that("category_check rejects empty allowed vector", {

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

  expect_error(
    category_check(
      dat,
      "x",
      character(0)
    ),
    "'allowed' must contain at least one value"
  )

})

test_that("category_check rejects allowed containing NA", {

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

  expect_error(
    category_check(
      dat,
      "x",
      c(
        "A",
        NA_character_
      )
    ),
    "'allowed' must not contain missing values"
  )

})

test_that("category_check rejects list allowed", {

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

  expect_error(
    category_check(
      dat,
      "x",
      list(
        "A",
        "B"
      )
    ),
    "'allowed' must be an atomic vector"
  )

})

test_that("category_check rejects data.frame allowed", {

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

  expect_error(
    category_check(
      dat,
      "x",
      data.frame(
        allowed = c(
          "A",
          "B"
        )
      )
    ),
    "'allowed' must be an atomic vector"
  )

})

test_that("category_check rejects matrix allowed", {

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

  allowed <- matrix(
    c(
      "A",
      "B"
    ),
    ncol = 1
  )

  expect_error(
    category_check(
      dat,
      "x",
      allowed
    ),
    "'allowed' must be an atomic vector"
  )

})


#==========================================================
# Unsupported variable classes
#==========================================================

test_that("category_check rejects Date variable", {

  dat <- data.frame(
    x = as.Date(
      "2026-01-01"
    ) + 0:2
  )

  expect_error(
    category_check(
      dat,
      "x",
      as.Date("2026-01-01")
    ),
    "'variable' must be character, factor, numeric, integer, or logical"
  )

})

test_that("category_check rejects POSIXct variable", {

  dat <- data.frame(
    x = as.POSIXct(
      "2026-01-01",
      tz = "UTC"
    ) + 0:2
  )

  expect_error(
    category_check(
      dat,
      "x",
      1:3
    ),
    "'variable' must be character, factor, numeric, integer, or logical"
  )

})


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

test_that("category_check rejects NULL data", {

  expect_error(
    category_check(
      NULL,
      "x",
      c("A", "B")
    ),
    "'data' cannot be NULL"
  )

})

test_that("category_check rejects numeric vector data", {

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

})

test_that("category_check rejects character vector data", {

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

})

test_that("category_check rejects list data", {

  expect_error(
    category_check(
      list(
        x = c("A", "B")
      ),
      "x",
      c("A", "B")
    ),
    "'data' must be a data.frame"
  )

})

test_that("category_check rejects empty data.frame", {

  expect_error(
    category_check(
      data.frame(),
      "x",
      c("A", "B")
    ),
    "at least one variable"
  )

})
