# ---- .wid_df ---------------------------------------------------------------

test_that(".wid_df returns empty wid_df with correct columns", {
  d <- widr:::.wid_df()
  expect_s3_class(d, "wid_df")
  expect_equal(nrow(d), 0L)
  expect_named(d, c("country","variable","percentile","year","value","age","pop"))
})

# ---- .get_key --------------------------------------------------------------

test_that(".get_key errors with no key set and no env var", {
  withr::with_envvar(list(WID_API_KEY = NA_character_), {
    if (exists("key", envir = widr:::.wid_env)) rm("key", envir = widr:::.wid_env)
    expect_error(widr:::.get_key(), "No WID API key")
  })
})

test_that(".get_key reads WID_API_KEY env var and base64-encodes it", {
  withr::with_envvar(list(WID_API_KEY = "envkey"), {
    if (exists("key", envir = widr:::.wid_env)) rm("key", envir = widr:::.wid_env)
    expect_equal(rawToChar(base64enc::base64decode(widr:::.get_key())), "envkey")
  })
})

test_that("wid_set_key stores key invisibly", {
  on.exit(rm(list = "key", envir = widr:::.wid_env), add = TRUE)
  expect_invisible(wid_set_key("testkey"))
  expect_equal(get("key", envir = widr:::.wid_env), "testkey")
})

# ---- .wid_get --------------------------------------------------------------

test_that(".wid_get calls request pipeline and parses JSON", {
  with_mocked_bindings(
    request           = function(url) structure(list(), class = "httr2_request"),
    req_url_query     = function(r, ...) r,
    req_headers       = function(r, ...) r,
    req_timeout       = function(r, ...) r,
    req_retry         = function(r, ...) r,
    req_perform       = function(r) structure(list(), class = "httr2_response"),
    resp_check_status = function(r) invisible(r),
    resp_body_string  = function(r, ...) '{"foo":"bar"}',
    .get_key          = function() "dGVzdA==",
    { expect_equal(widr:::.wid_get("/test")$foo, "bar") },
    .package = "widr"
  )
})

# ---- .cache ----------------------------------------------------------------

test_that(".cache set/get round-trips a value", {
  tmp <- tempfile(); on.exit(unlink(tmp, recursive = TRUE))
  assignInNamespace(".CACHE_DIR", tmp, "widr")
  k <- widr:::.cache_key("x")
  widr:::.cache("set", k, 42L)
  expect_equal(widr:::.cache("get", k), 42L)
})

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

test_that(".cache clear removes files and returns count", {
  tmp <- tempfile(); on.exit(unlink(tmp, recursive = TRUE))
  assignInNamespace(".CACHE_DIR", tmp, "widr")
  widr:::.cache("set", "k1", 1L); widr:::.cache("set", "k2", 2L)
  expect_equal(widr:::.cache("clear"), 2L)
  expect_length(widr:::.cache("list"), 0L)
})

test_that(".cache list returns rds filenames", {
  tmp <- tempfile(); on.exit(unlink(tmp, recursive = TRUE))
  assignInNamespace(".CACHE_DIR", tmp, "widr")
  widr:::.cache("set", "k", 1L)
  expect_length(widr:::.cache("list"), 1L)
})

test_that(".cache errors on unknown action", {
  expect_error(widr:::.cache("bad"), "Unknown action")
})

test_that("wid_cache_list and wid_cache_clear delegate to .cache", {
  tmp <- tempfile(); on.exit(unlink(tmp, recursive = TRUE))
  assignInNamespace(".CACHE_DIR", tmp, "widr")
  widr:::.cache("set", "x", 1L)
  expect_length(wid_cache_list(), 1L)
  expect_equal(wid_cache_clear(), 1L)
})

# ---- .recode ---------------------------------------------------------------

test_that(".recode maps known values and passes through unknowns", {
  lkp <- c(US = "United States", FR = "France")
  expect_equal(widr:::.recode(c("US","XX"), lkp), c("United States","XX"))
})

# ---- .check ----------------------------------------------------------------

test_that(".check errors on non-wid_df, empty df, and type mismatch", {
  expect_error(widr:::.check(data.frame()), "wid_df")
  expect_error(widr:::.check(widr:::.wid_df()), "empty")
  expect_error(widr:::.check(.wid(type = "s"), series_type = "t"), "threshold")
})

test_that(".check passes invisibly on valid data and matching type", {
  expect_invisible(widr:::.check(.wid()))
  expect_invisible(widr:::.check(.wid(type = "t"), series_type = "t"))
})

# ---- .resolve_var ----------------------------------------------------------

test_that(".resolve_var handles all four paths", {
  d <- .wid()
  v <- unique(d$variable)
  expect_equal(widr:::.resolve_var(d, v), v)
  expect_error(widr:::.resolve_var(d, "xyz"), "not found")
  expect_equal(widr:::.resolve_var(d, NULL), v)
  d2 <- d; d2$variable[1L] <- "aptinc992j"
  expect_error(widr:::.resolve_var(d2, NULL), "Multiple variables")
})

# ---- .empty ----------------------------------------------------------------

test_that(".empty produces zero-row data.frame with named columns", {
  df <- widr:::.empty("a","b")
  expect_equal(nrow(df), 0L)
  expect_named(df, c("a","b"))
})

# ---- .recode_imp -----------------------------------------------------------

test_that(".recode_imp maps all known codes, unknowns, and NA/NULL", {
  expect_equal(widr:::.recode_imp("region"),    "regional imputation")
  expect_equal(widr:::.recode_imp("survey"),    "adjusted surveys")
  expect_equal(widr:::.recode_imp("tax"),       "surveys and tax data")
  expect_equal(widr:::.recode_imp("full"),      "surveys and tax microdata")
  expect_equal(widr:::.recode_imp("rescaling"), "rescaled fiscal income")
  expect_equal(widr:::.recode_imp("other"),     "other")
  expect_identical(widr:::.recode_imp(NULL), NA_character_)
  expect_identical(widr:::.recode_imp(NA),   NA_character_)
})

# ---- .strip_extrap ---------------------------------------------------------

test_that(".strip_extrap returns df unchanged when extrapolation absent", {
  df <- data.frame(year = c("2000","2001"), value = 1:2, stringsAsFactors = FALSE)
  expect_equal(widr:::.strip_extrap(df, list(extrapolation = NULL)), df)
  expect_equal(widr:::.strip_extrap(df, list(extrapolation = NA)),   df)
  expect_equal(widr:::.strip_extrap(df, list(extrapolation = "")),   df)
})

test_that(".strip_extrap removes extrapolated years and retains data_points", {
  df     <- data.frame(year = as.character(2000:2004), value = 1:5, stringsAsFactors = FALSE)
  extrap <- jsonlite::toJSON(matrix(c(2001L, 2004L), nrow = 1L))
  # Without data_points: years 2002-2004 dropped (extrap range is 2002..2004)
  out1 <- widr:::.strip_extrap(df, list(extrapolation = extrap, data_points = NULL))
  expect_equal(out1$year, c("2000","2001"))
  # With data_point 2002: retained
  dp   <- jsonlite::toJSON("2002")
  out2 <- widr:::.strip_extrap(df, list(extrapolation = extrap, data_points = dp))
  expect_true("2002" %in% out2$year)
  expect_false("2003" %in% out2$year)
})

# ---- .filter_vars ----------------------------------------------------------

test_that(".filter_vars filters by perc, ages, pop; 'all' skips filter", {
  v <- data.frame(percentile = c("p0p50","p50p100","p0p50"),
                  age = c("992","992","999"), pop = c("j","j","i"),
                  stringsAsFactors = FALSE)
  expect_equal(nrow(widr:::.filter_vars(v, "all", "all", "all")), 3L)
  expect_equal(nrow(widr:::.filter_vars(v, "p0p50", "all", "all")), 2L)
  expect_equal(nrow(widr:::.filter_vars(v, "all", "999", "all")), 1L)
  expect_equal(nrow(widr:::.filter_vars(v, "all", "all", "i")), 1L)
})

# ---- .parse_indicator ------------------------------------------------------

test_that(".parse_indicator builds canonical variable and extracts percentile", {
  df  <- data.frame(indicator = "sptinc_p99p100_992_j", country = "US",
                    year = "2020", value = 0.2, stringsAsFactors = FALSE)
  out <- widr:::.parse_indicator(df)
  expect_equal(out$variable, "sptinc992j")
  expect_equal(out$percentile, "p99p100")
  expect_null(out$indicator)
})

test_that(".parse_indicator errors on malformed indicator", {
  df <- data.frame(indicator = "bad", country = "US", year = "2020", value = 0,
                   stringsAsFactors = FALSE)
  expect_error(widr:::.parse_indicator(df), "indicator format")
})

# ---- .get_variables_areas --------------------------------------------------

test_that(".get_variables_areas parses json into data.frame", {
  # API structure: list(variable -> list(country -> list(list(perc, age, pop))))
  json <- list(
    sptinc992j = list(
      US = list(list("p90p100", "992", "j"))
    )
  )
  with_mocked_bindings(
    .wid_get = function(...) json,
    {
      out <- widr:::.get_variables_areas("sptinc992j", "US")
    },
    .package = "widr"
  )
  expect_equal(nrow(out), 1L)
  expect_equal(out$variable,   "sptinc992j")
  expect_equal(out$country,    "US")
  expect_equal(out$percentile, "p90p100")
})

test_that(".get_variables_areas returns empty frame when json is empty", {
  with_mocked_bindings(
    .wid_get = function(...) list(),
    { out <- widr:::.get_variables_areas("sptinc992j", "US") },
    .package = "widr"
  )
  expect_equal(nrow(out), 0L)
  expect_named(out, c("variable","country","percentile","age","pop"))
})

test_that(".get_variables_areas unwraps single-element outer list", {
  # length(json)==1 triggers json <- json[[1L]]
  json <- list(list(
    sptinc992j = list(US = list(list("p90p100", "992", "j")))
  ))
  with_mocked_bindings(
    .wid_get = function(...) json,
    { out <- widr:::.get_variables_areas("sptinc992j", "US") },
    .package = "widr"
  )
  expect_equal(nrow(out), 1L)
})

# ---- .get_data_variables ---------------------------------------------------

test_that(".get_data_variables parses json and returns data.frame", {
  # API: list(api_code -> list(list(country -> list(values=list(list(yr,val)), meta=...))))
  json <- list(
    `sptinc_p90p100_992_j` = list(
      list(US = list(
        values = list(list(2020L, 0.4)),
        meta   = list(extrapolation = NULL, data_points = NULL)
      ))
    )
  )
  with_mocked_bindings(
    .wid_get = function(...) json,
    { out <- widr:::.get_data_variables("US", "sptinc_p90p100_992_j") },
    .package = "widr"
  )
  expect_equal(out$country, "US")
  expect_equal(out$value,   0.4)
})

test_that(".get_data_variables returns empty frame when json is empty", {
  with_mocked_bindings(
    .wid_get = function(...) list(),
    { out <- widr:::.get_data_variables("US", "sptinc_p90p100_992_j") },
    .package = "widr"
  )
  expect_equal(nrow(out), 0L)
  expect_named(out, c("indicator","country","year","value"))
})

test_that(".get_data_variables strips extrapolations when no_extrapolation=TRUE", {
  extrap <- jsonlite::toJSON(matrix(c(2019L, 2021L), nrow = 1L))
  json <- list(
    `sptinc_p90p100_992_j` = list(
      list(US = list(
        values = list(list(2018L, 0.3), list(2020L, 0.4), list(2021L, 0.5)),
        meta   = list(extrapolation = extrap, data_points = NULL)
      ))
    )
  )
  with_mocked_bindings(
    .wid_get = function(...) json,
    { out <- widr:::.get_data_variables("US", "sptinc_p90p100_992_j",
                                        no_extrapolation = TRUE) },
    .package = "widr"
  )
  expect_false("2021" %in% out$year)
  expect_true("2018"  %in% out$year)
})

# ---- .get_metadata_variables -----------------------------------------------

# Exact structure mirroring .get_metadata_variables parsing logic:
# jv[[1L]][[1L]] = jname  (list with $shortname, $simpledes)
# jv[[3L]][[1L]] = jpop   (list with $longdes)
# jv[[4L]][[1L]] = jage   -> as.character() -> named char vector -> $fullname
# jv[[5L]][[1L]] = junits (list of {country, country_name})
# jv[[6L]][[1L]][[1L]] = notes list, each element has alpha2 + note fields
.mock_meta <- function(imputation = "survey", has_note = TRUE) {
  note <- if (has_note)
    list(list(alpha2 = "US", source = "Tax data", method = "DFL",
              data_quality = "high", imputation = imputation))
  else
    list(list(alpha2 = "US"))   # present but missing note fields -> n$source etc = NULL
  list(list(metadata_func = list(
    list(sptinc992j = list(
      list(list(shortname = "ptinc", simpledes = "Pre-tax income")),  # jv[[1]]
      list(),                                                          # jv[[2]]
      list(list(longdes = "equal-split adults")),                      # jv[[3]]
      list(c(fullname = "20+")),                                       # jv[[4]] named char vec
      list(list(list(country = "US", country_name = "United States"))),# jv[[5]]
      list(list(note))                                                 # jv[[6]]
    ))
  )))
}

test_that(".get_metadata_variables parses json and decodes imputation", {
  with_mocked_bindings(
    .wid_get = function(...) .mock_meta(),
    { out <- widr:::.get_metadata_variables("US", "sptinc992j") },
    .package = "widr"
  )
  expect_s3_class(out, "data.frame")
  expect_equal(out$source[1L],     "Tax data")
  expect_equal(out$imputation[1L], "adjusted surveys")
})

test_that(".get_metadata_variables returns NA fields when note has no data", {
  # note exists but fields missing
  with_mocked_bindings(
    .wid_get = function(...) .mock_meta(has_note = FALSE),
    { out <- widr:::.get_metadata_variables("US", "sptinc992j") },
    .package = "widr"
  )
  expect_true(is.na(out$source[1L]))
})

test_that(".get_metadata_variables returns empty frame when jmeta is empty", {
  with_mocked_bindings(
    .wid_get = function(...) list(list(metadata_func = list())),
    { out <- widr:::.get_metadata_variables("US", "sptinc992j") },
    .package = "widr"
  )
  expect_equal(nrow(out), 0L)
  expect_named(out, c("variable","country","countryname","shortname","shortdes",
                      "pop","age","source","method","quality","imputation"))
})

# ---- .chunk_fetch ----------------------------------------------------------

test_that(".chunk_fetch batches into correct number of chunks", {
  vars <- data.frame(
    variable   = paste0("v", 1:12), percentile = "p90p100",
    age = "992", pop = "j", country = "US",
    api_code   = paste0("v", 1:12, "_p90p100_992_j"),
    stringsAsFactors = FALSE)
  calls <- 0L
  widr:::.chunk_fetch(vars, size = 5L, function(countries, codes) {
    calls <<- calls + 1L
    data.frame(country="US", indicator=codes[1L], year="2020", value=0.1,
               stringsAsFactors=FALSE)
  })
  expect_true(calls >= 2L)
})

test_that(".chunk_fetch returns combined data.frame from all chunks", {
  vars <- data.frame(
    variable = "sptinc992j", percentile = "p90p100",
    age = "992", pop = "j", country = "US",
    api_code = "sptinc_p90p100_992_j", stringsAsFactors = FALSE)
  out <- widr:::.chunk_fetch(vars, size = 10L, function(countries, codes)
    data.frame(country="US", indicator=codes[1L], year="2020", value=0.4,
               stringsAsFactors=FALSE))
  expect_s3_class(out, "data.frame")
  expect_equal(out$value, 0.4)
})

# ---- .auto_title -----------------------------------------------------------

test_that(".auto_title builds label with countries appended (<= 3)", {
  t <- widr:::.auto_title("sptinc992j", c("US","FR"))
  expect_type(t, "character")
  expect_match(t, "US")
})

test_that(".auto_title omits countries when > 3", {
  t <- widr:::.auto_title("sptinc992j", c("US","FR","DE","CN"))
  expect_false(grepl("US", t, fixed = TRUE))
})

test_that(".auto_title falls back to code on decode error or unknown concept", {
  with_mocked_bindings(
    wid_decode = function(...) stop("bad"),
    { expect_match(widr:::.auto_title("sptinc992j", "US"), "sptinc992j") },
    .package = "widr"
  )
  with_mocked_bindings(
    wid_decode = function(...) list(series_type="s", concept="ZZZZZ", age="992", pop="j"),
    { expect_match(widr:::.auto_title("sptinc992j", "US"), "sptinc992j") },
    .package = "widr"
  )
})

# ---- .common_year ----------------------------------------------------------

test_that(".common_year returns max shared year and errors when none", {
  d <- data.frame(country=c("US","US","FR","FR"), year=c("2019","2020","2020","2021"),
                  stringsAsFactors=FALSE)
  expect_equal(widr:::.common_year(d), "2020")
  d2 <- data.frame(country=c("US","FR"), year=c("2019","2020"), stringsAsFactors=FALSE)
  expect_error(widr:::.common_year(d2), "No common year")
})