library(testthat)
library(DataAudit)

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

test_that("type_check returns TypeCheck", {

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

  result <- type_check(
    dat,
    c(x = "integer")
  )

  expect_s3_class(
    result,
    "TypeCheck"
  )

})

test_that("type_check returns data.frame", {

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

  result <- type_check(
    dat,
    c(x = "integer")
  )

  expect_s3_class(
    result,
    "data.frame"
  )

})

test_that("type_check returns expected columns", {

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

  result <- type_check(
    dat,
    c(x = "integer")
  )

  expect_equal(
    names(result),
    c(
      "Variable",
      "Expected",
      "Actual",
      "Match"
    )
  )

})


#==========================================================
# Integer
#==========================================================

test_that("type_check identifies integer", {

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

  result <- type_check(
    dat,
    c(x = "integer")
  )

  expect_equal(
    result$Actual,
    "integer"
  )

  expect_true(
    result$Match
  )

})


#==========================================================
# Numeric
#==========================================================

test_that("type_check identifies numeric", {

  dat <- data.frame(
    x = c(
      1.2,
      2.4,
      3.6
    )
  )

  result <- type_check(
    dat,
    c(x = "numeric")
  )

  expect_equal(
    result$Actual,
    "numeric"
  )

  expect_true(
    result$Match
  )

})

test_that("integer satisfies numeric expectation", {

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

  result <- type_check(
    dat,
    c(x = "numeric")
  )

  expect_true(
    result$Match
  )

})


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

test_that("type_check identifies character", {

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

  result <- type_check(
    dat,
    c(x = "character")
  )

  expect_equal(
    result$Actual,
    "character"
  )

  expect_true(
    result$Match
  )

})


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

test_that("type_check identifies factor", {

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

  result <- type_check(
    dat,
    c(x = "factor")
  )

  expect_equal(
    result$Actual,
    "factor"
  )

  expect_true(
    result$Match
  )

})


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

test_that("type_check identifies logical", {

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

  result <- type_check(
    dat,
    c(x = "logical")
  )

  expect_equal(
    result$Actual,
    "logical"
  )

  expect_true(
    result$Match
  )

})


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

test_that("type_check identifies Date", {

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

  result <- type_check(
    dat,
    c(x = "Date")
  )

  expect_equal(
    result$Actual,
    "Date"
  )

  expect_true(
    result$Match
  )

})


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

test_that("type_check identifies POSIXct", {

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

  result <- type_check(
    dat,
    c(x = "POSIXct")
  )

  expect_equal(
    result$Actual,
    "POSIXct"
  )

  expect_true(
    result$Match
  )

})


#==========================================================
# Type mismatch
#==========================================================

test_that("type_check identifies mismatch", {

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

  result <- type_check(
    dat,
    c(x = "numeric")
  )

  expect_equal(
    result$Actual,
    "character"
  )

  expect_false(
    result$Match
  )

})


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

test_that("type_check checks multiple variables", {

  dat <- data.frame(
    ID = 1:3,
    Weight = c(
      20.5,
      25.2,
      30.1
    ),
    Species = factor(
      c(
        "Dog",
        "Cat",
        "Dog"
      )
    ),
    Positive = c(
      TRUE,
      FALSE,
      TRUE
    )
  )

  result <- type_check(
    dat,
    c(
      ID = "integer",
      Weight = "numeric",
      Species = "factor",
      Positive = "logical"
    )
  )

  expect_equal(
    result$Variable,
    c(
      "ID",
      "Weight",
      "Species",
      "Positive"
    )
  )

  expect_true(
    all(result$Match)
  )

})


#==========================================================
# Mixed matches
#==========================================================

test_that("type_check returns mixed matches correctly", {

  dat <- data.frame(
    Age = c(
      1,
      2,
      3
    ),
    Species = c(
      "Dog",
      "Cat",
      "Dog"
    ),
    stringsAsFactors = FALSE
  )

  result <- type_check(
    dat,
    c(
      Age = "character",
      Species = "character"
    )
  )

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

})


#==========================================================
# Missing values do not alter type
#==========================================================

test_that("type_check handles missing values", {

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

  result <- type_check(
    dat,
    c(x = "numeric")
  )

  expect_true(
    result$Match
  )

})


#==========================================================
# All missing typed vectors
#==========================================================

test_that("type_check handles all NA numeric", {

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

  result <- type_check(
    dat,
    c(x = "numeric")
  )

  expect_equal(
    result$Actual,
    "numeric"
  )

  expect_true(
    result$Match
  )

})

test_that("type_check handles all NA character", {

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

  result <- type_check(
    dat,
    c(x = "character")
  )

  expect_equal(
    result$Actual,
    "character"
  )

  expect_true(
    result$Match
  )

})


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

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

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

  result <- type_check(
    dat,
    c(
      x = "numeric",
      y = "character"
    )
  )

  expect_equal(
    nrow(result),
    2L
  )

  expect_true(
    all(result$Match)
  )

})


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

test_that("type_check handles tibble", {

  skip_if_not_installed("tibble")

  dat <- tibble::tibble(
    x = 1:5,
    y = c(
      "A",
      "B",
      "C",
      "D",
      "E"
    )
  )

  result <- type_check(
    dat,
    c(
      x = "integer",
      y = "character"
    )
  )

  expect_s3_class(
    result,
    "TypeCheck"
  )

  expect_true(
    all(result$Match)
  )

})


#==========================================================
# expected validation
#==========================================================

test_that("type_check rejects missing expected", {

  expect_error(
    type_check(iris),
    "'expected' must be a non-empty named character vector"
  )

})

test_that("type_check rejects NULL expected", {

  expect_error(
    type_check(
      iris,
      NULL
    ),
    "'expected' must be a non-empty named character vector"
  )

})

test_that("type_check rejects numeric expected", {

  expect_error(
    type_check(
      iris,
      c(Species = 1)
    ),
    "'expected' must be a non-empty named character vector"
  )

})

test_that("type_check rejects empty expected", {

  expect_error(
    type_check(
      iris,
      character(0)
    ),
    "'expected' must be a non-empty named character vector"
  )

})

test_that("type_check rejects unnamed expected", {

  expect_error(
    type_check(
      iris,
      "numeric"
    ),
    "'expected' must have variable names"
  )

})

test_that("type_check rejects blank expected name", {

  expected <- c(
    "numeric"
  )

  names(expected) <- ""

  expect_error(
    type_check(
      iris,
      expected
    ),
    "'expected' must have variable names"
  )

})

test_that("type_check rejects NA expected name", {

  expected <- c(
    "numeric"
  )

  names(expected) <- NA_character_

  expect_error(
    type_check(
      iris,
      expected
    ),
    "'expected' must have variable names"
  )

})

test_that("type_check rejects duplicate variable names", {

  expected <- c(
    "numeric",
    "numeric"
  )

  names(expected) <- c(
    "Sepal.Length",
    "Sepal.Length"
  )

  expect_error(
    type_check(
      iris,
      expected
    ),
    "Variable names in 'expected' must be unique"
  )

})


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

test_that("type_check rejects unsupported type", {

  expect_error(
    type_check(
      iris,
      c(
        Sepal.Length = "doubleXYZ"
      )
    ),
    "Unsupported expected type"
  )

})

test_that("type_check reports multiple unsupported types", {

  expect_error(
    type_check(
      iris,
      c(
        Sepal.Length = "doubleXYZ",
        Sepal.Width = "unknownXYZ"
      )
    ),
    "Unsupported expected type"
  )

})


#==========================================================
# Unknown variables
#==========================================================

test_that("type_check rejects unknown variable", {

  expect_error(
    type_check(
      iris,
      c(
        Unknown = "numeric"
      )
    ),
    "Variable\\(s\\) not found in 'data': Unknown"
  )

})

test_that("type_check reports multiple unknown variables", {

  expect_error(
    type_check(
      iris,
      c(
        Unknown1 = "numeric",
        Unknown2 = "character"
      )
    ),
    "Variable\\(s\\) not found in 'data'"
  )

})


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

test_that("type_check rejects NULL data", {

  expect_error(
    type_check(
      NULL,
      c(x = "numeric")
    ),
    "'data' cannot be NULL"
  )

})

test_that("type_check rejects vector data", {

  expect_error(
    type_check(
      1:10,
      c(x = "numeric")
    ),
    "'data' must be a data.frame"
  )

})

test_that("type_check rejects list data", {

  expect_error(
    type_check(
      list(x = 1:5),
      c(x = "numeric")
    ),
    "'data' must be a data.frame"
  )

})

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

  expect_error(
    type_check(
      data.frame(),
      c(x = "numeric")
    ),
    "at least one variable"
  )

})

#==========================================================
# Other actual variable classes
#==========================================================

test_that("type_check reports unsupported actual class", {

  dat <- data.frame(
    z = c(
      1 + 2i,
      3 + 4i,
      5 + 6i
    )
  )

  result <- type_check(
    dat,
    expected = c(
      z = "numeric"
    )
  )

  expect_true(
    is.data.frame(result)
  )

  expect_equal(
    result$Actual,
    "complex"
  )

  expect_false(
    result$Match
  )

})
