test_that("wb_language", {
  local_mocked_bindings(
    worldbank = function(...) readRDS(test_path("fixtures", "wb-language.rds"))
  )
  actual <- wb_language()
  expect_s3_class(actual, "data.frame")
  expect_shape(actual, dim = c(23L, 3L))
  expect_all_true(map_lgl(actual, is.character))
  expect_all_true(map_lgl(actual, \(x) all(nzchar(x))))
  expect_false(any(map_lgl(actual, has_ws)))
})

test_that("wb_lending_type", {
  local_mocked_bindings(
    worldbank = function(...) readRDS(test_path("fixtures", "wb-lending-type.rds"))
  )
  actual <- wb_lending_type()
  expect_s3_class(actual, "data.frame")
  expect_shape(actual, dim = c(4L, 3L))
  expect_all_true(map_lgl(actual, is.character))
  expect_all_true(map_lgl(actual, \(x) all(nzchar(x))))
  expect_false(any(map_lgl(actual, has_ws)))
})

test_that("wb_income_level", {
  local_mocked_bindings(
    worldbank = function(...) readRDS(test_path("fixtures", "wb-income-level.rds"))
  )
  actual <- wb_income_level()
  expect_s3_class(actual, "data.frame")
  expect_shape(actual, dim = c(7L, 3L))
  expect_all_true(map_lgl(actual, is.character))
  expect_all_true(map_lgl(actual, \(x) all(nzchar(x))))
  expect_false(any(map_lgl(actual, has_ws)))
})

test_that("wb_source", {
  local_mocked_bindings(
    worldbank = function(...) readRDS(test_path("fixtures", "wb-source.rds"))
  )
  actual <- wb_source()
  expect_s3_class(actual, "data.frame")
  expect_shape(actual, dim = c(69L, 9L))
  for (x in actual) {
    if (is.character(x)) {
      expect_all_true(nzchar(x))
      expect_false(has_ws(x))
    }
  }
})

test_that("wb_topic", {
  local_mocked_bindings(
    worldbank = function(...) readRDS(test_path("fixtures", "wb-topic.rds"))
  )
  actual <- wb_topic()
  expect_s3_class(actual, "data.frame")
  expect_shape(actual, dim = c(21L, 3L))
  for (x in actual) {
    if (is.character(x)) {
      expect_all_true(nzchar(x))
      expect_false(has_ws(x))
    }
  }
})

test_that("wb_region", {
  local_mocked_bindings(
    worldbank = function(...) readRDS(test_path("fixtures", "wb-region.rds"))
  )
  actual <- wb_region()
  expect_s3_class(actual, "data.frame")
  expect_shape(actual, dim = c(44L, 4L))
  for (x in actual) {
    if (is.character(x)) {
      expect_all_true(nzchar(x))
      expect_false(has_ws(x))
    }
  }
})

test_that("wb_country", {
  local_mocked_bindings(
    worldbank = function(...) readRDS(test_path("fixtures", "wb-country.rds"))
  )
  actual <- wb_country()
  expect_s3_class(actual, "data.frame")
  expect_shape(actual, dim = c(296L, 18L))
  for (x in actual) {
    if (is.character(x)) {
      expect_all_true(nzchar(x))
      expect_false(has_ws(x))
    }
  }
})

test_that("wb_indicator", {
  local_mocked_bindings(
    worldbank = function(...) readRDS(test_path("fixtures", "wb-indicator.rds"))
  )
  actual <- wb_indicator()
  expect_s3_class(actual, "data.frame")
  expect_shape(actual, dim = c(100L, 9L))
  for (x in actual) {
    if (is.character(x)) {
      expect_all_true(nzchar(x))
      expect_false(has_ws(x))
    }
  }
})

test_that("wb_country_indicator", {
  local_mocked_bindings(
    worldbank = function(...) readRDS(test_path("fixtures", "wb-country-indicator.rds"))
  )
  actual <- wb_country_indicator()
  expect_s3_class(actual, "data.frame")
  expect_shape(actual, dim = c(63L, 10L))
  expect_type(actual$date, "integer")
  for (x in actual) {
    if (is.character(x)) {
      expect_all_true(nzchar(x))
      expect_false(has_ws(x))
    }
  }
})

test_that("wb_lending_type input validation works", {
  # type should be a three letter code or NULL
  expect_error(wb_lending_type(character()))
  expect_error(wb_lending_type(""))
  expect_error(wb_lending_type("ab"))
  expect_error(wb_lending_type("abcd"))
  expect_error(wb_lending_type(c("abc", "abcd")))
  expect_error(wb_lending_type(NA))
  expect_error(wb_lending_type(1L))
  expect_error(wb_lending_type(TRUE))
  # lang should be two letter code
  expect_error(wb_lending_type(lang = "a"))
  expect_error(wb_lending_type(lang = "abc"))
  expect_error(wb_lending_type(lang = c("a", "b")))
  expect_error(wb_lending_type(lang = TRUE))
  expect_error(wb_lending_type(lang = 1L))
})

test_that("wb_income_level input validation works", {
  # income should be a three letter code or NULL
  expect_error(wb_income_level(character()))
  expect_error(wb_income_level(""))
  expect_error(wb_income_level("ab"))
  expect_error(wb_income_level("abcd"))
  expect_error(wb_income_level(c("abc", "abcd")))
  expect_error(wb_income_level(NA))
  expect_error(wb_income_level(1L))
  expect_error(wb_income_level(TRUE))
  # lang should be two letter code
  expect_error(wb_income_level(lang = "a"))
  expect_error(wb_income_level(lang = "abc"))
  expect_error(wb_income_level(lang = c("a", "b")))
  expect_error(wb_income_level(lang = TRUE))
  expect_error(wb_income_level(lang = 1L))
})

test_that("wb_source input validation works", {
  # source should be a character vector or NULL
  expect_error(wb_source(character()))
  expect_error(wb_source(NA))
  expect_error(wb_source(1L))
  expect_error(wb_source(TRUE))
  # lang should be two letter code
  expect_error(wb_source(lang = "a"))
  expect_error(wb_source(lang = "abc"))
  expect_error(wb_source(lang = c("a", "b")))
  expect_error(wb_source(lang = TRUE))
  expect_error(wb_source(lang = 1L))
})

test_that("wb_topic input validation works", {
  # topic should be a character vector or NULL
  expect_error(wb_topic(character()))
  expect_error(wb_topic(NA))
  expect_error(wb_topic(1L))
  expect_error(wb_topic(TRUE))
  # lang should be two letter code
  expect_error(wb_topic(lang = "a"))
  expect_error(wb_topic(lang = "abc"))
  expect_error(wb_topic(lang = c("a", "b")))
  expect_error(wb_topic(lang = TRUE))
  expect_error(wb_topic(lang = 1L))
})

test_that("wb_region input validation works", {
  # region should be a character vector or NULL
  expect_error(wb_region(character()))
  expect_error(wb_region(NA))
  expect_error(wb_region(1L))
  expect_error(wb_region(TRUE))
  # lang should be two letter code
  expect_error(wb_region(lang = "a"))
  expect_error(wb_region(lang = "abc"))
  expect_error(wb_region(lang = c("a", "b")))
  expect_error(wb_region(lang = TRUE))
  expect_error(wb_region(lang = 1L))
})

test_that("wb_country input validation works", {
  # country should be a character vector with 2/3 letters or NULL
  expect_error(wb_country("a"))
  expect_error(wb_country("abcd"))
  expect_error(wb_country(c("ab", "abcd")))
  expect_error(wb_country(character()))
  expect_error(wb_country(NA))
  expect_error(wb_country(1L))
  expect_error(wb_country(TRUE))
  # lang should be two letter code
  expect_error(wb_country(lang = "a"))
  expect_error(wb_country(lang = "abc"))
  expect_error(wb_country(lang = c("a", "b")))
  expect_error(wb_country(lang = TRUE))
  expect_error(wb_country(lang = 1L))
})

test_that("wb_indicator input validation works", {
  # indicator should be a string or NULL
  expect_error(wb_indicator(c("ab", "abcd")))
  expect_error(wb_indicator(character()))
  expect_error(wb_indicator(NA))
  expect_error(wb_indicator(1L))
  expect_error(wb_indicator(TRUE))
  # lang should be two letter code
  expect_error(wb_indicator(lang = "a"))
  expect_error(wb_indicator(lang = "abc"))
  expect_error(wb_indicator(lang = c("a", "b")))
  expect_error(wb_indicator(lang = TRUE))
  expect_error(wb_indicator(lang = 1L))
})

test_that("wb_search filters indicators by pattern", {
  indicators <- readRDS(test_path("fixtures", "wb-indicator.rds"))
  local_mocked_bindings(
    worldbank = function(...) indicators
  )

  catalog <- wb_indicator()

  actual <- wb_search("HCount")
  expect_s3_class(actual, "data.frame")
  expect_identical(names(actual), names(catalog))
  expect_true(nrow(actual) > 0L)
  hit <- grepl("HCount", catalog$id, ignore.case = TRUE) |
    grepl("HCount", catalog$name, ignore.case = TRUE) |
    grepl("HCount", catalog$source_note, ignore.case = TRUE)
  expect_identical(nrow(actual), sum(hit, na.rm = TRUE))

  expect_identical(nrow(wb_search("zzz_no_match_zzz")), 0L)

  expect_true(
    nrow(wb_search("hcount")) >= nrow(wb_search("hcount", ignore.case = FALSE))
  )

  expect_identical(
    wb_search("HCount", catalog = catalog),
    wb_search("HCount")
  )
})

test_that("wdi_pivot_long pivots wide WDI data to long format", {
  wide <- data.frame(
    country_name = c("Germany", "United States"),
    country_code = c("DEU", "USA"),
    indicator_name = c("GDP (current US$)", "GDP (current US$)"),
    indicator_code = c("NY.GDP.MKTP.CD", "NY.GDP.MKTP.CD"),
    x1960 = c(NA, 543300000000),
    x1961 = c(NA, 563300000000),
    x1962 = c(NA, 605100000000),
    x = NA,
    check.names = FALSE
  )

  long <- wdi_pivot_long(wide)
  expect_s3_class(long, "data.frame")
  expect_identical(
    names(long),
    c("country_name", "country_code", "indicator_name", "indicator_code", "year", "value")
  )
  expect_identical(nrow(long), 6L)
  expect_identical(long$year, rep(1960:1962, each = 2L))
  expect_identical(long$country_code, rep(c("DEU", "USA"), times = 3L))
  expect_identical(
    long$value,
    c(NA, 543300000000, NA, 563300000000, NA, 605100000000)
  )
  expect_false("X" %in% names(long))
})

test_that("wb_bulk input validation works", {
  expect_error(wb_bulk(timeout = -1))
  expect_error(wb_bulk(timeout = "a"))
  expect_error(wb_bulk(timeout = NA))
  expect_error(wb_bulk(timeout = c(1, 2)))
  expect_error(wb_bulk(timeout = NULL))
})

test_that("wb_search input validation works", {
  catalog <- readRDS(test_path("fixtures", "wb-indicator.rds"))
  local_mocked_bindings(
    worldbank = function(...) catalog
  )
  expect_error(wb_search(NULL))
  expect_error(wb_search(c("a", "b")))
  expect_error(wb_search(NA))
  expect_error(wb_search(1L))
  expect_error(wb_search("GDP", fields = character()))
  expect_error(wb_search("GDP", fields = "not_a_column"), "not_a_column")
  expect_error(wb_search("GDP", catalog = list()))
})

test_that("wb_country_indicator input validation works", {
  # indicator should be a non-empty character vector
  expect_error(wb_country_indicator(indicator = NULL))
  expect_error(wb_country_indicator(indicator = 1L))
  expect_error(wb_country_indicator(indicator = NA))
  # country should be a character vector with 2/3 letters or NULL
  expect_error(wb_country_indicator(country = "a"))
  expect_error(wb_country_indicator(country = "abcd"))
  expect_error(wb_country_indicator(country = c("ab", "abcd")))
  expect_error(wb_country_indicator(country = character()))
  expect_error(wb_country_indicator(country = NA))
  expect_error(wb_country_indicator(country = 1L))
  expect_error(wb_country_indicator(country = TRUE))
  # lang should be two letter code
  expect_error(wb_country_indicator(lang = "a"))
  expect_error(wb_country_indicator(lang = "abc"))
  expect_error(wb_country_indicator(lang = c("a", "b")))
  expect_error(wb_country_indicator(lang = TRUE))
  expect_error(wb_country_indicator(lang = 1L))
})

test_that("wb_data returns an empty data.frame when there are no observations", {
  local_mocked_bindings(worldbank = function(...) NULL)
  actual <- wb_data("NY.GDP.MKTP.CD", "US")
  expect_s3_class(actual, "data.frame")
  expect_shape(actual, dim = c(0L, 10L))
})

test_that("wb_data preserves mixed-frequency dates as character", {
  observation <- function(date, indicator) {
    list(
      date = date,
      indicator = list(id = indicator, value = indicator),
      country = list(id = "US", value = "United States"),
      countryiso3code = "USA",
      value = 1,
      unit = "",
      obs_status = "",
      decimal = 0L
    )
  }
  local_mocked_bindings(
    worldbank_seq = function(resource, ...) {
      lapply(resource, function(x) {
        indicator <- sub(".*/", "", x)
        date <- if (indicator == "ANNUAL") "2020" else "2020Q1"
        list(observation(date, indicator))
      })
    }
  )

  actual <- wb_data(c("annual", "quarterly"), "US")
  reversed <- wb_data(c("quarterly", "annual"), "US")

  expect_identical(actual$date, c("2020", "2020Q1"))
  expect_identical(reversed$date, c("2020Q1", "2020"))
  expect_identical(wb_data(c("annual", "annual"), "US")$date, c(2020L, 2020L))
  expect_identical(wb_data(c("quarterly", "quarterly"), "US")$date, c("2020Q1", "2020Q1"))
})

test_that("wb_data returns footnotes only when asked", {
  captured <- NULL
  local_mocked_bindings(
    worldbank = function(...) {
      captured <<- list(...)$footnote
      list(wb_observation(footnote = "Based on data from LSMS."), wb_observation())
    }
  )

  expect_false("footnote" %in% names(wb_data("SI.POV.DDAY", "ALB")))
  expect_null(captured)

  actual <- wb_data("SI.POV.DDAY", "ALB", footnote = TRUE)
  expect_identical(captured, "Y")
  expect_identical(actual$footnote, c("Based on data from LSMS.", NA))
})

test_that("wb_data returns footnotes for multiple indicators", {
  local_mocked_bindings(
    worldbank_seq = function(resource, ...) {
      lapply(resource, function(x) {
        indicator <- sub(".*/", "", x)
        list(wb_observation(indicator, footnote = paste("note for", indicator)))
      })
    }
  )
  actual <- wb_data(c("a", "b"), "ALB", footnote = TRUE)
  expect_identical(actual$footnote, c("note for A", "note for B"))
})

test_that("wb_data mrv and gapfill validation works", {
  expect_error(wb_data(mrv = 3, start_date = 2020), "mrv")
  expect_error(wb_data(mrv = 3, end_date = 2020), "mrv")
  expect_error(wb_data(gapfill = TRUE), "gapfill")
  expect_error(wb_data(mrv = -1))
  expect_error(wb_data(mrv = "a"))
  expect_error(wb_data(footnote = "yes"))
  expect_error(wb_data(footnote = NA))
})

test_that("error parsing works", {
  skip_if_offline()
  skip_on_cran()
  skip_on_ci()

  expect_snapshot(wb_indicator("something.wrong"), error = TRUE)
})
