test_that("convergence check detects max_iter criterion", {
  result <- check_convergence(
    g_current = c(0.1, 0.1),
    f_current = 10,
    f_previous = 11,
    x_current = c(1, 2),
    x_previous = c(0.9, 1.9),
    iter = 1000,
    max_iter = 1000
  )

  expect_true(result$converged)
  expect_equal(result$reason, "max_iter")
})

test_that("convergence check detects gradient_abs criterion", {
  result <- check_convergence(
    g_current = c(1e-10, -5e-11),  # max |g| = 1e-10 < 1e-8
    f_current = 10,
    f_previous = 11,
    x_current = c(1, 2),
    x_previous = c(0.9, 1.9),
    iter = 5,
    tol_grad = 1e-8
  )

  expect_true(result$converged)
  expect_equal(result$reason, "gradient_abs")
})

test_that("convergence check detects gradient_rel criterion", {
  result <- check_convergence(
    g_current = c(1e-5, -5e-6),  # max |g| = 1e-5
    f_current = 1e6,  # Large f value
    f_previous = 1e6 + 1,
    x_current = c(1, 2),
    x_previous = c(0.9, 1.9),
    iter = 5,
    tol_grad = 1e-6,  # Won't trigger abs criterion (1e-5 > 1e-6)
    tol_rel_grad = 1e-10  # But 1e-5 < 1e-10 * 1e6 = 1e-4
  )

  expect_true(result$converged)
  expect_equal(result$reason, "gradient_rel")
})

test_that("convergence check detects objective_abs criterion", {
  # Note: objective convergence requires gradient to be reasonably small (< 10 * tol_grad)
  # Use 1e-6 gradient which is:
  # - > 1e-7 (tol_grad), so won't trigger gradient_abs
  # - > 1e-12 * 10 = 1e-11 (tol_rel_grad * f_scale), so won't trigger gradient_rel
  # - < 10 * 1e-7 = 1e-6, so passes reasonably_small check
  result <- check_convergence(
    g_current = c(5e-7, 5e-7),  # Between 1e-7 and 1e-6
    f_current = 10.0,
    f_previous = 10.0 + 1e-13,  # Change < 1e-12
    x_current = c(1, 2),
    x_previous = c(0.9, 1.9),  # Large enough to not trigger parameter criterion
    iter = 5,
    tol_grad = 1e-7,  # gradient is 5e-7, not small enough for gradient_abs
    tol_rel_grad = 1e-12,  # Very strict relative gradient check
    tol_obj = 1e-12
  )

  expect_true(result$converged)
  expect_equal(result$reason, "objective_abs")
})

test_that("convergence check detects objective_rel criterion", {
  # Note: objective convergence requires gradient to be reasonably small (< 10 * tol_grad)
  # Use 5e-7 gradient which is:
  # - > 1e-7 (tol_grad), so won't trigger gradient_abs
  # - > 1e-14 * 1e6 = 1e-8 (tol_rel_grad * f_scale), so won't trigger gradient_rel
  # - < 10 * 1e-7 = 1e-6, so passes reasonably_small check
  result <- check_convergence(
    g_current = c(5e-7, 5e-7),
    f_current = 1e6,
    f_previous = 1e6 + 1e-2,  # Change = 1e-2, relative = 1e-2/1e6 = 1e-8
    x_current = c(1, 2),
    x_previous = c(0.9, 1.9),  # Large enough to avoid parameter criterion
    iter = 5,
    tol_grad = 1e-7,  # gradient is 5e-7, not small enough for gradient_abs
    tol_rel_grad = 1e-14,  # Very strict relative gradient check (5e-7 > 1e-14 * 1e6)
    tol_obj = 1e-20,  # Won't trigger abs criterion
    tol_rel_obj = 1e-7  # But 1e-8 < 1e-7
  )

  expect_true(result$converged)
  expect_equal(result$reason, "objective_rel")
})

test_that("convergence check detects parameter criterion", {
  # Note: parameter convergence requires gradient to be reasonably small (< 10 * tol_grad)
  # Use 5e-7 gradient which is:
  # - > 1e-7 (tol_grad), so won't trigger gradient_abs
  # - > 1e-12 * 10 = 1e-11 (tol_rel_grad * f_scale), so won't trigger gradient_rel
  # - < 10 * 1e-7 = 1e-6, so passes reasonably_small check
  result <- check_convergence(
    g_current = c(5e-7, 5e-7),
    f_current = 10,
    f_previous = 10.1,
    x_current = c(1.0, 2.0),
    x_previous = c(1.0 + 1e-10, 2.0 - 5e-11),  # max |dx| = 1e-10 < 1e-8
    iter = 5,
    tol_grad = 1e-7,  # gradient is 5e-7, not small enough for gradient_abs
    tol_rel_grad = 1e-12,  # Very strict relative gradient check
    tol_param = 1e-8
  )

  expect_true(result$converged)
  expect_equal(result$reason, "parameter")
})

test_that("convergence check returns FALSE when no criteria met", {
  result <- check_convergence(
    g_current = c(0.1, 0.1),
    f_current = 10,
    f_previous = 9,
    x_current = c(1, 2),
    x_previous = c(0.9, 1.9),
    iter = 5,
    max_iter = 1000,
    tol_grad = 1e-8,
    tol_rel_grad = 1e-6,
    tol_obj = 1e-12,
    tol_rel_obj = 1e-8,
    tol_param = 1e-8
  )

  expect_false(result$converged)
  expect_equal(result$reason, "")
})

test_that("convergence check handles first iteration correctly", {
  # First iteration: no previous values, should only check gradient and max_iter
  result <- check_convergence(
    g_current = c(0.1, 0.1),
    f_current = 10,
    f_previous = NA,
    x_current = c(1, 2),
    x_previous = NULL,
    iter = 0
  )

  expect_false(result$converged)
  expect_equal(result$reason, "")
})

test_that("convergence check uses infinity norm for gradient", {
  # Test that max(abs(g)) is used, not Euclidean norm
  result <- check_convergence(
    g_current = c(1e-10, 1e-10, 1e-10, 1e-7),  # max = 1e-7
    f_current = 10,
    f_previous = 11,
    x_current = rep(1, 4),
    x_previous = rep(0.9, 4),
    iter = 5,
    tol_grad = 1e-6  # 1e-7 < 1e-6, should converge
  )

  expect_true(result$converged)
  expect_equal(result$reason, "gradient_abs")
})

test_that("convergence check uses infinity norm for parameters", {
  # Test that max(abs(x - x_prev)) is used
  # Note: parameter convergence requires gradient to be reasonably small (< 10 * tol_grad)
  # Use 5e-7 gradient which is:
  # - > 1e-7 (tol_grad), so won't trigger gradient_abs
  # - > 1e-12 * 10 = 1e-11 (tol_rel_grad * f_scale), so won't trigger gradient_rel
  # - < 10 * 1e-7 = 1e-6, so passes reasonably_small check
  result <- check_convergence(
    g_current = c(5e-7, 5e-7, 5e-7, 5e-7),
    f_current = 10,
    f_previous = 9,
    x_current = c(1, 2, 3, 4),
    x_previous = c(1 + 1e-10, 2, 3, 4 + 1e-7),  # max change = 1e-7
    iter = 5,
    tol_grad = 1e-7,  # gradient is 5e-7, not small enough for gradient_abs
    tol_rel_grad = 1e-12,  # Very strict relative gradient check
    tol_param = 1e-6  # 1e-7 < 1e-6, should converge
  )

  expect_true(result$converged)
  expect_equal(result$reason, "parameter")
})
