library("testthat")
library("boot")
library("rsample")
library("Cyclops")
library("survival")

## helper functions

convertToDf <- function(sim, ncovars) {

    if (ncovars < max(sim$covariates$covariateId)) {
        stop("Too few covariates")
    }

    mat <- matrix(0, nrow = nrow(sim$outcomes), ncol = ncovars)

    invisible(mapply(function(rowId, covariateId, covariateValue) {
        mat[rowId, covariateId] <<- covariateValue
    },
    sim$covariates$rowId, sim$covariates$covariateId, sim$covariates$covariateValue))

    df <- as.data.frame(mat)
    outcomes <- sim$outcomes[order(sim$outcomes$rowId),]
    df$y <- outcomes$y
    df$stratumId <- outcomes$stratumId

    if (!is.null(outcomes$weights)) {
        df$weights <- outcomes$weights
    }

    if (!is.null(outcomes$time)) {
        df$time <- outcomes$time
    }

    return(df)
}

getAllPercentileIntervals <- function(bb) {
    result <- t(apply(bb, 2L, quantile, c(0.025, 0.975)))
    colnames(result) <- c("bpi_lower", "bpi_upper")
    as.data.frame(result)
}

bootstrap <- function(df, func, R) {

    replicates <- rsample::bootstraps(df, times = R)

    betas <- lapply(replicates$splits, func) %>%
        bind_rows()

    return(betas)
}

## End helper functions

test_that("Small Poisson bootstrap examples with and without weights", {

    # skip_on_cran()

    set.seed(123)

    dobson <- data.frame(
        counts = c(18,17,15,20,10,20,25,13,12),
        outcome2 = c(0,1,0,0,1,0,0,1,0),
        outcome3 = c(0,0,1,0,0,1,0,0,1),
        treatment2 = c(0,0,0,1,1,1,0,0,0),
        treatment3 = c(0,0,0,0,0,0,1,1,1),
        weights = c(0.17,0.03,0.93,0.64,0.94,0.19,0.12,0.58,0.01)
    )

    execute <- function(d, f) {
        cd <- createCyclopsData(counts ~ outcome2 + outcome3 + treatment2 + treatment3, data = d[f,], modelType = "pr")
        fit <- fitCyclopsModel(cd, prior = createPrior("normal", exclude = "(Intercept)"))
        coef(fit)
    }

    bb <- boot(dobson, execute, R = 4999)
    bBias <- apply(bb$t, 2L, mean) - bb$t0

    cd <- createCyclopsData(counts ~ outcome2 + outcome3 + treatment2 + treatment3, data = dobson, modelType = "pr")
    cf <- fitCyclopsModel(cd, prior = createPrior("normal", exclude = "(Intercept)"),
                          control = createControl(seed = 123))
    cb <- runBootstrap(cf, replicates = 4999)

    expect_equivalent(bBias, cb$summary$bias, tolerance = 1E-2) # Check unweighted bias

    wexecute <- function(d, f) {
        cd <- createCyclopsData(counts ~ outcome2 + outcome3 + treatment2 + treatment3, data = d[f,], modelType = "pr")
        fit <- fitCyclopsModel(cd, weights = d[f,]$weights,
                               prior = createPrior("normal", exclude = "(Intercept)"))
        coef(fit)
    }

    wbb <- boot(dobson, wexecute, R = 4999)
    wbbBias <- apply(wbb$t, 2L, mean) - wbb$t0

    wcd <- createCyclopsData(counts ~ outcome2 + outcome3 + treatment2 + treatment3, data = dobson, modelType = "pr")
    wcf <- fitCyclopsModel(wcd, weights = dobson$weights,
                          prior = createPrior("normal", exclude = "(Intercept)"),
                          control = createControl(seed = 123))
    wcb <- runBootstrap(wcf, replicates = 4999)

    expect_equivalent(wbbBias, wcb$summary$bias, tolerance = 1E-2) # Check weighted bias

    expect_gt(mean(abs(cb$summary$bias - wcb$summary$bias)), 0.01) # Difference estimates with and without weights
})

test_that("Small Poisson bootstrap examples with an offset", {

    # skip_on_cran()

    dobson <- data.frame(
        counts = c(18,17,15,20,10,20,25,13,12),
        outcome2 = c(0,1,0,0,1,0,0,1,0),
        outcome3 = c(0,0,1,0,0,1,0,0,1),
        treatment2 = c(0,0,0,1,1,1,0,0,0),
        treatment3 = c(0,0,0,0,0,0,1,1,1),
        offset = c(1,1,1,1,1,1,1,1,1)
    )

    set.seed(123)
    cdo <- createCyclopsData(counts ~ outcome2 + outcome3 + treatment2 + treatment3 + offset(offset),
                            data = dobson, modelType = "pr")
    cfo <- fitCyclopsModel(cdo, prior = createPrior("normal", exclude = "(Intercept)"),
                          control = createControl(seed = 123))
    cbo <- runBootstrap(cfo, replicates = 4999)

    set.seed(123)
    cdn <- createCyclopsData(counts ~ outcome2 + outcome3 + treatment2 + treatment3,
                             data = dobson, modelType = "pr")
    cfn <- fitCyclopsModel(cdn, prior = createPrior("normal", exclude = "(Intercept)"),
                           control = createControl(seed = 123))
    cbn <- runBootstrap(cfn, replicates = 4999)

    expect_equal(cbo$summary$original + c(1,0,0,0,0), cbn$summary$original, tolerance = 1E-4)

    expect_equal(cbo$summary$bpi_lower + c(1,0,0,0,0), cbn$summary$bpi_lower, tolerance = 1E-4)
})


# boot.out <- list(
#     t = as.matrix(cb$samples),
#     sim = "ordinary",
#     stype = "i",
#     R = nrow(cb$samples),
#     t0 = coef(cf),
#     data = matrix(nrow = Cyclops::getNumberOfRows(cd)), # bb$data
#     strata = rep(1, Cyclops::getNumberOfRows(cd)) # bb$strata
# )
#
# boot.ci(boot.out, index = 1L, type = "bca")

test_that("bootstrap option for na.rm", {

    # skip_on_cran() # ASAN error https://github.com/OHDSI/Cyclops/actions/runs/16373844416

    test <- read.table(header=T, sep = ",", text = "
start, length, event, x1, x2
0, 4,  1,0,0
0, 3,  1,2,0
0, 3,  0,0,1
0, 2,  1,0,1
0, 2,  1,1,1
0, 1,  0,1,0
0, 1,  1,1,0
")
    test <- rbind(data.frame(test, index = 1:7), data.frame(test, index = 1:7))

    set.seed(123)

    cyclopsData <- createCyclopsData(Surv(length, event) ~ x1 + strata(x2),
                                     data = test,
                                     modelType = "cox")

    fit <- fitCyclopsModel(cyclopsData, weights = rep(c(0,1), 7))
    expect_error(bootstrap <- runBootstrap(fit, replicates = 4999), "missing values and NaN")

    fit <- fitCyclopsModel(cyclopsData, weights = rep(c(0,1), 7))
    bootstrap <- runBootstrap(fit, replicates = 4999, na.rm = TRUE)
    expect_lt(nrow(bootstrap$samples), 4999)
})

test_that("Large logistic bootstrap with and without weights", {

    # skip_on_cran()

    set.seed(123)
    sim <- simulateCyclopsData(nstrata=100,
                               ncovars=4,
                               nrows=1000,
                               effectSizeSd=0.5,
                               eCovarsPerRow=2,
                               model="logistic")
    cyclopsData <- convertToCyclopsData(outcomes = sim$outcomes,
                                        covariates = sim$covariates,
                                        modelType = "lr")
    fitCyclopsNoWeights <- fitCyclopsModel(cyclopsData = cyclopsData)
    bsNoWeights <- runBootstrap(fitCyclopsNoWeights, replicates = 1999)

    bbNoWeights <- bootstrap(
        convertToDf(sim, 4),
        function(split) {
            data <- rsample::analysis(split)
            fit <- glm(y ~ V1 + V2 + V3 + V4,
                       family = "binomial", data = data)
            coef(fit)
        }, R = 1999)

    bbNoStdError <- sqrt(apply(bbNoWeights, 2L, var))
    expect_equivalent(bsNoWeights$summary$std_err, bbNoStdError, tolerance = 0.02)

    expect_equivalent(bsNoWeights$summary[,c("bpi_lower", "bpi_upper")],
                      getAllPercentileIntervals(bbNoWeights),
                      tolerance = 0.1)

    sim$outcomes$weights <- rep(c(0.1,0.9), nrow(sim$outcomes) / 2)

    cyclopsData <- convertToCyclopsData(outcomes = sim$outcomes,
                                        covariates = sim$covariates,
                                        modelType = "lr")
    fitCyclopsYesWeights <- fitCyclopsModel(cyclopsData = cyclopsData)
    bsYesWeights <- runBootstrap(fitCyclopsYesWeights, replicates = 1999)

    bbYesWeights <- bootstrap(convertToDf(sim, 4),
        function(split) {
            data <- rsample::analysis(split)
            suppressWarnings(
                fit <- glm(y ~ V1 + V2 + V3 + V4, weights = data$weights,
                           family = "binomial", data = data))
            coef(fit)
        }, R = 1999)

    bbYesStdError <- sqrt(apply(bbYesWeights, 2L, var))
    expect_equivalent(bsYesWeights$summary$std_err, bbYesStdError, tolerance = 0.02)

    expect_equivalent(bsYesWeights$summary[,c("bpi_lower", "bpi_upper")],
                      getAllPercentileIntervals(bbYesWeights),
                      tolerance = 0.1)
})

test_that("Large Poisson bootstrap with and without weights", {

    # skip_on_cran()

    set.seed(123)
    sim <- simulateCyclopsData(nstrata=100,
                               ncovars=4,
                               nrows=1000,
                               effectSizeSd=0.5,
                               eCovarsPerRow=2,
                               model="poisson")
    cyclopsData <- convertToCyclopsData(outcomes = sim$outcomes,
                                        covariates = sim$covariates,
                                        modelType = "pr")
    fitCyclopsNoWeights <- fitCyclopsModel(cyclopsData = cyclopsData)
    bsNoWeights <- runBootstrap(fitCyclopsNoWeights, replicates = 1999)

    bbNoWeights <- bootstrap(
        convertToDf(sim, 4),
        function(split) {
            data <- rsample::analysis(split)
            fit <- glm(y ~ V1 + V2 + V3 + V4, offset = log(time),
                       family = "poisson", data = data)
            coef(fit)
        }, R = 1999)

    bbNoStdError <- sqrt(apply(bbNoWeights, 2L, var))
    expect_equivalent(bsNoWeights$summary$std_err, bbNoStdError, tolerance = 0.02)

    expect_equivalent(bsNoWeights$summary[,c("bpi_lower", "bpi_upper")],
                      getAllPercentileIntervals(bbNoWeights),
                      tolerance = 0.075)

    sim$outcomes$weights <- rep(c(0.1,0.9), nrow(sim$outcomes) / 2)

    cyclopsData <- convertToCyclopsData(outcomes = sim$outcomes,
                                        covariates = sim$covariates,
                                        modelType = "pr")
    fitCyclopsYesWeights <- fitCyclopsModel(cyclopsData = cyclopsData)
    bsYesWeights <- runBootstrap(fitCyclopsYesWeights, replicates = 1999)

    bbYesWeights <- bootstrap(
        convertToDf(sim, 4),
        function(split) {
            data <- rsample::analysis(split)
            fit <- glm(y ~ V1 + V2 + V3 + V4, offset = log(time),
                       family = "poisson", weights = data$weights, data = data)
            coef(fit)
        }, R = 1999)

    bbYesStdError <- sqrt(apply(bbYesWeights, 2L, var))
    expect_equivalent(bsYesWeights$summary$std_err, bbYesStdError, tolerance = 0.02)

    expect_equivalent(bsYesWeights$summary[,c("bpi_lower", "bpi_upper")],
                      getAllPercentileIntervals(bbYesWeights),
                      tolerance = 0.075)
})

test_that("Large Cox bootstrap with and without weights", {

    # skip_on_cran()

    set.seed(123)
    sim <- simulateCyclopsData(nstrata=100,
                               ncovars=4,
                               nrows=1000,
                               effectSizeSd=0.5,
                               eCovarsPerRow=2,
                               model="survival")
    sim$outcomes$stratumId <- NULL
    sim$covariates$stratumId <- NULL
    cyclopsData <- convertToCyclopsData(outcomes = sim$outcomes,
                                        covariates = sim$covariates,
                                        modelType = "cox")
    fitCyclopsNoWeights <- fitCyclopsModel(cyclopsData = cyclopsData)
    bsNoWeights <- runBootstrap(fitCyclopsNoWeights, replicates = 3999)

    bbNoWeights <- bootstrap(
        convertToDf(sim, 4),
        function(split) {
            data <- rsample::analysis(split)
            fit <- coxph(Surv(time, y) ~ V1 + V2 + V3 + V4,
                         data = data)
            coef(fit)
        }, R = 3999)

    bbNoStdError <- sqrt(apply(bbNoWeights, 2L, var))
    expect_equivalent(bsNoWeights$summary$std_err, bbNoStdError, tolerance = 0.02)

    expect_equivalent(bsNoWeights$summary[,c("bpi_lower", "bpi_upper")],
                      getAllPercentileIntervals(bbNoWeights),
                      tolerance = 0.075)

    sim$outcomes$weights <- rep(c(0.1,0.9), nrow(sim$outcomes) / 2)

    cyclopsData <- convertToCyclopsData(outcomes = sim$outcomes,
                                        covariates = sim$covariates,
                                        modelType = "cox")
    fitCyclops <- fitCyclopsModel(cyclopsData = cyclopsData)
    bsYesWeights <- runBootstrap(fitCyclops, replicates = 3999)

    bbYesWeights <- bootstrap(
        convertToDf(sim, 4),
        function(split) {
            data <- rsample::analysis(split)
            fit <- coxph(Surv(time, y) ~ V1 + V2 + V3 + V4,
                         weights = data$weights, data = data)
            coef(fit)
        }, R = 3999)

    bbYesStdError <- sqrt(apply(bbYesWeights, 2L, var))
    expect_equivalent(bsYesWeights$summary$std_err, bbYesStdError, tolerance = 0.02)

    expect_equivalent(bsYesWeights$summary[,c("bpi_lower", "bpi_upper")],
                      getAllPercentileIntervals(bbYesWeights),
                      tolerance = 0.075)
})
