local luaunit = require("testing.luaunit")
require("testing.loveStub")

local Easing = require("modules.Easing")
local ErrorHandler = require("modules.ErrorHandler")
local ErrorCodes = require("modules.ErrorCodes")

-- Initialize ErrorHandler
ErrorHandler.init({ ErrorCodes = ErrorCodes })

TestEasing = {}

function TestEasing:setUp()
  -- Reset state before each test
end

-- Test that all easing functions exist
function TestEasing:testAllEasingFunctionsExist()
  local easings = {
    -- Linear
    "linear",
    -- Quad
    "easeInQuad", "easeOutQuad", "easeInOutQuad",
    -- Cubic
    "easeInCubic", "easeOutCubic", "easeInOutCubic",
    -- Quart
    "easeInQuart", "easeOutQuart", "easeInOutQuart",
    -- Quint
    "easeInQuint", "easeOutQuint", "easeInOutQuint",
    -- Expo
    "easeInExpo", "easeOutExpo", "easeInOutExpo",
    -- Sine
    "easeInSine", "easeOutSine", "easeInOutSine",
    -- Circ
    "easeInCirc", "easeOutCirc", "easeInOutCirc",
    -- Back
    "easeInBack", "easeOutBack", "easeInOutBack",
    -- Elastic
    "easeInElastic", "easeOutElastic", "easeInOutElastic",
    -- Bounce
    "easeInBounce", "easeOutBounce", "easeInOutBounce",
  }
  
  for _, name in ipairs(easings) do
    luaunit.assertNotNil(Easing[name], "Easing function " .. name .. " should exist")
    luaunit.assertEquals(type(Easing[name]), "function", name .. " should be a function")
  end
end

-- Test that all easing functions accept t parameter (0-1)
function TestEasing:testEasingFunctionsAcceptParameter()
  local result = Easing.linear(0.5)
  luaunit.assertNotNil(result)
  luaunit.assertEquals(type(result), "number")
end

-- Test linear easing
function TestEasing:testLinear()
  luaunit.assertEquals(Easing.linear(0), 0)
  luaunit.assertEquals(Easing.linear(0.5), 0.5)
  luaunit.assertEquals(Easing.linear(1), 1)
end

-- Test easeInQuad
function TestEasing:testEaseInQuad()
  luaunit.assertEquals(Easing.easeInQuad(0), 0)
  luaunit.assertAlmostEquals(Easing.easeInQuad(0.5), 0.25, 0.01)
  luaunit.assertEquals(Easing.easeInQuad(1), 1)
end

-- Test easeOutQuad
function TestEasing:testEaseOutQuad()
  luaunit.assertEquals(Easing.easeOutQuad(0), 0)
  luaunit.assertAlmostEquals(Easing.easeOutQuad(0.5), 0.75, 0.01)
  luaunit.assertEquals(Easing.easeOutQuad(1), 1)
end

-- Test easeInOutQuad
function TestEasing:testEaseInOutQuad()
  luaunit.assertEquals(Easing.easeInOutQuad(0), 0)
  luaunit.assertAlmostEquals(Easing.easeInOutQuad(0.5), 0.5, 0.01)
  luaunit.assertEquals(Easing.easeInOutQuad(1), 1)
end

-- Test easeInSine
function TestEasing:testEaseInSine()
  luaunit.assertEquals(Easing.easeInSine(0), 0)
  local mid = Easing.easeInSine(0.5)
  luaunit.assertTrue(mid > 0 and mid < 1, "easeInSine(0.5) should be between 0 and 1")
  luaunit.assertAlmostEquals(Easing.easeInSine(1), 1, 0.01)
end

-- Test easeOutSine
function TestEasing:testEaseOutSine()
  luaunit.assertEquals(Easing.easeOutSine(0), 0)
  local mid = Easing.easeOutSine(0.5)
  luaunit.assertTrue(mid > 0 and mid < 1, "easeOutSine(0.5) should be between 0 and 1")
  luaunit.assertAlmostEquals(Easing.easeOutSine(1), 1, 0.01)
end

-- Test easeInOutSine
function TestEasing:testEaseInOutSine()
  luaunit.assertEquals(Easing.easeInOutSine(0), 0)
  luaunit.assertAlmostEquals(Easing.easeInOutSine(0.5), 0.5, 0.01)
  luaunit.assertAlmostEquals(Easing.easeInOutSine(1), 1, 0.01)
end

-- Test easeInQuint
function TestEasing:testEaseInQuint()
  luaunit.assertEquals(Easing.easeInQuint(0), 0)
  luaunit.assertAlmostEquals(Easing.easeInQuint(0.5), 0.03125, 0.01)
  luaunit.assertEquals(Easing.easeInQuint(1), 1)
end

-- Test easeOutQuint
function TestEasing:testEaseOutQuint()
  luaunit.assertEquals(Easing.easeOutQuint(0), 0)
  luaunit.assertAlmostEquals(Easing.easeOutQuint(0.5), 0.96875, 0.01)
  luaunit.assertEquals(Easing.easeOutQuint(1), 1)
end

-- Test easeInCirc
function TestEasing:testEaseInCirc()
  luaunit.assertEquals(Easing.easeInCirc(0), 0)
  local mid = Easing.easeInCirc(0.5)
  luaunit.assertTrue(mid > 0 and mid < 1, "easeInCirc(0.5) should be between 0 and 1")
  luaunit.assertAlmostEquals(Easing.easeInCirc(1), 1, 0.01)
end

-- Test easeOutCirc
function TestEasing:testEaseOutCirc()
  luaunit.assertEquals(Easing.easeOutCirc(0), 0)
  local mid = Easing.easeOutCirc(0.5)
  luaunit.assertTrue(mid > 0 and mid < 1, "easeOutCirc(0.5) should be between 0 and 1")
  luaunit.assertAlmostEquals(Easing.easeOutCirc(1), 1, 0.01)
end

-- Test easeInOutCirc
function TestEasing:testEaseInOutCirc()
  luaunit.assertEquals(Easing.easeInOutCirc(0), 0)
  luaunit.assertAlmostEquals(Easing.easeInOutCirc(0.5), 0.5, 0.01)
  luaunit.assertAlmostEquals(Easing.easeInOutCirc(1), 1, 0.01)
end

-- Test easeInBack (should overshoot at start)
function TestEasing:testEaseInBack()
  luaunit.assertEquals(Easing.easeInBack(0), 0)
  local early = Easing.easeInBack(0.3)
  luaunit.assertTrue(early < 0, "easeInBack should go negative (overshoot) early on")
  luaunit.assertAlmostEquals(Easing.easeInBack(1), 1, 0.001)
end

-- Test easeOutBack (should overshoot at end)
function TestEasing:testEaseOutBack()
  luaunit.assertAlmostEquals(Easing.easeOutBack(0), 0, 0.001)
  local late = Easing.easeOutBack(0.7)
  luaunit.assertTrue(late > 0.7, "easeOutBack should overshoot at the end")
  luaunit.assertAlmostEquals(Easing.easeOutBack(1), 1, 0.01)
end

-- Test easeInElastic (should oscillate)
function TestEasing:testEaseInElastic()
  luaunit.assertEquals(Easing.easeInElastic(0), 0)
  luaunit.assertAlmostEquals(Easing.easeInElastic(1), 1, 0.01)
  -- Elastic should go negative at some point
  local hasNegative = false
  for i = 1, 9 do
    local t = i / 10
    if Easing.easeInElastic(t) < 0 then
      hasNegative = true
      break
    end
  end
  luaunit.assertTrue(hasNegative, "easeInElastic should have negative values (oscillation)")
end

-- Test easeOutElastic (should oscillate)
function TestEasing:testEaseOutElastic()
  luaunit.assertEquals(Easing.easeOutElastic(0), 0)
  luaunit.assertAlmostEquals(Easing.easeOutElastic(1), 1, 0.01)
  -- Elastic should go above 1 at some point
  local hasOvershoot = false
  for i = 1, 9 do
    local t = i / 10
    if Easing.easeOutElastic(t) > 1 then
      hasOvershoot = true
      break
    end
  end
  luaunit.assertTrue(hasOvershoot, "easeOutElastic should overshoot 1 (oscillation)")
end

-- Test easeInBounce
function TestEasing:testEaseInBounce()
  luaunit.assertEquals(Easing.easeInBounce(0), 0)
  luaunit.assertAlmostEquals(Easing.easeInBounce(1), 1, 0.01)
  -- Bounce should have multiple "bounces" (local minima)
  local result = Easing.easeInBounce(0.5)
  luaunit.assertTrue(result >= 0 and result <= 1, "easeInBounce should stay within 0-1 range")
end

-- Test easeOutBounce
function TestEasing:testEaseOutBounce()
  luaunit.assertEquals(Easing.easeOutBounce(0), 0)
  luaunit.assertAlmostEquals(Easing.easeOutBounce(1), 1, 0.01)
  -- Bounce should have bounces
  local result = Easing.easeOutBounce(0.8)
  luaunit.assertTrue(result >= 0 and result <= 1, "easeOutBounce should stay within 0-1 range")
end

-- Test easeInOutBounce
function TestEasing:testEaseInOutBounce()
  luaunit.assertEquals(Easing.easeInOutBounce(0), 0)
  luaunit.assertAlmostEquals(Easing.easeInOutBounce(0.5), 0.5, 0.01)
  luaunit.assertAlmostEquals(Easing.easeInOutBounce(1), 1, 0.01)
end

-- Test configurable back() factory
function TestEasing:testBackFactory()
  local customBack = Easing.back(2.5)
  luaunit.assertEquals(type(customBack), "function")
  luaunit.assertEquals(customBack(0), 0)
  luaunit.assertEquals(customBack(1), 1)
  -- Should overshoot with custom amount
  local mid = customBack(0.3)
  luaunit.assertTrue(mid < 0, "Custom back easing should overshoot")
end

-- Test configurable elastic() factory
function TestEasing:testElasticFactory()
  local customElastic = Easing.elastic(1.5, 0.4)
  luaunit.assertEquals(type(customElastic), "function")
  luaunit.assertEquals(customElastic(0), 0)
  luaunit.assertAlmostEquals(customElastic(1), 1, 0.01)
end

-- Test Easing.list() method
function TestEasing:testList()
  local list = Easing.list()
  luaunit.assertEquals(type(list), "table")
  luaunit.assertEquals(#list, 31, "Should have exactly 31 easing functions")
  
  -- Check that linear is in the list
  local hasLinear = false
  for _, name in ipairs(list) do
    if name == "linear" then
      hasLinear = true
      break
    end
  end
  luaunit.assertTrue(hasLinear, "List should contain 'linear'")
end

-- Test Easing.get() method
function TestEasing:testGet()
  local linear = Easing.get("linear")
  luaunit.assertNotNil(linear)
  luaunit.assertEquals(type(linear), "function")
  luaunit.assertEquals(linear(0.5), 0.5)
  
  -- Test non-existent easing
  local nonExistent = Easing.get("nonExistentEasing")
  luaunit.assertNil(nonExistent)
end

-- Test that all InOut easings are symmetric around 0.5
function TestEasing:testInOutSymmetry()
  local inOutEasings = {
    "easeInOutQuad", "easeInOutCubic", "easeInOutQuart", "easeInOutQuint",
    "easeInOutExpo", "easeInOutSine", "easeInOutCirc", "easeInOutBack",
    "easeInOutElastic", "easeInOutBounce"
  }
  
  for _, name in ipairs(inOutEasings) do
    local easing = Easing[name]
    -- At t=0.5, all InOut easings should be close to 0.5
    local mid = easing(0.5)
    luaunit.assertAlmostEquals(mid, 0.5, 0.1, name .. " should be close to 0.5 at t=0.5")
  end
end

-- Test boundary conditions for all easings
function TestEasing:testBoundaryConditions()
  local easings = {
    "linear",
    "easeInQuad", "easeOutQuad", "easeInOutQuad",
    "easeInCubic", "easeOutCubic", "easeInOutCubic",
    "easeInQuart", "easeOutQuart", "easeInOutQuart",
    "easeInQuint", "easeOutQuint", "easeInOutQuint",
    "easeInExpo", "easeOutExpo", "easeInOutExpo",
    "easeInSine", "easeOutSine", "easeInOutSine",
    "easeInCirc", "easeOutCirc", "easeInOutCirc",
    "easeInBack", "easeOutBack", "easeInOutBack",
    "easeInElastic", "easeOutElastic", "easeInOutElastic",
    "easeInBounce", "easeOutBounce", "easeInOutBounce",
  }
  
  for _, name in ipairs(easings) do
    local easing = Easing[name]
    -- All easings should start at 0
    local start = easing(0)
    luaunit.assertAlmostEquals(start, 0, 0.01, name .. " should start at 0")
    
    -- All easings should end at 1
    local finish = easing(1)
    luaunit.assertAlmostEquals(finish, 1, 0.01, name .. " should end at 1")
  end
end

os.exit(luaunit.LuaUnit.run())
