library(testthat)
library(DataAudit)

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

test_that("dependency_check returns correct class", {

  dat <- data.frame(
    Status = c("Yes", "No"),
    Result = c("Positive", NA)
  )

  result <- dependency_check(
    dat,
    "Status",
    "Yes",
    "Result"
  )

  expect_s3_class(
    result,
    "DependencyCheck"
  )

  expect_s3_class(
    result,
    "data.frame"
  )
})

test_that("dependency_check returns expected columns", {

  dat <- data.frame(
    Status = "Yes",
    Result = "Positive"
  )

  result <- dependency_check(
    dat,
    "Status",
    "Yes",
    "Result"
  )

  expect_equal(
    names(result),
    c(
      "Row",
      "ConditionValue",
      "TargetValue",
      "ConditionMissing",
      "ConditionMet",
      "TargetMissing",
      "TargetBlank",
      "DependencyViolation",
      "ValidDependency"
    )
  )
})

#==========================================================
# Condition activation
#==========================================================

test_that("dependency_check identifies activated rows", {

  dat <- data.frame(
    Status = c(
      "Positive",
      "Negative",
      "Positive"
    ),
    Result = c(
      "Detected",
      NA,
      "Detected"
    )
  )

  result <- dependency_check(
    dat,
    "Status",
    "Positive",
    "Result"
  )

  expect_equal(
    result$ConditionMet,
    c(TRUE, FALSE, TRUE)
  )
})

#==========================================================
# Missing target
#==========================================================

test_that("dependency_check detects missing required target", {

  dat <- data.frame(
    Status = c(
      "Positive",
      "Negative",
      "Positive"
    ),
    Result = c(
      "Detected",
      NA,
      NA
    )
  )

  result <- dependency_check(
    dat,
    "Status",
    "Positive",
    "Result"
  )

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

  expect_equal(
    result$ValidDependency,
    c(TRUE, TRUE, FALSE)
  )
})

test_that("missing target is allowed when condition is not met", {

  dat <- data.frame(
    Status = c(
      "Negative",
      "Negative"
    ),
    Result = c(
      NA,
      NA
    )
  )

  result <- dependency_check(
    dat,
    "Status",
    "Positive",
    "Result"
  )

  expect_false(
    any(result$DependencyViolation)
  )

  expect_true(
    all(result$ValidDependency)
  )
})

#==========================================================
# Blank target
#==========================================================

test_that("blank target violates dependency by default", {

  dat <- data.frame(
    Status = c(
      "Yes",
      "Yes",
      "Yes"
    ),
    Result = c(
      "",
      "   ",
      "Available"
    )
  )

  result <- dependency_check(
    dat,
    "Status",
    "Yes",
    "Result"
  )

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

  expect_equal(
    result$DependencyViolation,
    c(TRUE, TRUE, FALSE)
  )
})

test_that("blank target can be allowed", {

  dat <- data.frame(
    Status = c(
      "Yes",
      "Yes"
    ),
    Result = c(
      "",
      " "
    )
  )

  result <- dependency_check(
    dat,
    "Status",
    "Yes",
    "Result",
    allow_blank = TRUE
  )

  expect_false(
    any(result$DependencyViolation)
  )

  expect_true(
    all(result$ValidDependency)
  )
})

#==========================================================
# Missing condition
#==========================================================

test_that("missing condition does not activate dependency", {

  dat <- data.frame(
    Status = c(
      NA,
      "Yes",
      "No"
    ),
    Result = c(
      NA,
      "Available",
      NA
    )
  )

  result <- dependency_check(
    dat,
    "Status",
    "Yes",
    "Result"
  )

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

  expect_false(
    result$ConditionMet[1]
  )

  expect_false(
    result$DependencyViolation[1]
  )
})

test_that("all missing conditions are handled", {

  dat <- data.frame(
    Status = c(
      NA_character_,
      NA_character_
    ),
    Result = c(
      NA_character_,
      NA_character_
    )
  )

  result <- dependency_check(
    dat,
    "Status",
    "Yes",
    "Result"
  )

  expect_true(
    all(result$ConditionMissing)
  )

  expect_false(
    any(result$ConditionMet)
  )

  expect_false(
    any(result$DependencyViolation)
  )
})

#==========================================================
# Numeric condition
#==========================================================

test_that("dependency_check supports numeric conditions", {

  dat <- data.frame(
    Disease = c(1, 0, 1),
    Result = c(
      "Positive",
      NA,
      NA
    )
  )

  result <- dependency_check(
    dat,
    "Disease",
    1,
    "Result"
  )

  expect_equal(
    result$DependencyViolation,
    c(FALSE, FALSE, TRUE)
  )
})

#==========================================================
# Logical condition
#==========================================================

test_that("dependency_check supports logical conditions", {

  dat <- data.frame(
    Tested = c(
      TRUE,
      FALSE,
      TRUE
    ),
    Result = c(
      "Positive",
      NA,
      NA
    )
  )

  result <- dependency_check(
    dat,
    "Tested",
    TRUE,
    "Result"
  )

  expect_equal(
    result$DependencyViolation,
    c(FALSE, FALSE, TRUE)
  )
})

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

test_that("dependency_check supports factor condition", {

  dat <- data.frame(
    Status = factor(
      c(
        "Yes",
        "No",
        "Yes"
      )
    ),
    Result = c(
      "Available",
      NA,
      NA
    )
  )

  result <- dependency_check(
    dat,
    "Status",
    "Yes",
    "Result"
  )

  expect_equal(
    result$DependencyViolation,
    c(FALSE, FALSE, TRUE)
  )
})

test_that("dependency_check detects blanks in factor target", {

  dat <- data.frame(
    Status = c(
      "Yes",
      "Yes"
    ),
    Result = factor(
      c(
        "",
        "Available"
      )
    )
  )

  result <- dependency_check(
    dat,
    "Status",
    "Yes",
    "Result"
  )

  expect_equal(
    result$TargetBlank,
    c(TRUE, FALSE)
  )
})

#==========================================================
# Numeric target
#==========================================================

test_that("numeric target does not use blank detection", {

  dat <- data.frame(
    Status = c(
      "Yes",
      "Yes"
    ),
    Result = c(
      10,
      NA
    )
  )

  result <- dependency_check(
    dat,
    "Status",
    "Yes",
    "Result"
  )

  expect_false(
    any(result$TargetBlank)
  )

  expect_equal(
    result$DependencyViolation,
    c(FALSE, TRUE)
  )
})

#==========================================================
# Required arguments
#==========================================================

test_that("condition_variable is required", {

  dat <- data.frame(
    Status = "Yes",
    Result = "Available"
  )

  expect_error(
    dependency_check(
      dat,
      condition_value = "Yes",
      target_variable = "Result"
    ),
    "'condition_variable' must be supplied"
  )
})

test_that("condition_value is required", {

  dat <- data.frame(
    Status = "Yes",
    Result = "Available"
  )

  expect_error(
    dependency_check(
      dat,
      condition_variable = "Status",
      target_variable = "Result"
    ),
    "'condition_value' must be supplied"
  )
})

test_that("target_variable is required", {

  dat <- data.frame(
    Status = "Yes",
    Result = "Available"
  )

  expect_error(
    dependency_check(
      dat,
      condition_variable = "Status",
      condition_value = "Yes"
    ),
    "'target_variable' must be supplied"
  )
})

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

test_that("condition_variable is validated", {

  dat <- data.frame(
    Status = "Yes",
    Result = "Available"
  )

  expect_error(
    dependency_check(
      dat,
      NULL,
      "Yes",
      "Result"
    ),
    "'condition_variable' must be a single non-empty character string"
  )

  expect_error(
    dependency_check(
      dat,
      "",
      "Yes",
      "Result"
    ),
    "'condition_variable' must be a single non-empty character string"
  )

  expect_error(
    dependency_check(
      dat,
      1,
      "Yes",
      "Result"
    ),
    "'condition_variable' must be a single non-empty character string"
  )

  expect_error(
    dependency_check(
      dat,
      c("Status", "Result"),
      "Yes",
      "Result"
    ),
    "'condition_variable' must be a single non-empty character string"
  )

  expect_error(
    dependency_check(
      dat,
      NA_character_,
      "Yes",
      "Result"
    ),
    "'condition_variable' must be a single non-empty character string"
  )
})

test_that("target_variable is validated", {

  dat <- data.frame(
    Status = "Yes",
    Result = "Available"
  )

  expect_error(
    dependency_check(
      dat,
      "Status",
      "Yes",
      NULL
    ),
    "'target_variable' must be a single non-empty character string"
  )

  expect_error(
    dependency_check(
      dat,
      "Status",
      "Yes",
      ""
    ),
    "'target_variable' must be a single non-empty character string"
  )
})

test_that("unknown variables are rejected", {

  dat <- data.frame(
    Status = "Yes",
    Result = "Available"
  )

  expect_error(
    dependency_check(
      dat,
      "Unknown",
      "Yes",
      "Result"
    ),
    "Variable 'Unknown' was not found in 'data'"
  )

  expect_error(
    dependency_check(
      dat,
      "Status",
      "Yes",
      "Unknown"
    ),
    "Variable 'Unknown' was not found in 'data'"
  )
})

test_that("condition and target variables must differ", {

  dat <- data.frame(
    Status = "Yes"
  )

  expect_error(
    dependency_check(
      dat,
      "Status",
      "Yes",
      "Status"
    ),
    "must refer to different variables"
  )
})

#==========================================================
# condition_value validation
#==========================================================

test_that("condition_value must be scalar and non-missing", {

  dat <- data.frame(
    Status = "Yes",
    Result = "Available"
  )

  expect_error(
    dependency_check(
      dat,
      "Status",
      NA_character_,
      "Result"
    ),
    "'condition_value' must contain exactly one non-missing value"
  )

  expect_error(
    dependency_check(
      dat,
      "Status",
      c("Yes", "No"),
      "Result"
    ),
    "'condition_value' must contain exactly one non-missing value"
  )
})

#==========================================================
# allow_blank validation
#==========================================================

test_that("allow_blank must be logical scalar", {

  dat <- data.frame(
    Status = "Yes",
    Result = "Available"
  )

  expect_error(
    dependency_check(
      dat,
      "Status",
      "Yes",
      "Result",
      allow_blank = 1
    ),
    "'allow_blank' must be TRUE or FALSE"
  )

  expect_error(
    dependency_check(
      dat,
      "Status",
      "Yes",
      "Result",
      allow_blank = NA
    ),
    "'allow_blank' must be TRUE or FALSE"
  )

  expect_error(
    dependency_check(
      dat,
      "Status",
      "Yes",
      "Result",
      allow_blank = c(TRUE, FALSE)
    ),
    "'allow_blank' must be TRUE or FALSE"
  )
})

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

test_that("dependency_check rejects invalid data", {

  expect_error(
    dependency_check(
      NULL,
      "Status",
      "Yes",
      "Result"
    )
  )

  expect_error(
    dependency_check(
      1:5,
      "Status",
      "Yes",
      "Result"
    )
  )

  expect_error(
    dependency_check(
      list(
        Status = "Yes",
        Result = "Available"
      ),
      "Status",
      "Yes",
      "Result"
    )
  )

  expect_error(
    dependency_check(
      data.frame(),
      "Status",
      "Yes",
      "Result"
    )
  )
})
