# h_coef_table ----

test_that("h_coef_table works as expected", {
  object <- get_mmrm()
  result <- expect_silent(h_coef_table(object))
  expect_matrix(
    result,
    mode = "numeric"
  )
  expect_names(rownames(result), identical.to = names(coef(object)))
  expect_names(
    colnames(result),
    identical.to = c("Estimate", "Std. Error", "df", "t value", "Pr(>|t|)")
  )
})

test_that("h_coef_table works as expected", {
  object <- get_mmrm_rank_deficient()
  result <- expect_silent(h_coef_table(object))
  expect_matrix(
    result,
    mode = "numeric",
    nrows = length(component(object, "beta_est_complete"))
  )
  expect_true(all(is.na(result["SEX2Female", ])))
})

# summary ----

test_that("summary works as expected", {
  object <- get_mmrm()
  result <- expect_silent(summary(object))
  expect_list(result)
  expect_named(
    result,
    c(
      "cov_type",
      "reml",
      "n_groups",
      "n_theta",
      "n_subjects",
      "n_timepoints",
      "n_obs",
      "beta_vcov",
      "varcor",
      "method",
      "vcov",
      "coefficients",
      "n_singular_coefs",
      "aic_list",
      "call"
    )
  )
})

# h_print_call ----

test_that("h_print_call works as expected", {
  object <- get_mmrm()
  expect_snapshot_output(h_print_call(object$call, 1, 2, 3), cran = TRUE)
})

test_that("h_print_call works as expected for weighted fits", {
  object <- get_mmrm_weighted()
  expect_snapshot_output(h_print_call(object$call, 1, 2, 3), cran = TRUE)
})

# h_print_cov ----

test_that("h_print_cov works as expected", {
  object <- get_mmrm()
  expect_snapshot_output(h_print_cov("toep", 3, 1L), cran = TRUE)
  expect_snapshot_output(h_print_cov("toep", 6, 2L), cran = TRUE)
})

# h_print_aic_list ----

test_that("h_print_aic_list works as expected", {
  expect_snapshot_output(
    h_print_aic_list(
      list(
        AIC = 234.234235,
        BIC = 234.23,
        logLik = -252.234234,
        deviance = 345235.2323
      )
    ),
    cran = FALSE
  )
})

# print.summary.mmrm ----

test_that("print.summary.mmrm works as expected", {
  object <- get_mmrm()
  result <- summary(object)
  expect_snapshot_output(print(result, digits = 1), cran = FALSE)
})

test_that("print.summary.mmrm works as expected for weighted models", {
  object <- get_mmrm_weighted()
  result <- summary(object)
  expect_snapshot_output(print(result, digits = 1), cran = FALSE)
})

test_that("print.summary.mmrm works as expected for rank deficient fits", {
  object <- get_mmrm_rank_deficient()
  result <- summary(object)
  expect_snapshot_output(print(result, digits = 1), cran = FALSE)
})

test_that("print.summary.mmrm works as expected for grouped fits", {
  object <- get_mmrm_group()
  result <- summary(object)
  expect_snapshot_output(print(result, digits = 1), cran = FALSE)
})
# for spatial covariance matrix, covariance matrix of time points
# of unit distance will be displayed.
test_that("print.summary.mmrm works as expected for spatial fits", {
  object <- get_mmrm_spatial()
  result <- summary(object)
  expect_snapshot_output(print(result, digits = 1), cran = FALSE)
})

# confint ----

test_that("confint works for different significance levels", {
  object <- get_mmrm()
  expect_silent(confint(object, level = 1))
  expect_silent(confint(object, level = 0))
  expect_silent(confint(object, level = 0.5))
})

test_that("confint works for different `parm` input", {
  object <- get_mmrm()
  res <- expect_silent(confint(object, parm = c(1, 2, 3)))
  expect_identical(row.names(res), names(coef(object))[c(1, 2, 3)])

  res <- expect_silent(confint(object, parm = c("ARMCDTRT", "AVISITVIS4")))
  expect_identical(row.names(res), c("ARMCDTRT", "AVISITVIS4"))

  expect_error(
    confint(object, parm = 100),
    "Element 1 is not <= 11"
  )
  expect_error(
    confint(object, parm = c("a", "b")),
    "Must be a subset of"
  )
})

test_that("confint give same result as emmeans if no interaction term", {
  object <- mmrm(FEV1 ~ ARMCD + us(AVISIT | USUBJID), data = fev_data)
  emm <- emmeans(object, ~ARMCD)
  emm_pair <- pairs(emm, reverse = TRUE)
  conf <- confint(emm_pair)
  conf_coef <- confint(object, 2)
  expect_equal(conf$lower.CL, conf_coef[, 1])
  expect_equal(conf$upper.CL, conf_coef[, 2])
})

test_that("confint give same result as SAS on unstructured", {
  # SAS result file is "design/SAS/sas_coef_ci_ml_un.txt"
  object <- mmrm(
    FEV1 ~ ARMCD + SEX + us(AVISIT | USUBJID),
    data = fev_data,
    reml = FALSE
  )
  conf_coef <- confint(object)
  cis <- matrix(
    c(40.0071, 42.1609, 2.5714, 5.0838, -1.3968, 1.1204),
    ncol = 2,
    byrow = TRUE
  )
  expect_equal(conf_coef, cis, ignore_attr = TRUE, tolerance = 1e-4)

  # SAS result file is "design/SAS/sas_coef_ci_reml_un.txt"
  object <- mmrm(
    FEV1 ~ ARMCD + SEX + us(AVISIT | USUBJID),
    data = fev_data,
    reml = TRUE
  )
  conf_coef <- confint(object)
  cis <- matrix(
    c(39.9890, 42.1634, 2.5584, 5.0945, -1.4108, 1.1301),
    ncol = 2,
    byrow = TRUE
  )
  expect_equal(conf_coef, cis, ignore_attr = TRUE, tolerance = 1e-4)
})

test_that("confint give same result as SAS on ar1", {
  # SAS result file is "design/SAS/sas_coef_ci_ml_ar1.txt"
  object <- mmrm(
    FEV1 ~ ARMCD + SEX + ar1(AVISIT | USUBJID),
    data = fev_data,
    reml = FALSE
  )
  conf_coef <- confint(object)
  cis <- matrix(
    c(38.5271, 41.8057, 2.3353, 6.1039, -1.6559, 2.1193),
    ncol = 2,
    byrow = TRUE
  )
  expect_equal(conf_coef, cis, ignore_attr = TRUE, tolerance = 1e-4)

  # SAS result file is "design/SAS/sas_coef_ci_reml_ar1.txt"
  object <- mmrm(
    FEV1 ~ ARMCD + SEX + ar1(AVISIT | USUBJID),
    data = fev_data,
    reml = TRUE
  )
  conf_coef <- confint(object)
  cis <- matrix(
    c(38.5119, 41.8167, 2.3216, 6.1206, -1.6664, 2.1392),
    ncol = 2,
    byrow = TRUE
  )
  expect_equal(conf_coef, cis, ignore_attr = TRUE, tolerance = 1e-4)
})

test_that("h_dataset_sort_all() sorts in column order", {
  expect_equal(
    h_dataset_sort_all(unique(mtcars[c("am", "vs", "cyl")])),
    data.frame(
      am = c(0, 0, 0, 1, 1, 1, 1),
      vs = c(0, 1, 1, 0, 0, 0, 1),
      cyl = c(8, 4, 6, 4, 6, 8, 4),
      row.names = c(
        "Hornet Sportabout",
        "Merc 240D",
        "Hornet 4 Drive",
        "Porsche 914-2",
        "Mazda RX4",
        "Ford Pantera L",
        "Datsun 710"
      )
    )
  )
})

test_that("h_check_columns_nested() correctly compares dfs", {
  expect_true(h_check_columns_nested(mtcars[, -2:-5], structure(mtcars, a = 1)))
  # FALSE because different numbers of rows:
  expect_false(h_check_columns_nested(mtcars[-2:-5, ], mtcars))
  # FALSE because first dataset has more columns than second dataset:
  expect_false(h_check_columns_nested(transform(mtcars, a = 1), mtcars))
  # FALSE because the first observation is different in each dataset:
  expect_false(h_check_columns_nested(
    transform(mtcars, vs = c(7, vs[-1])),
    mtcars
  ))
})

test_that("h_check_fits_all_data_same() correctly compares dfs", {
  expect_true(h_check_fits_all_data_same(list(
    get_mmrm_group(),
    get_mmrm(),
    get_mmrm_rank_deficient()
  )))
  # FALSE because get_mmrm_smaller_data() has a dataset with fewer rows:
  expect_false(h_check_fits_all_data_same(list(
    get_mmrm_group(),
    get_mmrm(),
    get_mmrm_smaller_data(),
    get_mmrm_rank_deficient()
  )))
  # FALSE because get_mmrm() uses more columns than get_mmrm_group():
  expect_false(h_check_fits_all_data_same(list(
    get_mmrm(),
    get_mmrm_group(),
    get_mmrm_rank_deficient()
  )))
  # FALSE because get_mmrm() and get_mmrm_alt_data() have different observations
  expect_false(h_check_fits_all_data_same(list(
    get_mmrm_group(),
    get_mmrm(),
    get_mmrm_alt_data(),
    get_mmrm_rank_deficient()
  )))
})

test_that("h_fits_common_data() grabs common observations among datasets", {
  expect_equal(
    h_fits_common_data(
      list(get_mmrm(), get_mmrm_alt_data(), get_mmrm_rank_deficient())
    ),
    data.frame(
      FEV1 = c(39.9710497720302, NA),
      AVISIT = factor(2:1, labels = c("VIS1", "VIS2")),
      USUBJID = factor(c(1L, 1L), labels = "PT1"),
      RACE = factor(c(2L, 2L), labels = "Black or African American"),
      SEX = factor(c(2L, 2L), labels = "Female"),
      ARMCD = factor(c(2L, 2L), labels = "TRT"),
      SEX2 = factor(c(2L, 2L), labels = "Female")
    ),
    tolerance = 1e-3
  )
})

test_that("h_get_minimal_fit_data() grabs only colums used in model fitting", {
  expect_equal(
    h_get_minimal_fit_data(get_mmrm_group()),
    fev_data[c("FEV1", "AVISIT", "USUBJID", "ARMCD")]
  )
  expect_equal(
    h_get_minimal_fit_data(get_mmrm()),
    fev_data[c("FEV1", "AVISIT", "USUBJID", "RACE", "SEX", "ARMCD")]
  )

  # Account for model terms that are calls instead of just symbols (e.g., log(x)
  # as opposed to just x)
  expect_equal(
    h_get_minimal_fit_data(get_mmrm_trans()),
    fev_data[c("FEV1", "AVISIT", "USUBJID", "FEV1_BL", "ARMCD")]
  )
})

test_that("h_check_covar_nesting() ensures models have nested covariates", {
  expect_equal(
    h_check_covar_nesting(
      get_mmrm()[["formula_parts"]],
      get_mmrm_rank_deficient()[["formula_parts"]]
    ),
    "nested"
  )
  # This also tests whether the function can recognize the same interaction term
  # whose components are specified in a different order (e.g, a:b = b:a)
  expect_equal(
    h_check_covar_nesting(
      get_mmrm()[["formula_parts"]],
      get_mmrm_cs()[["formula_parts"]]
    ),
    "identical"
  )
  # First model's covariates aren't nested within the second model's
  expect_error(
    h_check_covar_nesting(
      get_mmrm_trans()[["formula_parts"]],
      get_mmrm_tmb_rank_deficient()[["formula_parts"]]
    ),
    regexp = "covariates.+subset"
  )
  # First model's interaction terms aren't nested within the second model's
  expect_error(
    h_check_covar_nesting(
      get_mmrm()[["formula_parts"]],
      get_mmrm_interactions()[["formula_parts"]]
    ),
    regexp = "interaction.+subset"
  )
})

test_that("h_check_cov_struct_nesting() ensures models have nested covariance structures", {
  expect_equal(
    h_check_cov_struct_nesting(
      get_mmrm_trans()[["formula_parts"]],
      get_mmrm_tmb()[["formula_parts"]]
    ),
    "nested"
  )
  expect_equal(
    h_check_cov_struct_nesting(
      get_mmrm_transformed()[["formula_parts"]],
      get_mmrm_trans()[["formula_parts"]]
    ),
    "identical"
  )
  # Second model is nested within the first rather than the other way around
  expect_error(
    h_check_cov_struct_nesting(
      get_mmrm_tmb()[["formula_parts"]],
      get_mmrm_trans()[["formula_parts"]]
    ),
    regexp = "special case of the next model"
  )
})

test_that("h_assert_nested_models() ensures nested models", {
  # Different visit variable
  expect_error(
    h_assert_nested_models(
      get_mmrm_group(),
      get_mmrm_alt_visit(),
      any_reml = TRUE
    ),
    regexp = "visit variable"
  )
  # Different subject
  expect_error(
    h_assert_nested_models(
      get_mmrm_group(),
      get_mmrm_alt_subj(),
      any_reml = TRUE
    ),
    regexp = "subject variable"
  )
  # Different grouping variable
  expect_error(
    h_assert_nested_models(
      get_mmrm_group(),
      get_mmrm_alt_group(),
      any_reml = TRUE
    ),
    regexp = "grouping variable"
  )
  # Nested variables but REML
  expect_error(
    h_assert_nested_models(
      get_mmrm(),
      get_mmrm_rank_deficient(),
      any_reml = TRUE
    ),
    regexp = "REML.+covariates.+same"
  )
  # Identical model warning
  expect_warning(
    h_assert_nested_models(get_mmrm(), get_mmrm(), any_reml = FALSE),
    regexp = "identical"
  )
  expect_true(h_assert_nested_models(
    get_mmrm_kr(),
    get_mmrm(),
    any_reml = FALSE
  ))
})

test_that("h_assert_lrt_suitability() ensures suitability for LRT testing", {
  # non-increasing degrees of freedom
  expect_error(
    h_assert_lrt_suitability(
      list(get_mmrm_cs(), get_mmrm_smaller_data()),
      refit = FALSE,
      dfs = c(3, 3),
      is_reml = c(TRUE, TRUE)
    ),
    regexp = "degrees of freedom"
  )
  # refit = FALSE and they don't use the same data
  expect_error(
    h_assert_lrt_suitability(
      list(get_mmrm_cs(), get_mmrm_smaller_data()),
      refit = FALSE,
      dfs = c(
        attr(logLik(get_mmrm_cs()), "df"),
        attr(logLik(get_mmrm_smaller_data()), "df")
      ),
      is_reml = c(
        component(get_mmrm_cs(), "reml"),
        component(get_mmrm_smaller_data(), "reml")
      )
    ),
    regexp = "same data"
  )
  expect_true(h_assert_lrt_suitability(
    list(get_mmrm_cs(), get_mmrm()),
    refit = FALSE,
    dfs = c(attr(logLik(get_mmrm_cs()), "df"), attr(logLik(get_mmrm()), "df")),
    is_reml = c(component(get_mmrm_cs(), "reml"), component(get_mmrm(), "reml"))
  ))
})

test_that("h_generate_new_name() generates a string without a binding in env", {
  expect_equal(h_generate_new_name("c", asNamespace("stats")), "c.1")
  this_environment <- environment()
  test_env_parent <- new.env()
  test_env_child <- new.env(parent = test_env_parent)
  this_environment[["foo_mmrm_test_name.2"]] <- NA
  test_env_parent[["foo_mmrm_test_name"]] <- NA
  test_env_child[["foo_mmrm_test_name.1"]] <- NA
  expect_equal(
    h_generate_new_name("foo_mmrm_test_name", test_env_child),
    "foo_mmrm_test_name.3"
  )
})

test_that("h_refit_mmrm() successfully refits an mmrm fit", {
  fit <- get_mmrm()
  refit <- h_refit_mmrm(get_mmrm_smaller_data(), fev_data)

  # Get rid of tmb_object and call's data argument, which will be different
  fit$tmb_object <- NULL
  fit$call$data <- NULL
  refit$tmb_object <- NULL
  refit$call$data <- NULL

  expect_equal(refit, fit)
})

test_that("h_anova_single_mmrm_model() yields the significance of each term", {
  expect_equal(
    h_anova_single_mmrm_model(get_mmrm_group()),
    data.frame(
      num_df = c(1, 1),
      denom_df = c(93.50361, 146.55428),
      f_stat = c(10170.36271, 30.09819),
      p_val = c(3.319442e-97, 1.757614e-7),
      row.names = c("(Intercept)", "ARMCD")
    ),
    tolerance = 1e-3
  )
})

test_that("anova.mmrm() works for a single model", {
  expect_equal(
    anova(get_mmrm_group()),
    structure(
      data.frame(
        num_df = c(1, 1),
        denom_df = c(93.50361, 146.55428),
        f_stat = c(10170.36271, 30.09819),
        p_val = c(3.319442e-97, 1.757614e-7),
        row.names = c("(Intercept)", "ARMCD")
      ),
      class = c("anova.mmrm", "data.frame")
    ),
    tolerance = 1e-3
  )
})


test_that("anova.mmrm() works for multiple models -- no refitting", {
  call_cs <- quote(mmrm(
    formula = FEV1 ~ RACE + SEX + AVISIT * ARMCD + cs(AVISIT | USUBJID),
    data = fev_data
  ))
  call_us <- quote(mmrm(
    formula = FEV1 ~ RACE + SEX + ARMCD * AVISIT + us(AVISIT | USUBJID),
    data = fev_data
  ))
  expect_equal(
    anova(get_mmrm_cs(), get_mmrm()),
    structure(
      data.frame(
        Model = 1L:2L,
        refit = c(FALSE, FALSE),
        REML = c(TRUE, TRUE),
        n_param = c(2, 10),
        n_coef = c(11, 11),
        df = c(2, 10),
        AIC = c(3526.04295, 3406.44988),
        BIC = c(3532.60936, 3439.28191),
        logLik = c(-1761.02147, -1693.22494),
        test = c(NA, "1 vs 2"),
        log_likelihood_ratio = c(NA, 67.79654),
        p_value = c(NA, 1.95508e-25),
        call = c(deparse1(call_cs), deparse1(call_us))
      ),
      class = c("anova.mmrm", "data.frame")
    ),
    tolerance = 1e-3
  )
})


test_that("anova.mmrm() works for multiple models -- with refitting", {
  call_cs <- quote(mmrm(
    formula = FEV1 ~ RACE + SEX + AVISIT * ARMCD + cs(AVISIT | USUBJID),
    data = data.1
  ))
  call_us <- quote(mmrm(
    formula = FEV1 ~ RACE + SEX + ARMCD * AVISIT + us(AVISIT | USUBJID),
    data = .smaller_fev_data
  ))
  expect_equal(
    anova(get_mmrm_cs(), get_mmrm_smaller_data(), refit = TRUE),
    structure(
      data.frame(
        Model = 1L:2L,
        refit = c(TRUE, FALSE),
        REML = c(TRUE, TRUE),
        n_param = c(2, 6),
        n_coef = c(9, 9),
        df = c(2, 6),
        AIC = c(2448.51892, 2418.41620),
        BIC = c(2455.03391, 2437.96118),
        logLik = c(-1222.25946, -1203.20810),
        test = c(NA, "1 vs 2"),
        log_likelihood_ratio = c(NA, 19.05136),
        p_value = c(NA, 1.067197e-7),
        call = c(deparse1(call_cs), deparse1(call_us))
      ),
      class = c("anova.mmrm", "data.frame")
    ),
    tolerance = 1e-3
  )
})
