## Test compute_local_windows() =========================================
test_that("compute_local_windows returns correct structure", {
    t <- 1:10
    width <- 3
    result <- compute_local_windows(t, width = width, span = NULL)

    expect_type(result, "list")
    expect_length(result, length(t))
    expect_true(all(vapply(result, is.numeric, logical(1))))

    ## with span
    t <- seq(0, 10, by = 0.5)
    span <- 1
    result <- compute_local_windows(t, width = NULL, span = span)

    expect_type(result, "list")
    expect_length(result, length(t))
})

test_that("compute_local_windows respects width boundaries", {
    t <- 1:10
    width <- 3
    result <- compute_local_windows(t, width = width)

    # First element: can only look forward
    expect_equal(result[[1]], 1:2)
    # Last element: can only look backward
    expect_equal(result[[10]], 9:10)
    # Middle element: symmetrical window
    expect_equal(result[[5]], c(4:6))

    ## even width align centreed
    width <- 2
    result <- compute_local_windows(t, width = width, align = "centre")

    # left-biased forward looking
    expect_equal(result[[1]], 1:2)
    # Last element only at idx
    expect_equal(result[[10]], 10)
    # left-biased forward looking
    expect_equal(result[[5]], c(5:6))

    ## with span
    t <- seq(0, 10, by = 0.5)
    span <- 2
    result <- compute_local_windows(t, span = span)

    # First element: can only look forward
    expect_equal(result[[1]], 1:3)
    expect_equal(t[result[[1]]], t[1:3])
    # Last element: can only look backward
    expect_equal(result[[21]], 19:21)
    expect_equal(t[result[[21]]], t[19:21])
    # Middle element: symmetrical window
    expect_equal(result[[10]], c(8:12))
    expect_equal(t[result[[10]]], t[c(8:12)])
})

test_that("compute_local_windows align = 'center'", {
    t <- 1:10
    width <- 2
    result <- compute_local_windows(t, width = width, align = "center")

    expect_equal(result[[1]], 1:2)
    expect_equal(result[[10]], 10)
    expect_equal(result[[5]], c(5:6))
})

test_that("compute_local_windows width and span >= length(x) works", {
    t <- 1:10
    result <- compute_local_windows(t, width = 20)
    expect_equal(result, rep(list(t), length(t)))

    result <- compute_local_windows(t, span = 20)
    expect_equal(result, rep(list(t), length(t)))
})

test_that("compute_local_windows works with width = 1 and span = 0", {
    t <- 1:10
    result <- compute_local_windows(t, width = 1)
    expect_equal(result, as.list(t))

    result <- compute_local_windows(t, span = 0)
    expect_equal(result, as.list(t))

    compute_local_windows(t, width = 1, span = 0)
})

## test compute_valid_neighbours() ===========================
test_that("compute_valid_neighbours() works with width", {
    x <- c(1, 2, NA, 4, 5, NA, 7)

    # width = 1
    result <- compute_valid_neighbours(x, width = 3)
    expect_length(result, 2) # Two NAs
    expect_equal(result[[1]], c(2, 4))
    expect_equal(result[[2]], c(5, 7))

    # width = 3
    x <- c(1, 2, 3, NA, 5, 6, 7)
    result <- compute_valid_neighbours(x, width = 4)
    expect_equal(result[[1]], c(2, 3, 5, 6))

    # Edge case: NA at start
    x <- c(NA, 20, 30, 40)
    result <- compute_valid_neighbours(x, width = 3)
    expect_equal(result[[1]], 2)

    # Edge case: NA at end
    x <- c(10, 20, 30, NA)
    result <- compute_valid_neighbours(x, width = 3)
    expect_equal(result[[1]], 3)
})

test_that("compute_valid_neighbours() works with span", {
    t <- 0:4
    x <- c(1, 2, NA, 4, 5)

    # span = 1 (includes t[2] = 1 and t[4] = 3)
    result <- compute_valid_neighbours(x, t = t, span = 1)
    expect_length(result, 1)
    expect_equal(result[[1]], c(2, 4))

    # span = 0.5 (no values of t within t ± span should return same as width = 1)
    result <- compute_valid_neighbours(x, t = t, span = 0.5)
    expect_equal(result[[1]], c(2, 4))
    expect_equal(result, compute_valid_neighbours(x, width = 3))

    # Multiple NAs
    x <- c(1, NA, 3, NA, 5)
    result <- compute_valid_neighbours(x, t = t, span = 1)
    expect_length(result, 2)
    expect_equal(result[[1]], c(1, 3))
    expect_equal(result[[2]], c(3, 5))

    # Edge case: NA at start
    x <- c(NA, 2, 3, 4)
    expect_equal(compute_valid_neighbours(x, span = 1)[[1]], 2)
    expect_equal(compute_valid_neighbours(x, span = 0.5)[[1]], 2)

    # Edge case: NA at end
    x <- c(1, 2, 3, NA)
    expect_equal(compute_valid_neighbours(x, span = 1)[[1]], 3)
    expect_equal(compute_valid_neighbours(x, span = 0.5)[[1]], 3)
})

test_that("compute_valid_neighbours width and span >= length(x) works", {
    x <- c(1, 2, NA, 4, 5)
    result <- compute_valid_neighbours(x, width = 8)
    expect_equal(result, list(x[!is.na(x)]))

    result <- compute_valid_neighbours(x, span = 8)
    expect_equal(result, list(x[!is.na(x)]))
})

test_that("compute_valid_neighbours works with width = 1 and span = 0", {
    x <- c(1, 2, NA, 4, 5)
    result <- compute_valid_neighbours(x, width = 1)
    expect_equal(result[[1]], c(2, 4))

    result <- compute_valid_neighbours(x, span = 0)
    expect_equal(result[[1]], c(2, 4))
})

## Test compute_local_fun() =========================================
test_that("compute_local_fun returns correct length", {
    x <- c(1, 2, 3, 4, 5)
    window_idx <- compute_local_windows(x, width = 1)
    result <- compute_local_fun(x, window_idx, fn = median)

    expect_type(result, "double")
    expect_length(result, length(x))
})

test_that("compute_local_fun calculates correct medians", {
    x <- c(10, 20, 30, 40, 50)
    # Manual windows: each looks at neighbours
    window_idx <- compute_local_windows(x, width = 3)
    result <- compute_local_fun(x, window_idx, fn = median)

    expect_equal(result[1], median(x[1:2])) # median of x[2]
    expect_equal(result[2], median(x[1:3])) # median of x[c(1,3)]
    expect_equal(result[5], median(x[4:5])) # median of x[4]
})

test_that("compute_local_fun handles NA values", {
    ## this shouldn't happen with handle_na, but just in case
    x <- c(1, NA, 3, 4, 5)
    window_idx <- compute_local_windows(x, width = 3)
    result <- compute_local_fun(x, window_idx, fn = median, na.rm = TRUE)

    expect_false(all(is.na(result[1:3])))
    expect_equal(result[1], median(x[1]))
    expect_equal(result[2], median(x[c(1, 3)]))
    expect_equal(result[3], median(x[c(3, 4)]))
})

test_that("compute_local_fun() handles empty windows", {
    x <- c(1, 2, 3)
    window_idx <- list(integer(0))
    result <- compute_local_fun(x, window_idx, fn = median, na.rm = TRUE)
    expect_true(is.na(result))
})

test_that("compute_local_fun() handles single values", {
    x <- c(10, 20, 30)
    window_idx <- list(1, 2, 3)
    result <- compute_local_fun(x, window_idx, fn = median)
    expect_equal(result, x)
})


## Test compute_outliers() ==============================================
test_that("compute_outliers returns list of logical and numeric vectors", {
    x <- c(1, 2, 3, 100, 5)
    t <- 1:5
    window_idx <- compute_local_windows(t, width = 3, span = NULL)
    result <- compute_outliers(x, window_idx, outlier_cutoff = 3)

    expect_type(result$local_medians, "double")
    expect_type(result$is_outlier, "logical")
    expect_length(result$local_medians, length(x))
    expect_length(result$is_outlier, length(x))
    expect_true(result$is_outlier[4L]) # 100 should be flagged
    expect_false(any(result$is_outlier[-4L])) # Normal values should not be flagged
})

test_that("compute_outliers threshold sensitivity via outlier_cutoff", {
    x <- c(1, 2, 3, 10, 5)
    t <- 1:5
    window_idx <- compute_local_windows(t, width = 3, span = NULL)

    # Strict threshold
    strict <- compute_outliers(x, window_idx, outlier_cutoff = 1)
    # Lenient threshold
    lenient <- compute_outliers(x, window_idx, outlier_cutoff = 10)

    expect_true(sum(strict$is_outlier) > sum(lenient$is_outlier))
    expect_equal(strict$local_medians, lenient$local_medians)
})

test_that("compute_outliers handles no outliers", {
    x <- 1:5
    t <- 1:5
    window_idx <- compute_local_windows(t, width = 3, span = NULL)
    result <- compute_outliers(x, window_idx, outlier_cutoff = 3)

    expect_true(all(!result$is_outlier))
})

test_that("compute_outliers handles NA", {
    ## this shouldn't happen with handle_na, but just in case
    x <- c(1, 2, NA, 100, 5)
    t <- 1:5
    window_idx <- compute_local_windows(t, width = 3, span = NULL)
    result <- compute_outliers(x, window_idx, outlier_cutoff = 3)

    expect_true(all(!result$is_outlier))
})
