test_that("quantile_pred error types", {
  expect_snapshot(
    error = TRUE,
    quantile_pred(1:10, 1:4 / 5)
  )
  expect_snapshot(
    error = TRUE,
    quantile_pred(matrix(1:20, 5), -1:4 / 5)
  )
  expect_snapshot(
    error = TRUE,
    quantile_pred(matrix(1:20, 5), 1:5 / 6)
  )
  expect_snapshot(
    error = TRUE,
    quantile_pred(matrix(1:20, 5), 4:1 / 5)
  )
})

test_that("quantile levels are checked", {
  expect_snapshot(error = TRUE, {
    quantile_pred(matrix(1:20, 5), quantile_levels = NULL)
  })
  expect_snapshot(error = TRUE, {
    quantile_pred(matrix(1:20, 5), quantile_levels = c(0.7, 0.7, 0.7))
  })
  expect_snapshot(error = TRUE, {
    quantile_pred(
      matrix(1:20, 5),
      quantile_levels = c(rep(0.7, 2), rep(0.8, 3))
    )
  })
  expect_snapshot(error = TRUE, {
    quantile_pred(matrix(1:20, 5), quantile_levels = c(0.8, 0.7))
  })
})

test_that("quantile_pred outputs", {
  v <- quantile_pred(matrix(1:20, 5), 1:4 / 5)
  expect_s3_class(v, "quantile_pred")
  expect_identical(attr(v, "quantile_levels"), 1:4 / 5)
  expect_identical(
    vctrs::vec_data(v) %>% lapply(unname),
    list(quantile_values = matrix(1:20, 5))
  )
})

test_that("extract_quantile_levels", {
  v <- quantile_pred(matrix(1:20, 5), 1:4 / 5)
  expect_identical(extract_quantile_levels(v), 1:4 / 5)

  expect_snapshot(
    error = TRUE,
    extract_quantile_levels(1:10)
  )
})

test_that("median for quantile_pred", {
  v <- quantile_pred(matrix(1:25, 5), 3:7 / 10)
  expect_identical(median(v), 11:15)

  v_above_med <- quantile_pred(matrix(1:10, 2), 11:15 / 20)
  expect_equal(median(v_above_med), rep(NA, 2))

  v_below_med <- quantile_pred(matrix(1:10, 2), 5:9 / 20)
  expect_equal(median(v_above_med), rep(NA, 2))

  v4 <- quantile_pred(matrix(1:10, ncol = 1), 0.4)
  expect_equal(median(v4), rep(NA, 10))

  v5 <- quantile_pred(matrix(1:10, ncol = 1), 0.5)
  expect_equal(median(v5), as.double(1:10))
})

test_that("quantile_pred formatting", {
  # multiple quantiles
  v <- quantile_pred(matrix(1:20, 5), 1:4 / 5)
  expect_snapshot(v)
  expect_snapshot(quantile_pred(matrix(1:18, 9), c(1 / 3, 2 / 3)))
  expect_snapshot(
    quantile_pred(matrix(seq(0.01, 1 - 0.01, length.out = 6), 3), c(.2, .8))
  )
  expect_snapshot(tibble(qntls = v))
  m <- matrix(1:20, 5)
  m[2, 3] <- NA
  m[4, 2] <- NA
  expect_snapshot(quantile_pred(m, 1:4 / 5))

  # single quantile
  m <- matrix(1:5)
  one_quantile <- quantile_pred(m, 5 / 9)
  expect_snapshot(one_quantile)
  expect_snapshot(tibble(qntls = one_quantile))
  m[2] <- NA
  expect_snapshot(quantile_pred(m, 5 / 9))

  set.seed(393)
  v <- quantile_pred(matrix(exp(rnorm(20)), ncol = 4), 1:4 / 5)
  expect_snapshot(format(v))
  expect_snapshot(format(v, digits = 5))
  expect_snapshot(data.frame(v = v))
})

test_that("as_tibble() for quantile_pred", {
  v <- quantile_pred(matrix(1:20, 5), 1:4 / 5)
  tbl <- as_tibble(v)
  expect_s3_class(tbl, c("tbl_df", "tbl", "data.frame"))
  expect_named(tbl, c(".pred_quantile", ".quantile_levels", ".row"))
  expect_true(nrow(tbl) == 20)
})

test_that("as.matrix() for quantile_pred", {
  x <- matrix(1:20, 5)
  v <- quantile_pred(x, 1:4 / 5)
  m <- as.matrix(v)
  expect_true(is.matrix(m))
  expect_identical(m, x)
})

test_that("Various ways to introduce NAs in quantile_pred work", {
  dbl_mat <- matrix(c(1:3, c(1, NA, NA), rep(NA, 3)), 3, 3, byrow = TRUE)
  int_mat <- dbl_mat
  storage.mode(int_mat) <- "integer"
  levels <- 1:3 / 4
  dbl_v <- quantile_pred(dbl_mat, levels)
  int_v <- quantile_pred(int_mat, levels)
  dbl_sentinel <- quantile_pred(t(rep(NA_real_, length(levels))), levels)
  int_sentinel <- quantile_pred(t(rep(NA_integer_, length(levels))), levels)

  expect_identical(vec_init(dbl_v, 5), rep(dbl_sentinel, 5))
  expect_true(vec_equal(dbl_v[3], dbl_sentinel, na_equal = TRUE))
  expect_true(vec_equal(dbl_v[3], NA, na_equal = TRUE))
  expect_false(vec_equal(dbl_v[2], NA, na_equal = TRUE))
  expect_identical(vec_c(dbl_v[1:2], NA), dbl_v)
  expect_identical(vec_c(NA, dbl_v[1:2]), dbl_v[c(3, 1, 2)])
  expect_identical(
    merge(
      tibble(date = as.Date("2020-01-01") + 0:5),
      tibble(date = as.Date("2020-01-01") + 1:3, pred = dbl_v),
      by = "date",
      all = TRUE
    ),
    data.frame(
      date = as.Date("2020-01-01") + 0:5,
      pred = dbl_v[c(3, 1:3, 3, 3)]
    )
  )
  expect_identical(vec_detect_missing(dbl_v), c(FALSE, FALSE, TRUE))
  expect_identical(vec_detect_complete(dbl_v), c(TRUE, FALSE, FALSE))

  # Same tests cases, for integer typeof backing
  expect_identical(vec_init(int_v, 5), rep(int_sentinel, 5))
  expect_true(vec_equal(int_v[3], int_sentinel, na_equal = TRUE))
  expect_true(vec_equal(int_v[3], NA, na_equal = TRUE))
  expect_false(vec_equal(int_v[2], NA, na_equal = TRUE))
  expect_identical(vec_c(int_v[1:2], NA), int_v)
  expect_identical(vec_c(NA, int_v[1:2]), int_v[c(3, 1, 2)])
  expect_identical(
    merge(
      tibble(date = as.Date("2020-01-01") + 0:5),
      tibble(date = as.Date("2020-01-01") + 1:3, pred = int_v),
      by = "date",
      all = TRUE
    ),
    data.frame(
      date = as.Date("2020-01-01") + 0:5,
      pred = int_v[c(3, 1:3, 3, 3)]
    )
  )
  expect_identical(vec_detect_missing(int_v), c(FALSE, FALSE, TRUE))
  expect_identical(vec_detect_complete(int_v), c(TRUE, FALSE, FALSE))
})

test_that("quantile_pred == logic outputs NAs when expected", {
  single_pred <- function(values, levels) {
    quantile_pred(t(as.matrix(values)), levels)
  }
  expect_identical(single_pred(1, 0.5) == single_pred(NA, 0.5), NA)
  expect_identical(single_pred(NA, 0.5) == single_pred(NA, 0.5), NA)
  expect_identical(
    single_pred(c(1, NA), 1:2 / 3) == single_pred(c(4, NA), 1:2 / 3),
    FALSE
  )
  expect_identical(
    single_pred(c(1, NA), 1:2 / 3) == single_pred(c(4, 5), 1:2 / 3),
    FALSE
  )
})

test_that("Inequalities don't work on quantile_preds, but equality & sorting does", {
  v <- quantile_pred(matrix(c(6, 1, 2, 3, 5, 6), 2, 3, byrow = TRUE), 1:3 / 4)
  expect_error(v < v, class = "hardhat_error_comparing_quantile_preds")
  expect_error(v <= v, class = "hardhat_error_comparing_quantile_preds")
  expect_error(v > v, class = "hardhat_error_comparing_quantile_preds")
  expect_error(v >= v, class = "hardhat_error_comparing_quantile_preds")
  expect_identical(v == v, c(TRUE, TRUE))
  expect_identical(v != v, c(FALSE, FALSE))
  expect_identical(sort(v), v[c(2, 1)])
})

test_that("quantile_pred typeof compatibility works", {
  dbl_mat <- matrix(c(1:3, c(4, NA, NA), rep(NA, 3)), 3, 3, byrow = TRUE)
  int_mat <- dbl_mat
  storage.mode(int_mat) <- "integer"
  levels <- 1:3 / 4
  dbl_v <- quantile_pred(dbl_mat, levels)
  int_v <- quantile_pred(int_mat, levels)
  # ptype
  expect_identical(vec_ptype(dbl_v), dbl_v[0])
  expect_identical(vec_ptype(int_v), int_v[0])
  # ptype2
  expect_identical(vec_ptype2(dbl_v, int_v), dbl_v[0])
  expect_identical(vec_ptype2(int_v, dbl_v), dbl_v[0])
  # cast
  expect_identical(vec_cast(int_v, dbl_v), dbl_v)
  expect_identical(vec_cast(dbl_v, int_v), int_v)
  dbl_v2 <- dbl_v
  field(dbl_v2, "quantile_values") <- field(dbl_v2, "quantile_values") + 0.5
  expect_error(vec_cast(dbl_v2, int_v), class = "vctrs_error_cast_lossy")
})

test_that("quantile_pred level (in)compatibility works", {
  levels1 <- seq(0, 0.2, by = 0.05)
  levels2 <- c(0, 0.05, 0.1, 0.15, 0.2)
  expect_false(all(levels1 == levels2))
  v1 <- quantile_pred(t(as.matrix(1:5)), levels1)
  v2 <- quantile_pred(t(as.matrix(1:5)), levels2)
  expect_error(vec_ptype2(v1, v2), class = "vctrs_error_incompatible_type")
  expect_snapshot(vec_ptype2(v1, v2), error = TRUE, cnd_class = TRUE)
  expect_error(vec_cast(v1, v2), class = "vctrs_error_incompatible_type")
  expect_error(vec_cast(v2, v1), class = "vctrs_error_incompatible_type")
})

test_that("unary math works on quantiles", {
  dstn <- quantile_pred(
    matrix(c(1:4, 8:11), nrow = 2, byrow = TRUE),
    1:4 / 5
  )
  dstn2 <- quantile_pred(
    log(matrix(c(1:4, 8:11), nrow = 2, byrow = TRUE)),
    1:4 / 5
  )
  expect_identical(log(dstn), dstn2)
})

test_that("arithmetic works on quantiles", {
  dstn <- quantile_pred(
    matrix(c(1:4, 8:11), nrow = 2, byrow = TRUE),
    1:4 / 5
  )
  dstn2 <- quantile_pred(
    matrix(c(1:4, 8:11), nrow = 2, byrow = TRUE) + 1,
    1:4 / 5
  )
  expect_identical(dstn + 1, dstn2)
  expect_identical(1 + dstn, dstn2)

  dstn2 <- quantile_pred(
    matrix(c(1:4, 8:11), nrow = 2, byrow = TRUE) / 4,
    1:4 / 5
  )
  expect_identical(dstn / 4, dstn2)
  expect_identical((1 / 4) * dstn, dstn2)

  expect_snapshot(error = TRUE, sum(dstn))
})

test_that("vec_ptype works", {
  v1 <- quantile_pred(matrix(1:20, 5), 1:4 / 5)
  v2 <- quantile_pred(matrix(1:15, 5), 2:4 / 5)
  expect_identical(vec_ptype2(v1, v1), vec_ptype(v1))
  expect_identical(vec_ptype2(v1, v2), vec_ptype(v1))
  expect_identical(vec_ptype2(v2, v1), vec_ptype(v1))

  ugly_levels <- quantile_pred(matrix(1:20, 5), 1:4 / 5 + .1)
  expect_snapshot(error = TRUE, vec_ptype2(v1, ugly_levels))
})

test_that("vec_cast self-self works", {
  qp <- quantile_pred(matrix(rnorm(20), 5), c(.2, .4, .6, .8))
  qp2 <- quantile_pred(matrix(rnorm(7), nrow = 1), 2:8 / 10)
  expect_identical(vec_cast(qp, qp), qp)
  expect_identical(vec_cast(qp2, qp2), qp2)
})
