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

local Transform = require("modules.Transform")

TestTransform = {}

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

-- Test Transform.new()

function TestTransform:testNew_DefaultValues()
  local transform = Transform.new()

  luaunit.assertNotNil(transform)
  luaunit.assertEquals(transform.rotate, 0)
  luaunit.assertEquals(transform.scaleX, 1)
  luaunit.assertEquals(transform.scaleY, 1)
  luaunit.assertEquals(transform.translateX, 0)
  luaunit.assertEquals(transform.translateY, 0)
  luaunit.assertEquals(transform.skewX, 0)
  luaunit.assertEquals(transform.skewY, 0)
  luaunit.assertEquals(transform.originX, 0.5)
  luaunit.assertEquals(transform.originY, 0.5)
end

function TestTransform:testNew_CustomValues()
  local transform = Transform.new({
    rotate = math.pi / 4,
    scaleX = 2,
    scaleY = 3,
    translateX = 100,
    translateY = 200,
    skewX = 0.1,
    skewY = 0.2,
    originX = 0,
    originY = 1,
  })

  luaunit.assertAlmostEquals(transform.rotate, math.pi / 4, 0.01)
  luaunit.assertEquals(transform.scaleX, 2)
  luaunit.assertEquals(transform.scaleY, 3)
  luaunit.assertEquals(transform.translateX, 100)
  luaunit.assertEquals(transform.translateY, 200)
  luaunit.assertAlmostEquals(transform.skewX, 0.1, 0.01)
  luaunit.assertAlmostEquals(transform.skewY, 0.2, 0.01)
  luaunit.assertEquals(transform.originX, 0)
  luaunit.assertEquals(transform.originY, 1)
end

function TestTransform:testNew_PartialValues()
  local transform = Transform.new({
    rotate = math.pi,
    scaleX = 2,
  })

  luaunit.assertAlmostEquals(transform.rotate, math.pi, 0.01)
  luaunit.assertEquals(transform.scaleX, 2)
  luaunit.assertEquals(transform.scaleY, 1) -- default
  luaunit.assertEquals(transform.translateX, 0) -- default
end

function TestTransform:testNew_EmptyProps()
  local transform = Transform.new({})

  -- Should use all defaults
  luaunit.assertEquals(transform.rotate, 0)
  luaunit.assertEquals(transform.scaleX, 1)
  luaunit.assertEquals(transform.originX, 0.5)
end

function TestTransform:testNew_NilProps()
  local transform = Transform.new(nil)

  -- Should use all defaults
  luaunit.assertEquals(transform.rotate, 0)
  luaunit.assertEquals(transform.scaleX, 1)
end

-- Test Transform.lerp()

function TestTransform:testLerp_MidPoint()
  local from = Transform.new({ rotate = 0, scaleX = 1, scaleY = 1 })
  local to = Transform.new({ rotate = math.pi, scaleX = 2, scaleY = 3 })

  local result = Transform.lerp(from, to, 0.5)

  luaunit.assertAlmostEquals(result.rotate, math.pi / 2, 0.01)
  luaunit.assertAlmostEquals(result.scaleX, 1.5, 0.01)
  luaunit.assertAlmostEquals(result.scaleY, 2, 0.01)
end

function TestTransform:testLerp_StartPoint()
  local from = Transform.new({ rotate = 0, scaleX = 1 })
  local to = Transform.new({ rotate = math.pi, scaleX = 2 })

  local result = Transform.lerp(from, to, 0)

  luaunit.assertAlmostEquals(result.rotate, 0, 0.01)
  luaunit.assertAlmostEquals(result.scaleX, 1, 0.01)
end

function TestTransform:testLerp_EndPoint()
  local from = Transform.new({ rotate = 0, scaleX = 1 })
  local to = Transform.new({ rotate = math.pi, scaleX = 2 })

  local result = Transform.lerp(from, to, 1)

  luaunit.assertAlmostEquals(result.rotate, math.pi, 0.01)
  luaunit.assertAlmostEquals(result.scaleX, 2, 0.01)
end

function TestTransform:testLerp_AllProperties()
  local from = Transform.new({
    rotate = 0,
    scaleX = 1,
    scaleY = 1,
    translateX = 0,
    translateY = 0,
    skewX = 0,
    skewY = 0,
    originX = 0,
    originY = 0,
  })

  local to = Transform.new({
    rotate = math.pi,
    scaleX = 2,
    scaleY = 3,
    translateX = 100,
    translateY = 200,
    skewX = 0.2,
    skewY = 0.4,
    originX = 1,
    originY = 1,
  })

  local result = Transform.lerp(from, to, 0.5)

  luaunit.assertAlmostEquals(result.rotate, math.pi / 2, 0.01)
  luaunit.assertAlmostEquals(result.scaleX, 1.5, 0.01)
  luaunit.assertAlmostEquals(result.scaleY, 2, 0.01)
  luaunit.assertAlmostEquals(result.translateX, 50, 0.01)
  luaunit.assertAlmostEquals(result.translateY, 100, 0.01)
  luaunit.assertAlmostEquals(result.skewX, 0.1, 0.01)
  luaunit.assertAlmostEquals(result.skewY, 0.2, 0.01)
  luaunit.assertAlmostEquals(result.originX, 0.5, 0.01)
  luaunit.assertAlmostEquals(result.originY, 0.5, 0.01)
end

function TestTransform:testLerp_InvalidInputs()
  -- Should handle nil gracefully
  local result = Transform.lerp(nil, nil, 0.5)

  luaunit.assertNotNil(result)
  luaunit.assertEquals(result.rotate, 0)
  luaunit.assertEquals(result.scaleX, 1)
end

function TestTransform:testLerp_ClampT()
  local from = Transform.new({ scaleX = 1 })
  local to = Transform.new({ scaleX = 2 })

  -- Test t > 1
  local result1 = Transform.lerp(from, to, 1.5)
  luaunit.assertAlmostEquals(result1.scaleX, 2, 0.01)

  -- Test t < 0
  local result2 = Transform.lerp(from, to, -0.5)
  luaunit.assertAlmostEquals(result2.scaleX, 1, 0.01)
end

function TestTransform:testLerp_InvalidT()
  local from = Transform.new({ scaleX = 1 })
  local to = Transform.new({ scaleX = 2 })

  -- Test NaN
  local result1 = Transform.lerp(from, to, 0 / 0)
  luaunit.assertAlmostEquals(result1.scaleX, 1, 0.01) -- Should default to 0

  -- Test Infinity
  local result2 = Transform.lerp(from, to, math.huge)
  luaunit.assertAlmostEquals(result2.scaleX, 2, 0.01) -- Should clamp to 1
end

-- Test Transform.isIdentity()

function TestTransform:testIsIdentity_True()
  local transform = Transform.new()
  luaunit.assertTrue(Transform.isIdentity(transform))
end

function TestTransform:testIsIdentity_Nil()
  luaunit.assertTrue(Transform.isIdentity(nil))
end

function TestTransform:testIsIdentity_FalseRotate()
  local transform = Transform.new({ rotate = 0.1 })
  luaunit.assertFalse(Transform.isIdentity(transform))
end

function TestTransform:testIsIdentity_FalseScale()
  local transform = Transform.new({ scaleX = 2 })
  luaunit.assertFalse(Transform.isIdentity(transform))
end

function TestTransform:testIsIdentity_FalseTranslate()
  local transform = Transform.new({ translateX = 10 })
  luaunit.assertFalse(Transform.isIdentity(transform))
end

function TestTransform:testIsIdentity_FalseSkew()
  local transform = Transform.new({ skewX = 0.1 })
  luaunit.assertFalse(Transform.isIdentity(transform))
end

-- Test Transform.clone()

function TestTransform:testClone_AllProperties()
  local original = Transform.new({
    rotate = math.pi / 4,
    scaleX = 2,
    scaleY = 3,
    translateX = 100,
    translateY = 200,
    skewX = 0.1,
    skewY = 0.2,
    originX = 0.25,
    originY = 0.75,
  })

  local clone = Transform.clone(original)

  luaunit.assertAlmostEquals(clone.rotate, math.pi / 4, 0.01)
  luaunit.assertEquals(clone.scaleX, 2)
  luaunit.assertEquals(clone.scaleY, 3)
  luaunit.assertEquals(clone.translateX, 100)
  luaunit.assertEquals(clone.translateY, 200)
  luaunit.assertAlmostEquals(clone.skewX, 0.1, 0.01)
  luaunit.assertAlmostEquals(clone.skewY, 0.2, 0.01)
  luaunit.assertAlmostEquals(clone.originX, 0.25, 0.01)
  luaunit.assertAlmostEquals(clone.originY, 0.75, 0.01)

  -- Ensure it's a different object (use raw comparison)
  luaunit.assertFalse(rawequal(clone, original), "Clone should be a different table instance")
end

function TestTransform:testClone_Nil()
  local clone = Transform.clone(nil)

  luaunit.assertNotNil(clone)
  luaunit.assertEquals(clone.rotate, 0)
  luaunit.assertEquals(clone.scaleX, 1)
end

function TestTransform:testClone_Mutation()
  local original = Transform.new({ rotate = 0 })
  local clone = Transform.clone(original)

  -- Mutate clone
  clone.rotate = math.pi

  -- Original should be unchanged
  luaunit.assertEquals(original.rotate, 0)
  luaunit.assertAlmostEquals(clone.rotate, math.pi, 0.01)
end

-- Integration Tests

function TestTransform:testTransformAnimation()
  local Animation = require("modules.Animation")
  local Transform = require("modules.Transform")

  local anim = Animation.new({
    duration = 1,
    start = { transform = Transform.new({ rotate = 0, scaleX = 1 }) },
    final = { transform = Transform.new({ rotate = math.pi, scaleX = 2 }) },
  })

  anim:setTransformModule(Transform)
  anim:update(0.5)

  local result = anim:interpolate()

  luaunit.assertNotNil(result.transform)
  luaunit.assertAlmostEquals(result.transform.rotate, math.pi / 2, 0.01)
  luaunit.assertAlmostEquals(result.transform.scaleX, 1.5, 0.01)
end

if not _G.RUNNING_ALL_TESTS then
  os.exit(luaunit.LuaUnit.run())
end
