# test-temporal.R
# Tests for temporal filtering functionality across Python modules and R interface

.get_date_extractor <- function() {
  asa_test_date_extractor()
}

.mock_article_html <- '
  <html>
  <head>
    <script type="application/ld+json">
      {"@type": "Article", "datePublished": "2023-06-15"}
    </script>
  </head>
  <body></body>
  </html>
'

.verify_constraint <- function(date_extractor, date_after = NULL, date_before = NULL, html_content = .mock_article_html) {
  date_extractor$verify_date_constraint(
    url = "https://example.com/article",
    date_after = date_after,
    date_before = date_before,
    html_content = html_content
  )
}

.get_research_graph_module <- function() {
  asa_test_import_research_graph(required_modules = ASA_TEST_LANGGRAPH_CORE_MODULES)
}

.create_stopper <- function(target_items = NULL, plateau_rounds = 2L, novelty_min = 0.1) {
  research <- .get_research_graph_module()
  config <- research$ResearchConfig(
    target_items = target_items,
    plateau_rounds = plateau_rounds,
    novelty_min = novelty_min
  )
  research$create_stopper_node(config)
}

# ============================================================================
# R Interface Validation Tests (research.R)
# ============================================================================

test_that("asa_enumerate validates temporal$time_filter", {

  # Invalid time filter values
  expect_error(
    asa_enumerate(query = "test", temporal = list(time_filter = "x")),
    regexp = "time_filter"
  )

  expect_error(
    asa_enumerate(query = "test", temporal = list(time_filter = "day")),
    regexp = "time_filter"
  )

  expect_error(
    asa_enumerate(query = "test", temporal = list(time_filter = "year")),
    regexp = "time_filter"
  )

  expect_error(
    asa_enumerate(query = "test", temporal = list(time_filter = 123)),
    regexp = "time_filter"
  )
})

test_that("asa_enumerate validates temporal$after date format", {
  # Invalid month
  expect_error(
    asa_enumerate(query = "test", temporal = list(after = "2024-13-01")),
    regexp = "after.*valid|after.*ISO"
  )

  # Invalid format (not ISO 8601)
  expect_error(
    asa_enumerate(query = "test", temporal = list(after = "January 1, 2024")),
    regexp = "after.*valid|after.*ISO"
  )

  # Random string
  expect_error(
    asa_enumerate(query = "test", temporal = list(after = "not-a-date")),
    regexp = "after.*valid|after.*ISO"
  )

  # Note: Slash format "2024/01/15" is accepted by R's as.Date(), so no error
})

test_that("asa_enumerate validates temporal$before date format", {
  # Invalid day
  expect_error(
    asa_enumerate(query = "test", temporal = list(before = "2024-02-30")),
    regexp = "before.*valid|before.*ISO"
  )

  # Invalid month
  expect_error(
    asa_enumerate(query = "test", temporal = list(before = "2024-00-15")),
    regexp = "before.*valid|before.*ISO"
  )

  # Note: Slash format "2024/01/15" is accepted by R's as.Date(), so no error
})

test_that("asa_enumerate validates temporal$strictness", {
  expect_error(
    asa_enumerate(query = "test", temporal = list(strictness = "very_strict")),
    regexp = "strictness"
  )

  expect_error(
    asa_enumerate(query = "test", temporal = list(strictness = "relaxed")),
    regexp = "strictness"
  )

  expect_error(
    asa_enumerate(query = "test", temporal = list(strictness = "none")),
    regexp = "strictness"
  )
})

test_that(".create_research_config includes temporal parameters", {
  # Use a date range compatible with time_filter="w" to avoid warnings
  today <- Sys.Date()
  after <- as.character(today - 3)
  before <- as.character(today - 1)

  config <- asa:::.create_research_config(
    workers = 2,
    max_rounds = 5,
    budget = list(queries = 25),
    stop_policy = list(),
    sources = list(web = TRUE),
    temporal = list(
      time_filter = "w",
      after = after,
      before = before,
      strictness = "strict",
      use_wayback = TRUE
    )
  )

  expect_equal(config$time_filter, "w")
  expect_equal(config$date_after, after)
  expect_equal(config$date_before, before)
  expect_equal(config$temporal_strictness, "strict")
  expect_true(config$use_wayback)
})

test_that(".create_research_config handles NULL temporal", {
  config <- asa:::.create_research_config(
    workers = 2,
    max_rounds = 5,
    budget = list(),
    stop_policy = list(),
    sources = list(),
    temporal = NULL
  )

  expect_null(config$time_filter)
  expect_null(config$date_after)
  expect_null(config$date_before)
})

test_that(".create_research_config handles partial temporal", {
  # Only time_filter
  config1 <- asa:::.create_research_config(
    workers = 2, max_rounds = 5,
    budget = list(), stop_policy = list(), sources = list(),
    temporal = list(time_filter = "m")
  )
  expect_equal(config1$time_filter, "m")
  expect_null(config1$date_after)

  # Only after date
  config2 <- asa:::.create_research_config(
    workers = 2, max_rounds = 5,
    budget = list(), stop_policy = list(), sources = list(),
    temporal = list(after = "2020-01-01")
  )
  expect_null(config2$time_filter)
  expect_equal(config2$date_after, "2020-01-01")
})

test_that(".create_research_config defaults strictness to best_effort", {
  config <- asa:::.create_research_config(
    workers = 2, max_rounds = 5,
    budget = list(), stop_policy = list(), sources = list(),
    temporal = list(after = "2020-01-01")  # No strictness specified
  )

  expect_equal(config$temporal_strictness, "best_effort")
})

test_that(".create_research_config defaults use_wayback to FALSE", {
  config <- asa:::.create_research_config(
    workers = 2, max_rounds = 5,
    budget = list(), stop_policy = list(), sources = list(),
    temporal = list(after = "2020-01-01")  # No use_wayback specified
  )

  expect_false(config$use_wayback)
})

# ============================================================================
# Date Extractor Tests (shared/temporal_date_extractor.py)
# ============================================================================

test_that("_parse_date_string handles ISO 8601 formats", {
  date_extractor <- asa_test_date_extractor()

  # ISO 8601 with Z suffix
  expect_equal(date_extractor$`_parse_date_string`("2024-01-15T10:30:00Z"), "2024-01-15")

  # Simple ISO date
  expect_equal(date_extractor$`_parse_date_string`("2024-01-15"), "2024-01-15")

  # ISO 8601 without timezone
  expect_equal(date_extractor$`_parse_date_string`("2024-06-20T14:30:00"), "2024-06-20")
})

test_that("_parse_date_string handles human-readable formats", {
  date_extractor <- asa_test_date_extractor()

  # Full month name
  expect_equal(date_extractor$`_parse_date_string`("January 15, 2024"), "2024-01-15")

  # Abbreviated month
  expect_equal(date_extractor$`_parse_date_string`("Jan 15, 2024"), "2024-01-15")

  # Day-first format
  expect_equal(date_extractor$`_parse_date_string`("15 January 2024"), "2024-01-15")
})

test_that("_parse_date_string handles edge cases", {
  date_extractor <- asa_test_date_extractor()

  # Empty string
  result <- date_extractor$`_parse_date_string`("")
  expect_true(is.null(result) || is.na(result))

  # Invalid dates should return NULL
  result <- date_extractor$`_parse_date_string`("not a date")
  expect_true(is.null(result) || is.na(result))

  # Year-only extraction (returns YYYY-01-01)
  result <- date_extractor$`_parse_date_string`("Published in 2023")
  expect_equal(result, "2023-01-01")
})

test_that("_extract_from_url extracts dates from URL patterns", {
  date_extractor <- asa_test_date_extractor()

  # YYYY/MM/DD pattern
  result <- date_extractor$`_extract_from_url`("https://example.com/2024/01/15/article")
  expect_true(length(result) > 0)
  expect_equal(result[[1]]$date, "2024-01-15")
  expect_equal(result[[1]]$source, "url_pattern_ymd")

  # YYYY/MM pattern (day defaults to 01)
  result <- date_extractor$`_extract_from_url`("https://example.com/2024/03/news")
  expect_true(length(result) > 0)
  expect_equal(result[[1]]$date, "2024-03-01")

  # No date in URL
  result <- date_extractor$`_extract_from_url`("https://example.com/about")
  expect_equal(length(result), 0)
})

test_that("_extract_from_url handles ISO date pattern in URL", {
  date_extractor <- asa_test_date_extractor()

  result <- date_extractor$`_extract_from_url`("https://example.com/post-2024-01-15-title")
  expect_true(length(result) > 0)
  expect_equal(result[[1]]$date, "2024-01-15")
})

test_that("_extract_from_url handles boundary dates", {
  date_extractor <- asa_test_date_extractor()

  # Leap year February 29 (valid)
  result <- date_extractor$`_extract_from_url`("https://example.com/2024/02/29/leap")
  expect_true(length(result) > 0)
  expect_equal(result[[1]]$date, "2024-02-29")

  # December 31 (valid)
  result <- date_extractor$`_extract_from_url`("https://example.com/2023/12/31/news")
  expect_true(length(result) > 0)
  expect_equal(result[[1]]$date, "2023-12-31")
})

test_that("verify_date_constraint validates after constraint", {
  date_extractor <- .get_date_extractor()

  # Date passes "after" constraint (2023-06-15 > 2023-01-01)
  result <- .verify_constraint(date_extractor, date_after = "2023-01-01")
  expect_true(result$passes)
  expect_equal(result$extracted_date, "2023-06-15")

  # Date fails "after" constraint (2023-06-15 < 2024-01-01)
  result <- .verify_constraint(date_extractor, date_after = "2024-01-01")
  expect_false(result$passes)

  # Boundary test: extracted date exactly equals "after" date
  result <- .verify_constraint(date_extractor, date_after = "2023-06-15")
  # Documents the inclusive/exclusive boundary semantics
  expect_true(is.logical(result$passes) || is.null(result$passes),
    info = "Boundary case: date == after should have well-defined behavior")
})

test_that("verify_date_constraint validates before constraint boundary", {
  date_extractor <- .get_date_extractor()

  # Boundary test: extracted date exactly equals "before" date
  result <- .verify_constraint(date_extractor, date_before = "2023-06-15")
  # Documents the inclusive/exclusive boundary semantics
  expect_true(is.logical(result$passes) || is.null(result$passes),
    info = "Boundary case: date == before should have well-defined behavior")
})

test_that("verify_date_constraint validates before constraint", {
  date_extractor <- .get_date_extractor()

  # Date passes "before" constraint (2023-06-15 < 2024-01-01)
  result <- .verify_constraint(date_extractor, date_before = "2024-01-01")
  expect_true(result$passes)

  # Date fails "before" constraint (2023-06-15 is not before 2023-01-01)
  result <- .verify_constraint(date_extractor, date_before = "2023-01-01")
  expect_false(result$passes)
})

test_that("verify_date_constraint handles no date found", {
  date_extractor <- asa_test_date_extractor()

  mock_html <- '<html><body><p>No date here</p></body></html>'

  result <- date_extractor$verify_date_constraint(
    url = "https://example.com/nodates",
    date_after = "2023-01-01",
    html_content = mock_html
  )

  # Should be undetermined (NULL passes)
  expect_true(is.null(result$passes) || is.na(result$passes))
  expect_equal(result$reason, "no_date_found")
})

test_that("_extract_from_json_ld extracts datePublished", {
  date_extractor <- asa_test_date_extractor()
  bs4 <- reticulate::import("bs4")

  html <- '
    <html>
    <head>
      <script type="application/ld+json">
        {"@type": "NewsArticle", "datePublished": "2023-08-20T10:00:00Z", "dateModified": "2023-08-21T14:00:00Z"}
      </script>
    </head>
    </html>
  '
  soup <- bs4$BeautifulSoup(html, "html.parser")
  results <- date_extractor$`_extract_from_json_ld`(soup)

  expect_true(length(results) >= 1)
  # Find the datePublished result
  dates <- sapply(results, function(r) r$date)
  expect_true("2023-08-20" %in% dates)
})

test_that("_extract_from_meta_tags extracts article:published_time", {
  date_extractor <- asa_test_date_extractor()
  bs4 <- reticulate::import("bs4")

  html <- '
    <html>
    <head>
      <meta property="article:published_time" content="2023-05-10T09:00:00+00:00">
    </head>
    </html>
  '
  soup <- bs4$BeautifulSoup(html, "html.parser")
  results <- date_extractor$`_extract_from_meta_tags`(soup)

  expect_true(length(results) >= 1)
  expect_equal(results[[1]]$date, "2023-05-10")
  expect_true(results[[1]]$confidence >= 0.85)
})

test_that("DateExtractionConfig has correct defaults", {
  date_extractor <- asa_test_date_extractor()

  config <- date_extractor$DateExtractionConfig()

  expect_equal(config$timeout, 10.0)
  expect_equal(config$max_retries, 2L)
  expect_equal(config$min_confidence, 0.3)
})

test_that("extract_publication_date delegates fetch to shared make_request settings", {
  date_extractor <- asa_test_date_extractor()

  py <- reticulate::py
  py$td_mod <- date_extractor
  reticulate::py_run_string(paste0(
    "_old_make_request_temporal = td_mod.make_request\n",
    "_temporal_make_request_calls = []\n",
    "def _stub_make_request(url, method='GET', params=None, data=None, headers=None,\n",
    "                       timeout=30.0, max_retries=3, retry_delay=2.0,\n",
    "                       proxy=None, user_agent=None):\n",
    "    _temporal_make_request_calls.append({\n",
    "        'url': url,\n",
    "        'method': method,\n",
    "        'timeout': timeout,\n",
    "        'max_retries': max_retries,\n",
    "        'retry_delay': retry_delay,\n",
    "        'proxy': proxy,\n",
    "        'user_agent': user_agent,\n",
    "    })\n",
    "    class _Resp:\n",
    "        text = '<html><head><meta property=\"article:published_time\" content=\"2024-02-20\" /></head></html>'\n",
    "    return _Resp()\n",
    "td_mod.make_request = _stub_make_request\n"
  ))
  on.exit(
    reticulate::py_run_string(
      "if '_old_make_request_temporal' in globals():\n    td_mod.make_request = _old_make_request_temporal\n"
    ),
    add = TRUE
  )

  config <- date_extractor$DateExtractionConfig(
    timeout = 3.5,
    max_retries = 4L,
    retry_delay = 1.25,
    user_agent = "ASA-Test-Agent/1.0",
    proxy = "http://127.0.0.1:8080"
  )
  extracted <- date_extractor$extract_publication_date(
    url = "https://example.com/article",
    config = config
  )

  calls <- reticulate::py_to_r(py$`_temporal_make_request_calls`)
  expect_equal(length(calls), 1L)
  expect_equal(as.character(calls[[1]]$method), "GET")
  expect_equal(as.character(calls[[1]]$url), "https://example.com/article")
  expect_equal(as.numeric(calls[[1]]$timeout), 3.5)
  expect_equal(as.integer(calls[[1]]$max_retries), 4L)
  expect_equal(as.numeric(calls[[1]]$retry_delay), 1.25)
  expect_equal(as.character(calls[[1]]$proxy), "http://127.0.0.1:8080")
  expect_equal(as.character(calls[[1]]$user_agent), "ASA-Test-Agent/1.0")

  expect_equal(as.character(extracted$date), "2024-02-20")
  expect_match(as.character(extracted$source), "^meta\\.")
})

test_that("extract_publication_date handles shared make_request failure paths", {
  date_extractor <- asa_test_date_extractor()

  py <- reticulate::py
  py$td_mod <- date_extractor
  reticulate::py_run_string(paste0(
    "import requests\n",
    "_old_make_request_temporal_fail = td_mod.make_request\n",
    "def _failing_make_request(*args, **kwargs):\n",
    "    raise requests.exceptions.RequestException('forced fetch failure')\n",
    "td_mod.make_request = _failing_make_request\n"
  ))
  on.exit(
    reticulate::py_run_string(
      "if '_old_make_request_temporal_fail' in globals():\n    td_mod.make_request = _old_make_request_temporal_fail\n"
    ),
    add = TRUE
  )

  no_url_date <- date_extractor$extract_publication_date(
    url = "https://example.com/article-no-date"
  )
  expect_true(is.null(no_url_date$date) || is.na(no_url_date$date))
  expect_equal(as.character(no_url_date$source), "fetch_failed")

  url_date_fallback <- date_extractor$extract_publication_date(
    url = "https://example.com/2024/01/15/story"
  )
  expect_equal(as.character(url_date_fallback$date), "2024-01-15")
  expect_equal(as.character(url_date_fallback$source), "url_pattern_ymd")
})

test_that("filter_results_by_date handles empty list", {
  date_extractor <- asa_test_date_extractor()

  result <- date_extractor$filter_results_by_date(
    results = list(),
    date_after = "2023-01-01",
    date_before = NULL
  )

  expect_equal(length(result[[1]]), 0)  # passed
  expect_equal(length(result[[2]]), 0)  # failed
  expect_equal(length(result[[3]]), 0)  # undetermined
})

# ============================================================================
# Wikidata Tool Tests (tools/entity_wikidata_tool.py)
# ============================================================================

test_that("_build_temporal_filter generates correct SPARQL", {
  wikidata <- asa_test_import_langgraph_module("tools.entity_wikidata_tool", required_files = "shared/temporal_date_extractor.py", required_modules = ASA_TEST_LANGCHAIN_CORE_MODULES, initialize = FALSE)

  # No dates - empty filter
  result <- wikidata$`_build_temporal_filter`(NULL, NULL)
  expect_equal(result, "")

  # After date only
  result <- wikidata$`_build_temporal_filter`("2020-01-01", NULL)
  expect_match(result, "FILTER")
  expect_match(result, "2020-01-01")
  expect_match(result, "xsd:dateTime")

  # Before date only
  result <- wikidata$`_build_temporal_filter`(NULL, "2024-01-01")
  expect_match(result, "FILTER")
  expect_match(result, "2024-01-01")

  # Both dates
  result <- wikidata$`_build_temporal_filter`("2020-01-01", "2024-01-01")
  expect_match(result, "2020-01-01")
  expect_match(result, "2024-01-01")
})

test_that("_inject_temporal_filter modifies query correctly", {
  wikidata <- asa_test_import_langgraph_module("tools.entity_wikidata_tool", required_files = "shared/temporal_date_extractor.py", required_modules = ASA_TEST_LANGCHAIN_CORE_MODULES, initialize = FALSE)

  sample_query <- '
    SELECT ?person WHERE {
      ?person wdt:P39 wd:Q4416090 .
      SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
    }
  '

  # No dates - query unchanged
  result <- wikidata$`_inject_temporal_filter`(sample_query, NULL, NULL)
  expect_equal(result, sample_query)

  # With dates - filter injected
  result <- wikidata$`_inject_temporal_filter`(sample_query, "2020-01-01", NULL)
  expect_match(result, "FILTER")
  expect_match(result, "SERVICE wikibase:label")  # SERVICE still there
})

test_that("WikidataSearchTool._parse_temporal_from_query parses dates", {
  wikidata <- asa_test_import_langgraph_module("tools.entity_wikidata_tool", required_files = "shared/temporal_date_extractor.py", required_modules = ASA_TEST_LANGCHAIN_CORE_MODULES, initialize = FALSE)

  tool <- wikidata$WikidataSearchTool()

  # After date only
  result <- tool$`_parse_temporal_from_query`("entity_type:us_senators after:2020-01-01")
  expect_equal(result[[2]], "2020-01-01")  # date_after

  # Before date only
  result <- tool$`_parse_temporal_from_query`("entity_type:countries before:2024-06-01")
  expect_equal(result[[3]], "2024-06-01")  # date_before

  # Both dates
  result <- tool$`_parse_temporal_from_query`("entity_type:fortune500 after:2020-01-01 before:2023-12-31")
  expect_equal(result[[2]], "2020-01-01")
  expect_equal(result[[3]], "2023-12-31")
})

test_that("create_wikidata_tool accepts temporal parameters", {
  wikidata <- asa_test_import_langgraph_module("tools.entity_wikidata_tool", required_files = "shared/temporal_date_extractor.py", required_modules = ASA_TEST_LANGCHAIN_CORE_MODULES, initialize = FALSE)

  tool <- wikidata$create_wikidata_tool(
    date_after = "2020-01-01",
    date_before = "2024-01-01"
  )

  expect_equal(tool$date_after, "2020-01-01")
  expect_equal(tool$date_before, "2024-01-01")
})

test_that("create_wikidata_tool handles NULL temporal parameters", {
  wikidata <- asa_test_import_langgraph_module("tools.entity_wikidata_tool", required_files = "shared/temporal_date_extractor.py", required_modules = ASA_TEST_LANGCHAIN_CORE_MODULES, initialize = FALSE)

  tool <- wikidata$create_wikidata_tool()

  expect_true(is.null(tool$date_after) || tool$date_after == "")
  expect_true(is.null(tool$date_before) || tool$date_before == "")
})

test_that("Wikidata templates are opt-in and can be loaded explicitly", {
  wikidata <- asa_test_import_langgraph_module("tools.entity_wikidata_tool", required_files = "shared/temporal_date_extractor.py", required_modules = ASA_TEST_LANGCHAIN_CORE_MODULES, initialize = FALSE)

  python_path <- asa_test_python_path(required_files = c("tools/entity_wikidata_tool.py", "shared/resources/wikidata_templates.json"))
  template_path <- file.path(python_path, "shared/resources/wikidata_templates.json")
  if (!file.exists(template_path)) {
    skip("shared/resources/wikidata_templates.json not found in python path")
  }

  old_env <- Sys.getenv("ASA_WIKIDATA_TEMPLATES", unset = NA_character_)
  on.exit({
    if (is.na(old_env)) {
      Sys.unsetenv("ASA_WIKIDATA_TEMPLATES")
    } else {
      Sys.setenv(ASA_WIKIDATA_TEMPLATES = old_env)
    }
    try(wikidata$configure_entity_templates(template_path = ""), silent = TRUE)
  }, add = TRUE)

  Sys.unsetenv("ASA_WIKIDATA_TEMPLATES")
  wikidata$configure_entity_templates(template_path = "")
  expect_length(wikidata$get_known_entity_types(), 0L)

  tool <- wikidata$create_wikidata_tool(template_path = template_path)
  expect_false(is.null(tool))
  known_types <- wikidata$get_known_entity_types()
  expect_true("us_senators" %in% known_types)
})

test_that("WikidataConfig has correct defaults", {
  wikidata <- asa_test_import_langgraph_module("tools.entity_wikidata_tool", required_files = "shared/temporal_date_extractor.py", required_modules = ASA_TEST_LANGCHAIN_CORE_MODULES, initialize = FALSE)

  config <- wikidata$WikidataConfig()

  expect_equal(config$timeout, 60.0)
  expect_equal(config$max_results, 500L)
  expect_equal(config$retry_count, 3L)
  expect_equal(config$retry_delay, 2.0)
})

# ============================================================================
# Wayback Tool Tests (tools/archive_wayback_tool.py)
# ============================================================================

test_that("WaybackSearchTool._parse_query extracts URL and dates", {
  wayback <- asa_test_import_langgraph_module("tools.archive_wayback_tool", required_files = "shared/temporal_date_extractor.py", required_modules = ASA_TEST_LANGCHAIN_CORE_MODULES, initialize = FALSE)

  tool <- wayback$WaybackSearchTool()

  # URL with before date
  result <- tool$`_parse_query`("url:https://example.com before:2020-01-01")
  expect_equal(result[[1]], "https://example.com")
  expect_equal(result[[3]], "2020-01-01")  # before_date

  # URL with after date
  result <- tool$`_parse_query`("url:https://example.com/page after:2018-06-15")
  expect_equal(result[[1]], "https://example.com/page")
  expect_equal(result[[2]], "2018-06-15")  # after_date

  # Both dates
  result <- tool$`_parse_query`("url:https://test.org after:2019-01-01 before:2022-12-31")
  expect_equal(result[[1]], "https://test.org")
  expect_equal(result[[2]], "2019-01-01")
  expect_equal(result[[3]], "2022-12-31")
})

test_that("WaybackSearchTool._parse_query handles URL without prefix", {
  wayback <- asa_test_import_langgraph_module("tools.archive_wayback_tool", required_files = "shared/temporal_date_extractor.py", required_modules = ASA_TEST_LANGCHAIN_CORE_MODULES, initialize = FALSE)

  tool <- wayback$WaybackSearchTool()

  result <- tool$`_parse_query`("https://example.com before:2023-01-01")
  expect_equal(result[[1]], "https://example.com")
  expect_equal(result[[3]], "2023-01-01")
})

test_that("WaybackConfig has correct defaults", {
  wayback <- asa_test_import_langgraph_module("tools.archive_wayback_tool", required_files = "shared/temporal_date_extractor.py", required_modules = ASA_TEST_LANGCHAIN_CORE_MODULES, initialize = FALSE)

  config <- wayback$WaybackConfig()

  expect_equal(config$timeout, 30.0)
  expect_equal(config$max_results, 100L)
  expect_equal(config$retry_count, 3L)
  expect_equal(config$retry_delay, 2.0)
  expect_equal(config$request_delay, 0.5)
})

test_that("create_wayback_tool returns configured tool", {
  wayback <- asa_test_import_langgraph_module("tools.archive_wayback_tool", required_files = "shared/temporal_date_extractor.py", required_modules = ASA_TEST_LANGCHAIN_CORE_MODULES, initialize = FALSE)

  custom_config <- wayback$WaybackConfig(timeout = 60.0, max_results = 50L)
  tool <- wayback$create_wayback_tool(config = custom_config)

  expect_equal(tool$name, "wayback_search")
  expect_equal(tool$config$timeout, 60.0)
  expect_equal(tool$config$max_results, 50L)
})

test_that("create_wayback_tool uses default config when not specified", {
  wayback <- asa_test_import_langgraph_module("tools.archive_wayback_tool", required_files = "shared/temporal_date_extractor.py", required_modules = ASA_TEST_LANGCHAIN_CORE_MODULES, initialize = FALSE)

  tool <- wayback$create_wayback_tool()

  expect_equal(tool$name, "wayback_search")
  expect_equal(tool$config$timeout, 30.0)
})

# ============================================================================
# ResearchConfig Tests (workflows/research_graph_workflow.py)
# ============================================================================

test_that("ResearchConfig has temporal field defaults", {
  research <- asa_test_import_langgraph_module("workflows.research_graph_workflow", required_files = "shared/temporal_date_extractor.py", required_modules = ASA_TEST_LANGGRAPH_CORE_MODULES, initialize = FALSE)

  config <- research$ResearchConfig()

  expect_true(is.null(config$time_filter))
  expect_true(is.null(config$date_after))
  expect_true(is.null(config$date_before))
  expect_equal(config$temporal_strictness, "best_effort")
  expect_false(config$use_wayback)
})

test_that("ResearchConfig accepts temporal parameters", {
  research <- asa_test_import_langgraph_module("workflows.research_graph_workflow", required_files = "shared/temporal_date_extractor.py", required_modules = ASA_TEST_LANGGRAPH_CORE_MODULES, initialize = FALSE)

  config <- research$ResearchConfig(
    time_filter = "m",
    date_after = "2023-01-01",
    date_before = "2024-01-01",
    temporal_strictness = "strict",
    use_wayback = TRUE
  )

  expect_equal(config$time_filter, "m")
  expect_equal(config$date_after, "2023-01-01")
  expect_equal(config$date_before, "2024-01-01")
  expect_equal(config$temporal_strictness, "strict")
  expect_true(config$use_wayback)
})

test_that("ResearchConfig accepts all valid time_filter values", {
  research <- asa_test_import_langgraph_module("workflows.research_graph_workflow", required_files = "shared/temporal_date_extractor.py", required_modules = ASA_TEST_LANGGRAPH_CORE_MODULES, initialize = FALSE)

  for (filter in c("d", "w", "m", "y")) {
    config <- research$ResearchConfig(time_filter = filter)
    expect_equal(config$time_filter, filter)
  }
})

test_that("strict temporal search fails closed when verifier is unavailable", {
  research <- asa_test_import_langgraph_module("workflows.research_graph_workflow", required_files = "shared/temporal_date_extractor.py", required_modules = ASA_TEST_LANGGRAPH_CORE_MODULES, initialize = FALSE)

  py <- reticulate::py
  py$asa_temporal_research_mod <- research
  reticulate::py_run_string(paste(
    "_asa_prev_verify_temporal = getattr(asa_temporal_research_mod, 'verify_date_constraint', None)",
    "asa_temporal_research_mod.verify_date_constraint = None",
    sep = "\n"
  ))
  on.exit(
    reticulate::py_run_string(
      "asa_temporal_research_mod.verify_date_constraint = _asa_prev_verify_temporal"
    ),
    add = TRUE
  )

  searcher <- research$create_searcher_node(
    llm = NULL,
    tools = list(),
    wikidata_tool = NULL,
    research_config = research$ResearchConfig(
      use_web = TRUE,
      use_wikidata = FALSE,
      date_after = "2024-01-01",
      temporal_strictness = "strict"
    )
  )

  out <- reticulate::py_to_r(searcher(list(
    query = "Find recent entities",
    schema = list(name = "string"),
    config = list(
      use_web = TRUE,
      use_wikidata = FALSE,
      date_after = "2024-01-01",
      temporal_strictness = "strict"
    ),
    seen_hashes = list(),
    results = list(),
    novelty_history = list(),
    queries_used = 0L,
    tokens_used = 0L,
    input_tokens = 0L,
    output_tokens = 0L,
    round_number = 0L,
    start_time = as.numeric(Sys.time())
  )))

  expect_identical(as.character(out$status), "failed")
  expect_identical(
    as.character(out$stop_reason),
    "strict_temporal_verifier_unavailable"
  )
  expect_true(is.list(out$errors))
  expect_identical(as.character(out$errors[[1]]$stage), "temporal_verification")
})

# ============================================================================
# Stopper Node Tests (workflows/research_graph_workflow.py)
# ============================================================================

test_that("stopper honors target_items", {
  stopper <- .create_stopper(target_items = 2L, plateau_rounds = 2L, novelty_min = 0.1)

  state <- list(
    round_number = 1L,
    queries_used = 0L,
    seen_hashes = list(a = TRUE, b = TRUE),
    novelty_history = list(0.5)
  )

  result <- stopper(state)
  expect_equal(result$status, "complete")
  expect_equal(result$stop_reason, "target_reached")
  expect_equal(result$completion_gate$completion_status, "complete")
})

test_that("stopper stops on novelty plateau", {
  stopper <- .create_stopper(target_items = NULL, plateau_rounds = 2L, novelty_min = 0.1)

  state <- list(
    round_number = 3L,
    queries_used = 0L,
    seen_hashes = list(a = TRUE, b = TRUE, c = TRUE),
    novelty_history = list(0.05, 0.08)
  )

  result <- stopper(state)
  expect_equal(result$status, "complete")
  expect_equal(result$stop_reason, "novelty_plateau")
  expect_equal(result$completion_gate$completion_status, "partial")
})

test_that("stopper continues when novelty remains above threshold", {
  stopper <- .create_stopper(target_items = NULL, plateau_rounds = 2L, novelty_min = 0.1)

  state <- list(
    round_number = 2L,
    queries_used = 0L,
    seen_hashes = list(a = TRUE),
    novelty_history = list(0.2, 0.15)
  )

  result <- stopper(state)
  expect_equal(result$status, "searching")
  expect_equal(result$completion_gate$completion_status, "searching")
})

# ============================================================================
# ExtractedDate Dataclass Tests (shared/temporal_date_extractor.py)
# ============================================================================

test_that("ExtractedDate dataclass works correctly", {
  date_extractor <- asa_test_date_extractor()

  extracted <- date_extractor$ExtractedDate(
    date = "2023-06-15",
    confidence = 0.95,
    source = "json_ld.datePublished",
    raw_value = "2023-06-15T10:00:00Z"
  )

  expect_equal(extracted$date, "2023-06-15")
  expect_equal(extracted$confidence, 0.95)
  expect_equal(extracted$source, "json_ld.datePublished")
  expect_equal(extracted$raw_value, "2023-06-15T10:00:00Z")
})

# ============================================================================
# Edge Case Tests
# ============================================================================

test_that("Date parsing handles ambiguous MM/DD vs DD/MM formats", {
  date_extractor <- asa_test_date_extractor()

  # Unambiguous MM/DD/YYYY (month > 12 impossible as day)
  result <- date_extractor$`_parse_date_string`("01/15/2024")
  expect_equal(result, "2024-01-15")

  # Unambiguous (month=12, day=31)
  result <- date_extractor$`_parse_date_string`("12/31/2024")
  expect_equal(result, "2024-12-31")

  # Truly ambiguous: 03/04/2024 could be March 4 (US) or April 3 (EU)
  # Parser should return a valid date (US MM/DD convention expected)
  result <- date_extractor$`_parse_date_string`("03/04/2024")
  expect_true(!is.null(result) && !is.na(result))
  expect_equal(result, "2024-03-04")  # Expect US MM/DD interpretation

  # Another ambiguous case: 05/06/2024
  result <- date_extractor$`_parse_date_string`("05/06/2024")
  expect_equal(result, "2024-05-06")  # Expect US MM/DD interpretation
})

test_that("SPARQL filter injection preserves query structure", {
  wikidata <- asa_test_import_langgraph_module("tools.entity_wikidata_tool", required_files = "shared/temporal_date_extractor.py", required_modules = ASA_TEST_LANGCHAIN_CORE_MODULES, initialize = FALSE)

  query_with_service <- '
    SELECT ?x WHERE {
      ?x wdt:P31 wd:Q5 .
      SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
    }
  '

  result <- wikidata$`_inject_temporal_filter`(query_with_service, "2020-01-01", "2024-01-01")

  # FILTER should come before SERVICE
  filter_pos <- regexpr("FILTER", result)
  service_pos <- regexpr("SERVICE wikibase:label", result)
  expect_true(filter_pos[[1]] < service_pos[[1]])

  # Query should still be syntactically valid (has SELECT, WHERE, closing braces)
  expect_match(result, "SELECT")
  expect_match(result, "WHERE")
})

test_that("URL extraction handles edge cases in paths", {
  date_extractor <- asa_test_date_extractor()

  # Date at end of URL
  result <- date_extractor$`_extract_from_url`("https://example.com/news/2024/06/15")
  expect_true(length(result) > 0)

  # Date with trailing slash
  result <- date_extractor$`_extract_from_url`("https://example.com/2024/06/15/")
  expect_true(length(result) > 0)

  # Multiple date-like patterns in URL
  result <- date_extractor$`_extract_from_url`("https://example.com/archive/2023/2024/06/15")
  expect_true(length(result) >= 1)
  # Verify the extracted date is one of the plausible dates from the URL
  extracted_dates <- sapply(result, function(r) r$date)
  expect_true(
    any(extracted_dates %in% c("2024-06-15", "2023-01-01")),
    info = paste("Expected date from URL, got:", paste(extracted_dates, collapse = ", "))
  )
})
