# test-download.R
# Tests for download_wid(), wid_query(), wid_filter(), wid_fetch(), and cache.

# ── new_wid_df / print ────────────────────────────────────────────────────────

test_that("new_wid_df attaches correct class", {
  df  <- data.frame(country = "US", variable = "sptinc992j",
                    percentile = "p99p100", year = "2000",
                    value = 0.2, age = "992", pop = "j",
                    stringsAsFactors = FALSE)
  out <- widr:::new_wid_df(df)
  expect_s3_class(out, "wid_df")
  expect_s3_class(out, "data.frame")
})

test_that("print.wid_df emits <wid_df> header", {
  expect_output(print(.wid()), "<wid_df>")
})

test_that("wid_df passes through tidyverse-style subset", {
  d <- .wid(3L)
  expect_s3_class(d[d$year == "2020", ], "data.frame")
})

# ── download_wid argument validation ─────────────────────────────────────────

test_that("download_wid errors when both indicators and areas are 'all'", {
  expect_error(download_wid(), "`indicators` or `areas`")
  expect_error(download_wid(indicators = "all", areas = "all"),
               "`indicators` or `areas`")
})

test_that("download_wid warns on years outside [1800, 2100]", {
  testthat::with_mocked_bindings(
    `.cache` = function(a, ...) NULL,
    `.fetch` = function(...) NULL,
    {
      expect_warning(
        download_wid("sptinc992j", areas = "US", years = 1700L, cache = FALSE),
        "outside"
      )
    }
  )
})

test_that("download_wid warns on malformed area codes", {
  testthat::with_mocked_bindings(
    `.cache` = function(a, ...) NULL,
    `.fetch` = function(...) NULL,
    {
      expect_warning(
        download_wid("sptinc992j", areas = "us", cache = FALSE),
        "invalid"
      )
    }
  )
})

test_that("download_wid normalises ages != 'all'", {
  captured <- list()
  testthat::with_mocked_bindings(
    `.cache` = function(a, ...) NULL,
    `.fetch` = function(indicators, areas, perc, ages, pop, no_extrapolation, verbose) {
      captured$ages <<- ages
      .wid()
    },
    {
      download_wid("sptinc992j", areas = "US", ages = 992L, cache = FALSE)
      expect_equal(captured$ages, "992")
    }
  )
})

test_that("download_wid normalises pop != 'all'", {
  captured <- list()
  testthat::with_mocked_bindings(
    `.cache` = function(a, ...) NULL,
    `.fetch` = function(indicators, areas, perc, ages, pop, no_extrapolation, verbose) {
      captured$pop <<- pop
      .wid()
    },
    {
      download_wid("sptinc992j", areas = "US", pop = "j", cache = FALSE)
      expect_equal(captured$pop, "j")
    }
  )
})

# ── download_wid return value ─────────────────────────────────────────────────

test_that("download_wid returns wid_df on cache hit", {
  mock <- .wid()
  testthat::with_mocked_bindings(
    `.cache` = function(a, ...) if (a == "get") mock else NULL,
    {
      result <- download_wid("sptinc992j", areas = "US")
      expect_s3_class(result, "wid_df")
    }
  )
})

test_that("download_wid returns wid_df when .fetch returns a wid_df stub", {
  mock <- .wid()
  testthat::with_mocked_bindings(
    `.cache` = function(a, ...) NULL,
    `.fetch` = function(...) mock,
    {
      result <- download_wid("sptinc992j", areas = "US", cache = FALSE)
      expect_s3_class(result, "wid_df")
    }
  )
})

test_that("download_wid returns empty wid_df when .fetch yields NULL", {
  testthat::with_mocked_bindings(
    `.cache` = function(a, ...) NULL,
    `.fetch` = function(...) NULL,
    {
      expect_warning(
        result <- download_wid("sptinc992j", areas = "US", cache = FALSE),
        NA   # .fetch warns internally; download_wid itself does not
      )
      expect_s3_class(result, "wid_df")
      expect_equal(nrow(result), 0L)
    }
  )
})

test_that("download_wid filters years after fetch", {
  rows <- rbind(
    data.frame(country = "US", variable = "sptinc992j", percentile = "p99p100",
               year = "2019", value = 0.20, age = "992", pop = "j",
               stringsAsFactors = FALSE),
    data.frame(country = "US", variable = "sptinc992j", percentile = "p99p100",
               year = "2020", value = 0.21, age = "992", pop = "j",
               stringsAsFactors = FALSE)
  )
  testthat::with_mocked_bindings(
    `.cache` = function(a, ...) NULL,
    `.fetch` = function(...) rows,
    {
      result <- download_wid("sptinc992j", areas = "US",
                             years = 2020L, cache = FALSE)
      expect_equal(nrow(result), 1L)
      expect_equal(result$year, "2020")
    }
  )
})

test_that("download_wid returns wid_df — tidyverse compatible", {
  mock <- .wid()
  testthat::with_mocked_bindings(
    `.cache` = function(a, ...) NULL,
    `.fetch` = function(...) mock,
    {
      result <- download_wid("sptinc992j", areas = "US", cache = FALSE)
      expect_true(is.data.frame(result))
      expect_s3_class(result, "wid_df")
      expect_false(is.list(result) && !is.data.frame(result))
    }
  )
})

# ── verbose messaging ─────────────────────────────────────────────────────────

test_that("download_wid emits verbose message", {
  mock <- .wid()
  testthat::with_mocked_bindings(
    `.cache` = function(a, ...) NULL,
    `.fetch` = function(...) mock,
    {
      expect_message(
        download_wid("sptinc992j", areas = "US", cache = FALSE, verbose = TRUE),
        "Fetching"
      )
    }
  )
})

# ── include_extrapolations flag ───────────────────────────────────────────────

test_that("download_wid passes include_extrapolations=FALSE as no_extrapolation=TRUE", {
  captured <- list()
  testthat::with_mocked_bindings(
    `.cache` = function(a, ...) NULL,
    `.fetch` = function(indicators, areas, perc, ages, pop, no_extrapolation, verbose) {
      captured$no_extrapolation <<- no_extrapolation
      .wid()
    },
    {
      download_wid("sptinc992j", areas = "US",
                   include_extrapolations = FALSE, cache = FALSE)
      expect_true(captured$no_extrapolation)
    }
  )
})

# ── caching ───────────────────────────────────────────────────────────────────

test_that("download_wid stores result in cache after fetch", {
  mock  <- .wid()
  saved <- list()
  testthat::with_mocked_bindings(
    `.cache` = function(a, key = NULL, value = NULL) {
      if (a == "get") return(NULL)
      if (a == "set") { saved$value <<- value; return(invisible(value)) }
    },
    `.fetch` = function(...) mock,
    {
      download_wid("sptinc992j", areas = "US", cache = TRUE)
      expect_s3_class(saved$value, "wid_df")
    }
  )
})

# ── metadata attribute ────────────────────────────────────────────────────────

test_that("download_wid metadata=TRUE attaches wid_meta attribute", {
  mock    <- .wid()
  meta_df <- data.frame(variable = "sptinc992j", country = "US",
                        source = "Tax records", stringsAsFactors = FALSE)
  testthat::with_mocked_bindings(
    `.cache`               = function(a, ...) NULL,
    `.fetch`               = function(...) mock,
    `.get_variables_areas` = function(...) data.frame(
      variable = "sptinc", country = "US", percentile = "p99p100",
      age = "992", pop = "j", stringsAsFactors = FALSE),
    `.fetch_meta`          = function(...) meta_df,
    {
      result <- download_wid("sptinc992j", areas = "US",
                             metadata = TRUE, cache = FALSE)
      expect_s3_class(result, "wid_df")
      expect_false(is.null(attr(result, "wid_meta")))
    }
  )
})

test_that("download_wid metadata=TRUE with empty catalogue sets NULL wid_meta", {
  testthat::with_mocked_bindings(
    `.cache`               = function(a, ...) NULL,
    `.fetch`               = function(...) NULL,
    `.get_variables_areas` = function(...) NULL,
    {
      suppressWarnings(
        result <- download_wid("sptinc992j", areas = "US",
                               metadata = TRUE, cache = FALSE)
      )
      expect_s3_class(result, "wid_df")
      expect_null(attr(result, "wid_meta"))
    }
  )
})

# ── .fetch internals ──────────────────────────────────────────────────────────

test_that(".fetch warns when .get_variables_areas returns empty", {
  testthat::with_mocked_bindings(
    `.get_variables_areas` = function(...) NULL,
    {
      expect_warning(
        widr:::.fetch("sptinc992j", "US", "all", "992", "j", FALSE, FALSE),
        "No data found"
      )
    }
  )
})

test_that(".fetch warns when .filter_vars removes all rows", {
  # Variables are returned from catalogue but none match the requested percentile.
  vars <- data.frame(
    variable = "sptinc992j", country = "US", percentile = "p99p100",
    age = "992", pop = "j", stringsAsFactors = FALSE
  )
  testthat::with_mocked_bindings(
    `.get_variables_areas` = function(...) vars,
    `.filter_vars`         = function(...) vars[0L, ],   # empty after filter
    {
      expect_warning(
        widr:::.fetch("sptinc992j", "US", "p0p50", "992", "j", FALSE, FALSE),
        "No data after"
      )
    }
  )
})

test_that(".fetch warns when .chunk_fetch returns no rows", {
  vars <- data.frame(
    variable = "sptinc992j", country = "US", percentile = "p99p100",
    age = "992", pop = "j", stringsAsFactors = FALSE
  )
  testthat::with_mocked_bindings(
    `.get_variables_areas` = function(...) vars,
    `.chunk_fetch`         = function(...) NULL,
    {
      expect_warning(
        widr:::.fetch("sptinc992j", "US", "all", "992", "j", FALSE, FALSE),
        "API returned no data"
      )
    }
  )
})

test_that(".fetch verbose=TRUE emits catalogue and variable count messages", {
  vars <- data.frame(
    variable = "sptinc992j", country = "US", percentile = "p99p100",
    age = "992", pop = "j", api_code = "sptinc992j_p99p100_992_j",
    stringsAsFactors = FALSE
  )
  testthat::with_mocked_bindings(
    `.get_variables_areas` = function(...) vars,
    `.chunk_fetch`         = function(...) data.frame(
      indicator = "sptinc992j_p99p100_992_j", country = "US",
      year = "2000", value = 0.2, stringsAsFactors = FALSE),
    `.parse_indicator`     = function(df) {
      df$variable   <- "sptinc992j"
      df$percentile <- "p99p100"
      df$indicator  <- NULL
      df
    },
    {
      msgs <- character(0L)
      withCallingHandlers(
        widr:::.fetch("sptinc992j", "US", "all", "992", "j", FALSE, TRUE),
        message = function(m) {
          msgs <<- c(msgs, conditionMessage(m))
          invokeRestart("muffleMessage")
        }
      )
      expect_true(any(grepl("catalogue|variable", msgs, ignore.case = TRUE)))
    }
  )
})

# ── wid_query / wid_filter / wid_fetch ───────────────────────────────────────

test_that("wid_query defaults match download_wid formals", {
  q <- wid_query()
  expect_equal(q$areas,  "all")
  expect_equal(q$years,  "all")
  expect_equal(q$ages,   "992")
  expect_true(q$cache)
  expect_false(q$verbose)
})

test_that("wid_query sets parameters", {
  q <- wid_query(indicators = "sptinc992j", areas = "US")
  expect_equal(q$indicators, "sptinc992j")
  expect_equal(q$areas,      "US")
})

test_that("wid_query warns on unknown params", {
  expect_warning(wid_query(foo = "bar"), "Unknown parameter")
})

test_that("wid_filter updates fields and preserves others", {
  q  <- wid_query(areas = "US", years = 2000:2010)
  q2 <- wid_filter(q, areas = c("US", "FR"))
  expect_equal(q2$areas, c("US", "FR"))
  expect_equal(q2$years, 2000:2010)
})

test_that("wid_filter errors on non-query", {
  expect_error(wid_filter(list()), "inherits")
})

test_that("wid_fetch executes query and returns wid_df", {
  mock <- .wid()
  testthat::with_mocked_bindings(
    `.cache` = function(a, ...) NULL,
    `.fetch` = function(...) mock,
    {
      q <- wid_query(indicators = "sptinc992j", areas = "US", cache = FALSE)
      expect_s3_class(wid_fetch(q), "wid_df")
    }
  )
})

test_that("wid_fetch applies overrides", {
  mock <- .wid()
  testthat::with_mocked_bindings(
    `.cache` = function(a, ...) NULL,
    `.fetch` = function(...) mock,
    {
      q <- wid_query(indicators = "sptinc992j", cache = FALSE)
      expect_s3_class(wid_fetch(q, areas = "FR"), "wid_df")
    }
  )
})

test_that("print.wid_query outputs header", {
  expect_output(print(wid_query(areas = "US")), "<wid_query>")
})

test_that("print.wid_query truncates long vectors", {
  expect_output(print(wid_query(years = 1900:2100)), "\\.\\.\\.")
})

# ── cache (wid_cache / wid_cache_list / wid_cache_clear) ─────────────────────

test_that("wid_cache round-trips data", {
  tmp <- tempfile()
  on.exit(unlink(tmp, recursive = TRUE))
  assignInNamespace(".CACHE_DIR", tmp, "widr")
  key <- widr:::.cache_key("test")
  wid_cache("set", key, 42L)
  expect_equal(wid_cache("get", key), 42L)
})

test_that("wid_cache returns NULL for missing key", {
  tmp <- tempfile()
  on.exit(unlink(tmp, recursive = TRUE))
  assignInNamespace(".CACHE_DIR", tmp, "widr")
  expect_null(wid_cache("get", "nosuchkey"))
})

test_that("wid_cache_clear removes files", {
  tmp <- tempfile()
  on.exit(unlink(tmp, recursive = TRUE))
  assignInNamespace(".CACHE_DIR", tmp, "widr")
  wid_cache("set", "k1", 1L)
  wid_cache("set", "k2", 2L)
  n <- wid_cache_clear()
  expect_equal(n, 2L)
  expect_length(wid_cache_list(), 0L)
})

test_that("wid_cache errors on unknown action", {
  expect_error(wid_cache("bad"), "Unknown action")
})