library(testthat)
library(DataAudit)

test_that("audit_data returns correct class", {

  dat <- data.frame(
    x = 1:5,
    y = letters[1:5]
  )

  result <- audit_data(dat)

  expect_s3_class(
    result,
    "DataAuditReport"
  )

  expect_type(
    result,
    "list"
  )
})


test_that("audit_data returns expected components", {

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

  result <- audit_data(dat)

  expect_equal(
    names(result),
    c(
      "overview",
      "variables",
      "duplicates",
      "summary",
      "rules"
    )
  )

  expect_null(
    result$rules
  )
})

test_that("audit_data calculates dimensions", {

  dat <- data.frame(
    x = 1:4,
    y = letters[1:4]
  )

  result <- audit_data(dat)

  expect_equal(
    result$summary$Rows,
    4
  )

  expect_equal(
    result$summary$Variables,
    2
  )
})


test_that("audit_data detects missing values", {

  dat <- data.frame(
    x = c(1, NA, 3),
    y = c("A", "B", NA)
  )

  result <- audit_data(dat)

  expect_equal(
    result$variables$Missing,
    c(1L, 1L)
  )

  expect_equal(
    sum(result$variables$Missing),
    2
  )
})


test_that("audit_data calculates missing percentage", {

  dat <- data.frame(
    x = c(1, NA, NA, 4)
  )

  result <- audit_data(dat)

  expect_equal(
    result$variables$MissingPercent,
    50
  )
})


test_that("audit_data detects blank character values", {

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

  result <- audit_data(dat)

  expect_equal(
    result$variables$Blank,
    2L
  )
})


test_that("audit_data detects blanks in factors", {

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

  result <- audit_data(dat)

  expect_equal(
    result$variables$Blank,
    1L
  )
})


test_that("numeric variables have no blanks", {

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

  result <- audit_data(dat)

  expect_equal(
    result$variables$Blank,
    0L
  )
})


test_that("audit_data detects infinite values", {

  dat <- data.frame(
    x = c(
      1,
      Inf,
      -Inf,
      NA
    )
  )

  result <- audit_data(dat)

  expect_equal(
    result$variables$Infinite,
    2L
  )
})


test_that("non-numeric variables have no infinite values", {

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

  result <- audit_data(dat)

  expect_equal(
    result$variables$Infinite,
    0L
  )
})


test_that("audit_data counts unique values", {

  dat <- data.frame(
    x = c(
      1,
      1,
      2,
      NA
    )
  )

  result <- audit_data(dat)

  expect_equal(
    result$variables$Unique,
    2L
  )
})


test_that("audit_data detects constant variables", {

  dat <- data.frame(
    x = rep(1, 10),
    y = 1:10
  )

  result <- audit_data(
    dat,
    include_nzv = FALSE
  )

  expect_equal(
    result$variables$Constant,
    c(TRUE, FALSE)
  )
})


test_that("all missing variable is constant", {

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

  result <- audit_data(
    dat,
    include_nzv = FALSE
  )

  expect_true(
    result$variables$Constant
  )
})


test_that("audit_data detects duplicate rows", {

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

  result <- audit_data(dat)

  expect_equal(
    result$duplicates$Duplicate,
    c(
      FALSE,
      TRUE,
      TRUE,
      FALSE
    )
  )

  expect_equal(
    result$summary$DuplicateRows,
    2
  )
})


test_that("audit_data detects near-zero variance", {

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

  result <- audit_data(
    dat,
    include_nzv = TRUE
  )

  expect_true(
    result$variables$NearZeroVariance
  )
})


test_that("balanced variable is not near-zero variance", {

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

  result <- audit_data(
    dat,
    include_nzv = TRUE
  )

  expect_false(
    result$variables$NearZeroVariance
  )
})


test_that("constant variable is near-zero variance", {

  dat <- data.frame(
    x = rep("A", 20)
  )

  result <- audit_data(
    dat,
    include_nzv = TRUE
  )

  expect_true(
    result$variables$NearZeroVariance
  )
})


test_that("all missing variable is near-zero variance", {

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

  result <- audit_data(
    dat,
    include_nzv = TRUE
  )

  expect_true(
    result$variables$NearZeroVariance
  )
})


test_that("near-zero variance can be disabled", {

  dat <- data.frame(
    x = rep("A", 20)
  )

  result <- audit_data(
    dat,
    include_nzv = FALSE
  )

  expect_false(
    result$variables$NearZeroVariance
  )
})


test_that("variable issue is identified", {

  dat <- data.frame(
    clean = 1:5,
    missing = c(
      1,
      NA,
      3,
      4,
      5
    ),
    constant = rep(
      1,
      5
    )
  )

  result <- audit_data(
    dat,
    include_nzv = FALSE
  )

  expect_equal(
    result$variables$Issue,
    c(
      FALSE,
      TRUE,
      TRUE
    )
  )
})


test_that("overview contains expected metrics", {

  dat <- data.frame(
    x = c(1, NA, 3)
  )

  result <- audit_data(dat)

  expect_equal(
    result$overview$Metric,
    c(
      "Rows",
      "Variables",
      "Missing values",
      "Blank values",
      "Infinite values",
      "Duplicated rows",
      "Constant variables",
      "Near-zero variance variables",
      "Variables with issues"
    )
  )
})


test_that("summary reports total issues", {

  dat <- data.frame(
    x = c(
      1,
      NA,
      Inf
    )
  )

  result <- audit_data(
    dat,
    include_nzv = FALSE
  )

  expect_true(
    result$summary$TotalIssues >= 2
  )
})


test_that("include_nzv must be logical scalar", {

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

  expect_error(
    audit_data(
      dat,
      include_nzv = 1
    ),
    "'include_nzv' must be TRUE or FALSE"
  )

  expect_error(
    audit_data(
      dat,
      include_nzv = NA
    ),
    "'include_nzv' must be TRUE or FALSE"
  )

  expect_error(
    audit_data(
      dat,
      include_nzv = c(
        TRUE,
        FALSE
      )
    ),
    "'include_nzv' must be TRUE or FALSE"
  )
})


test_that("audit_data rejects invalid data", {

  expect_error(
    audit_data(NULL)
  )

  expect_error(
    audit_data(1:5)
  )

  expect_error(
    audit_data(
      list(x = 1:5)
    )
  )

  expect_error(
    audit_data(
      data.frame()
    )
  )
})
test_that("audit_data accepts custom audit rules", {

  dat <- data.frame(
    ID = c(
      "A001",
      "A002",
      "A002"
    ),
    Age = c(
      5,
      40,
      -2
    ),
    Species = c(
      "Dog",
      "Cat",
      ""
    ),
    stringsAsFactors = FALSE
  )

  rules <- audit_rules(
    range = list(
      Age = c(0, 30)
    ),
    unique = "ID",
    required = "Species"
  )

  result <- audit_data(
    dat,
    rules = rules
  )

  expect_s3_class(
    result,
    "DataAuditReport"
  )

  expect_s3_class(
    result$rules,
    "DataAuditRuleResult"
  )

  expect_equal(
    result$rules$total_violations,
    5L
  )

  expect_equal(
    result$summary$RuleViolations,
    5L
  )
})


test_that("audit_data reports zero rule violations without rules", {

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

  result <- audit_data(
    dat,
    include_nzv = FALSE
  )

  expect_null(
    result$rules
  )

  expect_equal(
    result$summary$RuleViolations,
    0L
  )

  expect_equal(
    result$summary$OverallIssues,
    result$summary$TotalIssues
  )
})


test_that("OverallIssues combines automatic and rule issues", {

  dat <- data.frame(
    Age = c(
      5,
      NA,
      40
    )
  )

  rules <- audit_rules(
    range = list(
      Age = c(0, 30)
    )
  )

  result <- audit_data(
    dat,
    include_nzv = FALSE,
    rules = rules
  )

  expect_equal(
    result$summary$OverallIssues,
    result$summary$TotalIssues +
      result$summary$RuleViolations
  )
})


test_that("audit_data rejects invalid rules", {

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

  expect_error(
    audit_data(
      dat,
      rules = list()
    ),
    "'rules' must be NULL or an object created by audit_rules"
  )
})


test_that("audit_data propagates rule variable errors", {

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

  rules <- audit_rules(
    required = "MissingVariable"
  )

  expect_error(
    audit_data(
      dat,
      rules = rules
    ),
    "referenced by rules not found"
  )
})
