test_that("four_pl evaluates the 4PL model", {
  # At x = ic50 the response is the midpoint of top and bottom
  expect_equal(four_pl(1, top = 1, ic50 = 1, hill = 1, bottom = 0), 0.5)

  # Vectorised input returns the same length
  x <- c(0.01, 0.1, 1, 10)
  out <- four_pl(x, top = 1, ic50 = 1, hill = 1, bottom = 0)
  expect_length(out, length(x))
  # monotonic decreasing for a killing curve (top > bottom)
  expect_true(all(diff(out) < 0))
})

test_that("four_pl rejects non-numeric x", {
  expect_error(four_pl("a", top = 1, ic50 = 1, hill = 1, bottom = 0),
    "must be numeric")
})

test_that("compute_auc integrates the 4PL curve", {
  # integral of 1/(1+x) from 0.01 to 10 = ln(11) - ln(1.01)
  expected <- log(11) - log(1.01)
  expect_equal(
    compute_auc(0.01, 10, top = 1, ic50 = 1, hill = 1, bottom = 0),
    expected,
    tolerance = 1e-6
  )
})

test_that("compute_auc errors when bounds are inverted", {
  expect_error(
    compute_auc(10, 1, top = 1, ic50 = 1, hill = 1, bottom = 0),
    "must be smaller"
  )
})

test_that("fit_dose_response fits a 4PL without external packages", {
  one <- subset(
    screen_doseresponse,
    drug_name == "DrugA" & condition == "Gy0" & experiment_type == "sample"
  )
  fit <- fit_dose_response(one, "normalized_cell_count", "concentration")

  expect_s3_class(fit, "dsr_4pl")
  cf <- coef(fit)
  expect_named(cf, c("top", "ic50", "hill", "bottom"))
  expect_length(cf, 4)

  # A killing curve: top (no drug) should exceed bottom (saturating drug)
  expect_gt(cf[["top"]], cf[["bottom"]])
  # IC50 should sit within the tested concentration range
  expect_gte(cf[["ic50"]], min(one$concentration))
  expect_lte(cf[["ic50"]], max(one$concentration))

  # S3 helpers behave consistently
  expect_length(fitted(fit), nrow(fit$data))
  expect_equal(residuals(fit), fit$data$response - fitted(fit))
  expect_equal(
    predict(fit, newdata = fit$data$concentration), fitted(fit),
    tolerance = 1e-8
  )
  expect_output(print(fit), "logistic fit")
})

test_that("fit_dose_response handles factor/character concentration columns", {
  one <- subset(
    screen_doseresponse,
    drug_name == "DrugA" & condition == "Gy0" & experiment_type == "sample"
  )
  num_fit <- fit_dose_response(one, "normalized_cell_count", "concentration")

  # Concentration stored as a factor (a common raw-data format). Naive
  # as.numeric() would return level codes 1..5 and wreck the fit; the function
  # must recover the real doses and match the numeric fit.
  one_factor <- one
  one_factor$concentration <- factor(as.character(one_factor$concentration))
  fac_fit <- fit_dose_response(one_factor, "normalized_cell_count", "concentration")
  expect_equal(coef(fac_fit), coef(num_fit), tolerance = 1e-6)

  # Concentration stored as character behaves identically.
  one_chr <- one
  one_chr$concentration <- as.character(one_chr$concentration)
  chr_fit <- fit_dose_response(one_chr, "normalized_cell_count", "concentration")
  expect_equal(coef(chr_fit), coef(num_fit), tolerance = 1e-6)
})

test_that("fit_dose_response errors on too few observations", {
  expect_error(
    fit_dose_response(
      data.frame(
        normalized_cell_count = c(1, 0.5, 0.1),
        concentration = c(0.1, 1, 10)
      ),
      "normalized_cell_count", "concentration"
    ),
    "At least 4"
  )
})

test_that("fit_dose_response errors on a missing column", {
  expect_error(
    fit_dose_response(
      data.frame(x = 1:5, concentration = 1:5),
      "normalized_cell_count", "concentration"
    ),
    "not found"
  )
})
