local LOVElyTests = require("tests.LOVElyTests")
local LT = require("LOVElyTree")
local RESULT = LT.RESULT
local Decorators = LT.Decorators

---@diagnostic disable:invisible

local function assert_equal(actual, expected, message)
    if actual ~= expected then
        error(message .. " (Expected: " .. tostring(expected) .. ", Got: " .. tostring(actual) .. ")")
    end
end

local function WaitAction(duration)
    local action = {}
    action.timer = 0
    action.name = "WaitAction (" .. duration .. "s)"

    function action.update(self, dt)
        self.timer = self.timer + dt
        return (self.timer >= duration) and RESULT.SUCCESS or RESULT.RUNNING
    end

    return action
end

local function FailAction()
    local action = {}
    action.name = "FailAction"

    function action:update(dt)
        self.status = RESULT.FAILURE
        return self.status
    end

    return action
end

local function SuccessAction()
    local action = {}
    action.name = "SuccessAction"

    function action:update(dt)
        self.status = RESULT.SUCCESS
        return self.status
    end

    return action
end

local FailActionWrapped = LT.WrapAction(FailAction)
local SuccessActionWrapped = LT.WrapAction(SuccessAction)
local WaitActionWrapped = LT.WrapAction(WaitAction)

-- Fake action creator to simulate controlled state transitions.
-- I know that is weird that reset increments a counter, this made tests harder
-- when I thought it would be the opposite and honestly I should change this. 
-- But the lib works and I want to make a game, PRs are welcome. Wait for future me to improve tests
-- When I get to add more nodes.

-- Also, this function doesn't use a wrapper because it already defines the states
-- It's not really a correct node since it doesn't set state to idle in the reset, but that is mainly a thing
-- for the renderer, the states property of nodes is not really needed.
local function FakeAction(states)
    local action = { resetted_times = 1, states = states }
    function action:update(dt)
        local index = self.resetted_times - 1
        local s = self.states[index] or RESULT.RUNNING
        return s
    end

    function action:reset()
        self.resetted_times = self.resetted_times + 1
    end

    return action
end

-- Decorator tests
LOVElyTests.register("Decorators", function(test_case)
    local function test_repeat()
        local rep = LT.Decorators.Repeat(SuccessActionWrapped(), 3)
        local status = rep:update(0.1)
        assert_equal(status, RESULT.RUNNING, "Repeat should be running before task completes")
        assert_equal(rep.count, 1, "Repeat count should be 1 after one cycle")
        status = rep:update(0.1)
        assert_equal(status, RESULT.RUNNING, "Repeat should be running before task completes")
        assert_equal(rep.count, 2, "Repeat count should be 2 after two cycles")
        rep:update(0.1)
        assert_equal(rep.count, 3, "Repeat count should be 3 after three cycles")
        assert_equal(rep.status, RESULT.SUCCESS, "Repeat should succeed after required repetitions")
    end

    local function test_invert()
        local task = WaitActionWrapped(0)
        task.update = function(self, dt)
            self.status = RESULT.FAILURE
            return self.status
        end
        local inv = LT.Decorators.Invert(task)
        local status = inv:update(0.1)
        assert_equal(status, RESULT.SUCCESS, "Invert should return success when child returns false")
        task:update(0)
        task.update = function(self, dt)
            self.status = RESULT.SUCCESS
            return self.status
        end
        inv:reset()
        status = inv:update(0.1)
        assert_equal(status, RESULT.FAILURE, "Invert should return false when child returns success")
    end

    local function test_retry_failure()
        -- Child always fails; after max attempts returns FAILURE.
        local child = FakeAction({ RESULT.FAILURE, RESULT.FAILURE, RESULT.FAILURE })
        local retry = Decorators.Retry(child, 3)
        local stat = retry:update(0.1)
        assert(stat == RESULT.RUNNING, "Retry should be running after first failure")
        -- second update: resets child and fails again
        retry:update(0.1)
        -- third update: resets child and fails final time
        stat = retry:update(0.1)
        assert(stat == RESULT.FAILURE, "Retry should fail after max attempts")
    end

    local function test_retry_success()
        -- Child fails on first, then succeeds.
        local child = FakeAction({ RESULT.FAILURE, RESULT.SUCCESS })
        local retry = Decorators.Retry(child, 2)
        local stat = retry:update(0.1)
        assert(stat == RESULT.RUNNING, "Retry should be running after first failure")
        stat = retry:update(0.1)
        assert(stat == RESULT.SUCCESS, "Retry should succeed when child eventually returns success")
    end

    ----------------------
    -- AlwaysSuccess / AlwaysFail Tests
    ----------------------
    local function test_always_success()
        local child = FakeAction({ RESULT.FAILURE })
        local as = Decorators.AlwaysSuccess(child)
        local stat = as:update(0.1)
        assert(stat == RESULT.SUCCESS, "AlwaysSuccess should force FAILURE to SUCCESS")
    end

    local function test_always_fail()
        local child = FakeAction({ RESULT.SUCCESS })
        local af = Decorators.AlwaysFail(child)
        local stat = af:update(0.1)
        assert(stat == RESULT.FAILURE, "AlwaysFail should force SUCCESS to FAILURE")
    end

    ----------------------
    -- TimeLimit Tests
    ----------------------
    local function test_time_limit_running_then_fail()
        -- Child always running; after time limit, returns FAILURE.
        local child = FakeAction({ RESULT.RUNNING })
        local tl = Decorators.TimeLimit(child, 0.5)
        local stat = tl:update(0.2)
        assert(stat == RESULT.RUNNING, "TimeLimit should be running before limit reached")
        tl:update(0.2)
        stat = tl:update(0.2)
        assert(stat == RESULT.FAILURE, "TimeLimit should fail after time limit is exceeded")
    end

    local function test_time_limit_child_finishes()
        -- Child returns success before the limit is reached.
        local child = FakeAction({ RESULT.SUCCESS })
        local tl = Decorators.TimeLimit(child, 1)
        local stat = tl:update(0.2)
        assert(stat == RESULT.SUCCESS, "TimeLimit should return child's status if not running")
    end

    ----------------------
    -- ExecutionLimit Tests
    ----------------------
    local function test_execution_limit()
        -- Child returns immediate success.
        local child = FakeAction({ RESULT.SUCCESS, RESULT.SUCCESS, RESULT.SUCCESS })
        local el = Decorators.ExecutionLimit(child, 2)
        local stat = el:update(0.1)
        assert(stat == RESULT.SUCCESS, "ExecutionLimit first execution should succeed")
        el:update(0.1) -- second execution
        stat = el:update(0.1)
        assert(stat == RESULT.FAILURE, "ExecutionLimit should fail after max executions reached")
    end

    ----------------------
    -- UntilFail Tests
    ----------------------
    local function test_until_fail_eventually()
        -- Child will return SUCCESS once then FAILURE.
        local child = FakeAction({ RESULT.SUCCESS, RESULT.FAILURE })
        local uf = Decorators.UntilFail(child)
        local stat = uf:update(0.1)
        assert(stat == RESULT.RUNNING, "UntilFail should be running after success (child resets)")
        stat = uf:update(0.1)
        assert(stat == RESULT.FAILURE, "UntilFail should return FAILURE when child fails")
    end

    ----------------------
    -- UntilSuccess Tests
    ----------------------
    local function test_until_success_eventually()
        -- Child will return FAILURE once then SUCCESS.
        local child = FakeAction({ RESULT.FAILURE, RESULT.SUCCESS })
        local us = Decorators.UntilSuccess(child)
        local stat = us:update(0.1)
        assert(stat == RESULT.RUNNING, "UntilSuccess should be running after failure (child resets)")
        stat = us:update(0.1)
        assert(stat == RESULT.SUCCESS, "UntilSuccess should return SUCCESS when child succeeds")
    end

    ----------------------
    -- Register tests
    ----------------------
    test_case("test_repeat", test_repeat)
    test_case("test_invert", test_invert)

    test_case("test_retry_failure", test_retry_failure)
    test_case("test_retry_success", test_retry_success)
    test_case("test_always_success", test_always_success)
    test_case("test_always_fail", test_always_fail)
    test_case("test_time_limit_running_then_fail", test_time_limit_running_then_fail)
    test_case("test_time_limit_child_finishes", test_time_limit_child_finishes)
    test_case("test_execution_limit", test_execution_limit)
    test_case("test_until_fail_eventually", test_until_fail_eventually)
    test_case("test_until_success_eventually", test_until_success_eventually)
end)

LOVElyTests.run_all()
