library(testthat)
library(DataAudit)

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

test_that("pattern_check returns PatternCheck", {

  dat <- data.frame(
    ID = c("SMP-001", "SMP-002")
  )

  result <- pattern_check(
    dat,
    "ID",
    "^SMP-[0-9]{3}$"
  )

  expect_s3_class(
    result,
    "PatternCheck"
  )

})

test_that("pattern_check returns data.frame", {

  dat <- data.frame(
    ID = c("SMP-001", "SMP-002")
  )

  result <- pattern_check(
    dat,
    "ID",
    "^SMP-[0-9]{3}$"
  )

  expect_s3_class(
    result,
    "data.frame"
  )

})

test_that("pattern_check returns expected columns", {

  dat <- data.frame(
    ID = "SMP-001"
  )

  result <- pattern_check(
    dat,
    "ID",
    "^SMP-[0-9]{3}$"
  )

  expect_equal(
    names(result),
    c(
      "Row",
      "Value",
      "Missing",
      "Match",
      "InvalidPattern"
    )
  )

})


#==========================================================
# Matching
#==========================================================

test_that("pattern_check identifies valid patterns", {

  dat <- data.frame(
    ID = c(
      "SMP-001",
      "SMP-002",
      "SMP-999"
    )
  )

  result <- pattern_check(
    dat,
    "ID",
    "^SMP-[0-9]{3}$"
  )

  expect_true(
    all(result$Match)
  )

  expect_false(
    any(result$InvalidPattern)
  )

})

test_that("pattern_check identifies invalid patterns", {

  dat <- data.frame(
    ID = c(
      "SMP-001",
      "SMP001",
      "ABC-001",
      "SMP-12"
    )
  )

  result <- pattern_check(
    dat,
    "ID",
    "^SMP-[0-9]{3}$"
  )

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

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

})


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

test_that("pattern_check handles missing values", {

  dat <- data.frame(
    ID = c(
      "SMP-001",
      NA,
      "BAD"
    )
  )

  result <- pattern_check(
    dat,
    "ID",
    "^SMP-[0-9]{3}$"
  )

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

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

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

})

test_that("pattern_check handles all missing values", {

  dat <- data.frame(
    ID = rep(
      NA_character_,
      5
    )
  )

  result <- pattern_check(
    dat,
    "ID",
    "^SMP-"
  )

  expect_true(
    all(result$Missing)
  )

  expect_false(
    any(result$Match)
  )

  expect_false(
    any(result$InvalidPattern)
  )

})


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

test_that("pattern_check handles factor variables", {

  dat <- data.frame(
    ID = factor(
      c(
        "SMP-001",
        "BAD-001",
        "SMP-002"
      )
    )
  )

  result <- pattern_check(
    dat,
    "ID",
    "^SMP-[0-9]{3}$"
  )

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

  expect_true(
    is.factor(result$Value)
  )

})


#==========================================================
# Case sensitivity
#==========================================================

test_that("pattern_check is case sensitive by default", {

  dat <- data.frame(
    ID = c(
      "SMP-001",
      "smp-002"
    )
  )

  result <- pattern_check(
    dat,
    "ID",
    "^SMP-[0-9]{3}$"
  )

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

})

test_that("pattern_check can ignore case", {

  dat <- data.frame(
    ID = c(
      "SMP-001",
      "smp-002",
      "Smp-003"
    )
  )

  result <- pattern_check(
    dat,
    "ID",
    "^SMP-[0-9]{3}$",
    ignore_case = TRUE
  )

  expect_true(
    all(result$Match)
  )

})


#==========================================================
# General patterns
#==========================================================

test_that("pattern_check handles alphabetic pattern", {

  dat <- data.frame(
    Code = c(
      "ABC",
      "XYZ",
      "A12"
    )
  )

  result <- pattern_check(
    dat,
    "Code",
    "^[A-Za-z]+$"
  )

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

})

test_that("pattern_check handles numeric text pattern", {

  dat <- data.frame(
    Code = c(
      "123",
      "456",
      "12A"
    )
  )

  result <- pattern_check(
    dat,
    "Code",
    "^[0-9]+$"
  )

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

})

test_that("pattern_check handles blank strings", {

  dat <- data.frame(
    Code = c(
      "ABC",
      "",
      "XYZ"
    )
  )

  result <- pattern_check(
    dat,
    "Code",
    "^[A-Z]+$"
  )

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

})


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

test_that("pattern_check returns row numbers", {

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

  result <- pattern_check(
    dat,
    "ID",
    "^[A-Z]$"
  )

  expect_equal(
    result$Row,
    1:3
  )

})


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

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

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

  result <- pattern_check(
    dat,
    "ID",
    "^SMP-"
  )

  expect_s3_class(
    result,
    "PatternCheck"
  )

  expect_equal(
    nrow(result),
    0L
  )

})


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

test_that("pattern_check handles tibble", {

  skip_if_not_installed("tibble")

  dat <- tibble::tibble(
    ID = c(
      "SMP-001",
      "BAD",
      "SMP-002"
    )
  )

  result <- pattern_check(
    dat,
    "ID",
    "^SMP-[0-9]{3}$"
  )

  expect_s3_class(
    result,
    "PatternCheck"
  )

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

})


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

test_that("pattern_check rejects missing variable", {

  dat <- data.frame(
    ID = "SMP-001"
  )

  expect_error(
    pattern_check(
      dat,
      pattern = "^SMP-"
    ),
    "'variable' must be a single non-empty character string"
  )

})

test_that("pattern_check rejects NULL variable", {

  expect_error(
    pattern_check(
      iris,
      NULL,
      "^A"
    ),
    "'variable' must be a single non-empty character string"
  )

})

test_that("pattern_check rejects numeric variable argument", {

  expect_error(
    pattern_check(
      iris,
      1,
      "^A"
    ),
    "'variable' must be a single non-empty character string"
  )

})

test_that("pattern_check rejects multiple variable names", {

  expect_error(
    pattern_check(
      iris,
      c("Species", "Sepal.Length"),
      "^A"
    ),
    "'variable' must be a single non-empty character string"
  )

})

test_that("pattern_check rejects NA variable", {

  expect_error(
    pattern_check(
      iris,
      NA_character_,
      "^A"
    ),
    "'variable' must be a single non-empty character string"
  )

})

test_that("pattern_check rejects empty variable", {

  expect_error(
    pattern_check(
      iris,
      "",
      "^A"
    ),
    "'variable' must be a single non-empty character string"
  )

})

test_that("pattern_check rejects unknown variable", {

  expect_error(
    pattern_check(
      iris,
      "Unknown",
      "^A"
    ),
    "Variable 'Unknown' was not found in 'data'"
  )

})


#==========================================================
# Pattern validation
#==========================================================

test_that("pattern_check rejects missing pattern", {

  dat <- data.frame(
    ID = "A"
  )

  expect_error(
    pattern_check(
      dat,
      "ID"
    ),
    "'pattern' must be a single non-empty character string"
  )

})

test_that("pattern_check rejects NULL pattern", {

  dat <- data.frame(
    ID = "A"
  )

  expect_error(
    pattern_check(
      dat,
      "ID",
      NULL
    ),
    "'pattern' must be a single non-empty character string"
  )

})

test_that("pattern_check rejects numeric pattern", {

  dat <- data.frame(
    ID = "A"
  )

  expect_error(
    pattern_check(
      dat,
      "ID",
      123
    ),
    "'pattern' must be a single non-empty character string"
  )

})

test_that("pattern_check rejects multiple patterns", {

  dat <- data.frame(
    ID = "A"
  )

  expect_error(
    pattern_check(
      dat,
      "ID",
      c("^A", "^B")
    ),
    "'pattern' must be a single non-empty character string"
  )

})

test_that("pattern_check rejects NA pattern", {

  dat <- data.frame(
    ID = "A"
  )

  expect_error(
    pattern_check(
      dat,
      "ID",
      NA_character_
    ),
    "'pattern' must be a single non-empty character string"
  )

})

test_that("pattern_check rejects empty pattern", {

  dat <- data.frame(
    ID = "A"
  )

  expect_error(
    pattern_check(
      dat,
      "ID",
      ""
    ),
    "'pattern' must be a single non-empty character string"
  )

})


#==========================================================
# ignore_case validation
#==========================================================

test_that("pattern_check rejects character ignore_case", {

  dat <- data.frame(
    ID = "A"
  )

  expect_error(
    pattern_check(
      dat,
      "ID",
      "^A$",
      ignore_case = "TRUE"
    ),
    "'ignore_case' must be TRUE or FALSE"
  )

})

test_that("pattern_check rejects numeric ignore_case", {

  dat <- data.frame(
    ID = "A"
  )

  expect_error(
    pattern_check(
      dat,
      "ID",
      "^A$",
      ignore_case = 1
    ),
    "'ignore_case' must be TRUE or FALSE"
  )

})

test_that("pattern_check rejects vector ignore_case", {

  dat <- data.frame(
    ID = "A"
  )

  expect_error(
    pattern_check(
      dat,
      "ID",
      "^A$",
      ignore_case = c(TRUE, FALSE)
    ),
    "'ignore_case' must be TRUE or FALSE"
  )

})

test_that("pattern_check rejects NA ignore_case", {

  dat <- data.frame(
    ID = "A"
  )

  expect_error(
    pattern_check(
      dat,
      "ID",
      "^A$",
      ignore_case = NA
    ),
    "'ignore_case' must be TRUE or FALSE"
  )

})


#==========================================================
# Unsupported variable types
#==========================================================

test_that("pattern_check rejects numeric data variable", {

  dat <- data.frame(
    ID = 1:5
  )

  expect_error(
    pattern_check(
      dat,
      "ID",
      "^[0-9]+$"
    ),
    "'variable' must refer to a character or factor variable"
  )

})

test_that("pattern_check rejects logical data variable", {

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

  expect_error(
    pattern_check(
      dat,
      "ID",
      "^TRUE$"
    ),
    "'variable' must refer to a character or factor variable"
  )

})

test_that("pattern_check rejects Date data variable", {

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

  expect_error(
    pattern_check(
      dat,
      "ID",
      "^2026"
    ),
    "'variable' must refer to a character or factor variable"
  )

})


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

test_that("pattern_check rejects NULL data", {

  expect_error(
    pattern_check(
      NULL,
      "ID",
      "^A"
    ),
    "'data' cannot be NULL"
  )

})

test_that("pattern_check rejects vector data", {

  expect_error(
    pattern_check(
      letters,
      "ID",
      "^A"
    ),
    "'data' must be a data.frame"
  )

})

test_that("pattern_check rejects list data", {

  expect_error(
    pattern_check(
      list(ID = letters),
      "ID",
      "^A"
    ),
    "'data' must be a data.frame"
  )

})

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

  expect_error(
    pattern_check(
      data.frame(),
      "ID",
      "^A"
    ),
    "at least one variable"
  )

})
