library(testthat)
library(DataAudit)

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

test_that("outlier_check returns OutlierCheck", {

  result <- outlier_check(iris)

  expect_s3_class(
    result,
    "OutlierCheck"
  )

})

test_that("outlier_check returns data.frame", {

  result <- outlier_check(iris)

  expect_s3_class(
    result,
    "data.frame"
  )

})

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

  result <- outlier_check(iris)

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

})

test_that("outlier_check has expected columns", {

  result <- outlier_check(iris)

  expect_equal(
    names(result),
    c(
      "Variable",
      "Numeric",
      "Missing",
      "Infinite",
      "Observed",
      "Q1",
      "Q3",
      "IQR",
      "LowerBound",
      "UpperBound",
      "LowerOutliers",
      "UpperOutliers",
      "Outliers",
      "OutlierPercent",
      "HasOutlier"
    )
  )

})


#==========================================================
# No outliers
#==========================================================

test_that("outlier_check identifies data without outliers", {

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

  result <- outlier_check(dat)

  expect_equal(
    result$Outliers,
    0L
  )

  expect_false(
    result$HasOutlier
  )

})


#==========================================================
# Upper outlier
#==========================================================

test_that("outlier_check detects upper outlier", {

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

  result <- outlier_check(dat)

  expect_equal(
    result$UpperOutliers,
    1L
  )

  expect_equal(
    result$LowerOutliers,
    0L
  )

  expect_equal(
    result$Outliers,
    1L
  )

  expect_true(
    result$HasOutlier
  )

})


#==========================================================
# Lower outlier
#==========================================================

test_that("outlier_check detects lower outlier", {

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

  result <- outlier_check(dat)

  expect_equal(
    result$LowerOutliers,
    1L
  )

  expect_equal(
    result$UpperOutliers,
    0L
  )

  expect_equal(
    result$Outliers,
    1L
  )

})


#==========================================================
# Both directions
#==========================================================

test_that("outlier_check detects lower and upper outliers", {

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

  result <- outlier_check(dat)

  expect_equal(
    result$LowerOutliers,
    1L
  )

  expect_equal(
    result$UpperOutliers,
    1L
  )

  expect_equal(
    result$Outliers,
    2L
  )

})


#==========================================================
# Constant variable
#==========================================================

test_that("outlier_check handles constant variable", {

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

  result <- outlier_check(dat)

  expect_equal(
    result$IQR,
    0
  )

  expect_equal(
    result$LowerBound,
    5
  )

  expect_equal(
    result$UpperBound,
    5
  )

  expect_equal(
    result$Outliers,
    0L
  )

  expect_false(
    result$HasOutlier
  )

})


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

test_that("outlier_check excludes missing values", {

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

  result <- outlier_check(dat)

  expect_equal(
    result$Missing,
    2L
  )

  expect_equal(
    result$Observed,
    21L
  )

  expect_equal(
    result$Outliers,
    1L
  )

})


#==========================================================
# Infinite values
#==========================================================

test_that("outlier_check excludes infinite values", {

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

  result <- outlier_check(dat)

  expect_equal(
    result$Infinite,
    2L
  )

  expect_equal(
    result$Observed,
    10L
  )

  expect_equal(
    result$Outliers,
    0L
  )

})


#==========================================================
# No finite observations
#==========================================================

test_that("outlier_check handles all missing numeric data", {

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

  result <- outlier_check(dat)

  expect_equal(
    result$Observed,
    0L
  )

  expect_equal(
    result$Missing,
    5L
  )

  expect_true(
    is.na(result$Q1)
  )

  expect_true(
    is.na(result$Q3)
  )

  expect_true(
    is.na(result$IQR)
  )

  expect_equal(
    result$Outliers,
    0L
  )

  expect_false(
    result$HasOutlier
  )

})

test_that("outlier_check handles only infinite values", {

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

  result <- outlier_check(dat)

  expect_equal(
    result$Observed,
    0L
  )

  expect_equal(
    result$Infinite,
    2L
  )

  expect_true(
    is.na(result$LowerBound)
  )

  expect_true(
    is.na(result$UpperBound)
  )

  expect_true(
    is.na(result$OutlierPercent)
  )

})


#==========================================================
# Non-numeric variables
#==========================================================

test_that("outlier_check handles character variables", {

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

  result <- outlier_check(dat)

  expect_false(
    result$Numeric
  )

  expect_true(
    is.na(result$Outliers)
  )

  expect_false(
    result$HasOutlier
  )

})

test_that("outlier_check handles factor variables", {

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

  result <- outlier_check(dat)

  expect_false(
    result$Numeric
  )

  expect_true(
    is.na(result$IQR)
  )

})

test_that("outlier_check handles logical variables", {

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

  result <- outlier_check(dat)

  expect_false(
    result$Numeric
  )

  expect_true(
    is.na(result$Outliers)
  )

})


#==========================================================
# Date variables
#==========================================================

test_that("outlier_check does not treat Date as numeric", {

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

  result <- outlier_check(dat)

  expect_false(
    result$Numeric
  )

  expect_true(
    is.na(result$Outliers)
  )

})


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

test_that("outlier_check does not treat POSIXct as numeric", {

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

  result <- outlier_check(dat)

  expect_false(
    result$Numeric
  )

})


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

test_that("outlier_check handles mixed data", {

  dat <- data.frame(
    normal = 1:21,
    upper = c(
      rep(10, 20),
      100
    ),
    group = rep(
      "A",
      21
    ),
    stringsAsFactors = FALSE
  )

  result <- outlier_check(dat)

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

  expect_equal(
    result$Outliers,
    c(
      0L,
      1L,
      NA_integer_
    )
  )

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

})


#==========================================================
# Percentage
#==========================================================

test_that("outlier_check calculates percentage correctly", {

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

  result <- outlier_check(dat)

  expect_equal(
    result$OutlierPercent,
    4.76
  )

})


#==========================================================
# Custom k
#==========================================================

test_that("outlier_check accepts custom k", {

  dat <- data.frame(
    x = c(
      1:10,
      20
    )
  )

  result1 <- outlier_check(
    dat,
    k = 1.5
  )

  result2 <- outlier_check(
    dat,
    k = 3
  )

  expect_true(
    result1$Outliers >= result2$Outliers
  )

})

test_that("smaller k can identify more outliers", {

  dat <- data.frame(
    x = c(
      1:10,
      15
    )
  )

  result1 <- outlier_check(
    dat,
    k = 0.5
  )

  result2 <- outlier_check(
    dat,
    k = 3
  )

  expect_true(
    result1$Outliers >= result2$Outliers
  )

})


#==========================================================
# k validation
#==========================================================

test_that("outlier_check rejects character k", {

  expect_error(
    outlier_check(
      iris,
      k = "1.5"
    ),
    "'k' must be a single finite numeric value"
  )

})

test_that("outlier_check rejects vector k", {

  expect_error(
    outlier_check(
      iris,
      k = c(1.5, 3)
    ),
    "'k' must be a single finite numeric value"
  )

})

test_that("outlier_check rejects NA k", {

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

})

test_that("outlier_check rejects infinite k", {

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

})

test_that("outlier_check rejects zero k", {

  expect_error(
    outlier_check(
      iris,
      k = 0
    ),
    "'k' must be greater than zero"
  )

})

test_that("outlier_check rejects negative k", {

  expect_error(
    outlier_check(
      iris,
      k = -1
    ),
    "'k' must be greater than zero"
  )

})


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

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

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

  result <- outlier_check(dat)

  expect_s3_class(
    result,
    "OutlierCheck"
  )

  expect_equal(
    nrow(result),
    2L
  )

  expect_equal(
    result$Observed[1],
    0L
  )

  expect_true(
    is.na(result$Observed[2])
  )

  expect_false(
    any(result$HasOutlier)
  )

})


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

test_that("outlier_check handles tibble", {

  skip_if_not_installed("tibble")

  dat <- tibble::tibble(
    x = c(
      rep(10, 20),
      100
    ),
    group = rep(
      "A",
      21
    )
  )

  result <- outlier_check(dat)

  expect_s3_class(
    result,
    "OutlierCheck"
  )

  expect_equal(
    result$Outliers[1],
    1L
  )

})


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

test_that("outlier_check rejects NULL", {

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

})

test_that("outlier_check rejects numeric vector", {

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

})

test_that("outlier_check rejects character vector", {

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

})

test_that("outlier_check rejects list", {

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

})

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

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

})
