# Los tests usan pocas iteraciones para mantenerse rápidos; comprueban
# estructura, dimensiones y coherencia de los resultados, no valores exactos
# de p-valores.

data(grants)
covs <- c("HSTech", "hsgrade", "Y2005")

new_rdo <- function(covariates = covs) {
  RegressionDiscontinuityClass$new(
    data = grants, forcing = "S", threshold = 15000,
    Z1S1 = "lower", covariates = covariates
  )
}

test_that("RegressionDiscontinuityClass is created correctly", {
  rdo <- new_rdo()
  expect_s3_class(rdo, "RegressionDiscontinuityClass")
  expect_s3_class(rdo, "R6")
  expect_equal(rdo$forcing, "S")
  expect_equal(rdo$threshold, 15000)
  expect_equal(rdo$Z1S1, "lower")
  expect_equal(rdo$covariates, covs)
})

test_that("initialize validates its inputs", {
  expect_error(RegressionDiscontinuityClass$new(
    data = "not a data frame", forcing = "S", threshold = 15000,
    Z1S1 = "lower", covariates = covs
  ))
  expect_error(RegressionDiscontinuityClass$new(
    data = grants, forcing = "S", threshold = "not numeric",
    Z1S1 = "lower", covariates = covs
  ))
})

test_that("summary_statistics_mean returns means by assignment status", {
  rdo <- new_rdo()
  res <- rdo$summary_statistics_mean()
  expect_s3_class(res, "data.frame")
  expect_named(res, c("Name", "All", "Z=0", "Z=1"))
  # una fila por covariable + la fila N
  expect_equal(nrow(res), length(covs) + 1)
  nrow_line <- res[res$Name == "N", ]
  expect_equal(as.numeric(nrow_line$All), nrow(grants))
  expect_equal(as.numeric(nrow_line$`Z=0`) + as.numeric(nrow_line$`Z=1`),
               nrow(grants))
})

test_that("summary_statistics_mean works with a single covariate", {
  rdo <- new_rdo(covariates = "hsgrade")
  res <- rdo$summary_statistics_mean()
  expect_s3_class(res, "data.frame")
  expect_equal(nrow(res), 2) # covariable + fila N
  expect_equal(res$Name[1], "hsgrade")
})

test_that("bandwidth_selection returns range info, p-values and plot", {
  set.seed(27)
  rdo <- new_rdo()
  res <- rdo$bandwidth_selection(typerange = "percentage", range_value = 25,
                                 num_it = 20, plot = TRUE)
  expect_type(res, "list")
  expect_named(res, c("RangeInfo", "pvalues", "RangePlot"))
  expect_s3_class(res$RangePlot, "ggplot")
  expect_equal(nrow(res$pvalues), length(covs))
  # los p-valores estan en [0, 1]
  pv <- unlist(res$pvalues[, -1])
  expect_true(all(pv >= 0 & pv <= 1))
  # sin plot no se devuelve RangePlot
  res2 <- rdo$bandwidth_selection(typerange = "percentage", range_value = 25,
                                  num_it = 20, plot = FALSE)
  expect_named(res2, c("RangeInfo", "pvalues"))
})

test_that("summary_bandwidth returns a p-value per covariate and bandwidth", {
  set.seed(27)
  rdo <- new_rdo()
  bws <- c(1000, 2000)
  res <- rdo$summary_bandwidth(buffers = bws, num_it = 20)
  expect_s3_class(res, "data.frame")
  expect_equal(nrow(res), length(covs))
  # una columna de nombres + una por bandwidth
  expect_equal(ncol(res), 1 + length(bws))
})

test_that("causal_effect Sharp FEP returns table and plot", {
  set.seed(27)
  rdo <- new_rdo()
  res <- rdo$causal_effect(method = "Sharp FEP", buffers = c(1000, 2000),
                           num_it = 20, outcome = "dropout",
                           typeoutcome = "binary", plot = TRUE)
  expect_type(res, "list")
  expect_named(res, c("table", "plot"))
  expect_equal(nrow(res$table), 2)
  expect_s3_class(res$plot, "ggplot")
  pvals <- res$table[, ncol(res$table)]
  expect_true(all(pvals >= 0 & pvals <= 1))
})

test_that("causal_effect Sharp Neyman works with and without plot", {
  set.seed(27)
  rdo <- new_rdo()
  # regresion del bug: plot = FALSE fallaba con 'tau3 not found'
  res <- rdo$causal_effect(method = "Sharp Neyman", buffers = c(1000, 2000),
                           num_it = 20, outcome = "dropout",
                           typeoutcome = "binary", cin = 0.05, plot = FALSE)
  expect_s3_class(res, "data.frame")
  expect_equal(nrow(res), 2)

  res2 <- rdo$causal_effect(method = "Sharp Neyman", buffers = c(1000, 2000),
                            num_it = 20, outcome = "dropout",
                            typeoutcome = "binary", cin = 0.05, plot = TRUE)
  expect_named(res2, c("table", "plot"))
  # el intervalo contiene la estimacion puntual
  expect_true(all(res2$table$L <= res2$table$ACE &
                  res2$table$ACE <= res2$table$U))
})

test_that("causal_effect Fuzzy Neyman returns ITT and CACE estimands", {
  set.seed(27)
  rdo <- new_rdo()
  res <- rdo$causal_effect(method = "Fuzzy Neyman", buffers = c(1000, 2000),
                           num_it = 20, outcome = "dropout",
                           typeoutcome = "binary", treatm_cov = "W",
                           plot = TRUE)
  expect_named(res, c("table", "plot"))
  expect_true("Estimand" %in% names(res$table))
  expect_gte(length(unique(res$table$Estimand)), 2)
})

test_that("causal_effect Fuzzy FEP (two-sided) returns the three statistics", {
  set.seed(27)
  rdo <- new_rdo()
  res <- rdo$causal_effect(method = "Fuzzy FEP", buffers = c(1000),
                           num_it = 10, outcome = "dropout",
                           typeoutcome = "binary", sided = "twosided",
                           treatm_cov = "W", num_it_fuzzy = 3,
                           plot = FALSE)
  expect_s3_class(res, "data.frame")
  expect_equal(nrow(res), 1)
  expect_true(all(c("Statistic: IV estimate of CACE",
                    "Statistic: MLE of CACE",
                    "Statistic: Posterior median of CACE")
                  %in% names(res)))
})

test_that("grants dataset has the documented structure", {
  expect_s3_class(grants, "data.frame")
  expect_equal(dim(grants), c(15984, 21))
  expect_true(all(c("S", "Z", "A", "W", "dropout") %in% names(grants)))
  expect_true(all(grants$dropout %in% c(0, 1)))
  expect_true(all(grants$Z %in% c(0, 1)))
})

test_that("Neyman confidence intervals have the correct width (cin = alpha)", {
  set.seed(27)
  rdo <- new_rdo()
  # Sharp Neyman
  rs <- rdo$causal_effect(method = "Sharp Neyman", buffers = c(1000),
                          num_it = 10, outcome = "dropout",
                          typeoutcome = "binary", cin = 0.05, plot = FALSE)
  zz <- qnorm(1 - 0.05 / 2)
  expect_equal(rs[[6]] - rs[[5]], 2 * zz * rs[[4]], tolerance = 0.05)
  expect_true(grepl("^95%", names(rs)[5]))
  # Fuzzy Neyman (regresion: antes producia IC de anchura casi nula)
  rf <- rdo$causal_effect(method = "Fuzzy Neyman", buffers = c(1000),
                          num_it = 10, outcome = "dropout",
                          typeoutcome = "binary", treatm_cov = "W",
                          cin = 0.05, plot = FALSE)
  cace <- rf[rf$Estimand == "CACE", ]
  expect_equal(cace[[6]] - cace[[5]], 2 * zz * cace[[4]], tolerance = 0.05)
})

test_that("Fuzzy Neyman uses the treatm_cov argument (not a hard-coded column)", {
  set.seed(27)
  g2 <- grants
  names(g2)[names(g2) == "W"] <- "treat_received"
  rdo2 <- RegressionDiscontinuityClass$new(
    data = g2, forcing = "S", threshold = 15000,
    Z1S1 = "lower", covariates = "hsgrade"
  )
  # regresion: antes usaba self$W ("W") ignorando treatm_cov y fallaba
  expect_no_error(
    rdo2$causal_effect(method = "Fuzzy Neyman", buffers = c(1000),
                       num_it = 10, outcome = "dropout",
                       typeoutcome = "binary", treatm_cov = "treat_received",
                       plot = FALSE)
  )
})
