--[[
    Commander 3.0 - Pipeline Test Suite

    Tests for pipeline operators:
    - count, first, last, take, skip
    - sort, reverse
    - where (expressions)
    - select, group, unique, sum
]]

local ADDON_NAME, C3 = ...

local Framework = C3.TestFramework
local Assert = Framework.Assert
local suite = Framework:Suite("pipeline")

-------------------------------------------------------------------------------
-- Helper Functions
-------------------------------------------------------------------------------

---Get numeric value from count result
local function getCountValue(result)
    if type(result) == "table" and result._type == "Number" then
        return result.value
    end
    return nil
end

---Collect iterator/array results into array
local function collectToArray(result)
    if type(result) == "function" then
        local items = {}
        for item in result do
            table.insert(items, item)
        end
        return items
    elseif type(result) == "table" then
        if result[1] ~= nil then return result end
        if result._type then return { result } end
        return {}
    end
    return {}
end

-------------------------------------------------------------------------------
-- Count Operator Tests
-------------------------------------------------------------------------------

Framework:Test(suite, "count returns Number type", function()
    local result = C3:Query("bags | count")
    Assert.notNil(result)
    Assert.hasType(result, "Number", "count should return Number type")
end)

Framework:Test(suite, "count value is non-negative", function()
    local result = C3:Query("bags | count")
    local count = getCountValue(result)
    Assert.notNil(count, "count should have a value")
    Assert.greaterOrEqual(count, 0, "count should be >= 0")
end)

Framework:Test(suite, "count on mounts works", function()
    local result = C3:Query("mounts | count")
    Assert.notNil(result)
    Assert.hasType(result, "Number")
end)

-------------------------------------------------------------------------------
-- First/Take Operator Tests
-------------------------------------------------------------------------------

Framework:Test(suite, "first returns single item", function()
    local result = C3:Query("mounts | first")
    -- May be nil if no mounts, or a single mount
    if result and type(result) == "table" and result._type then
        Assert.equals("Mount", result._type, "first mount should be Mount type")
    end
end)

Framework:Test(suite, "first N returns at most N items", function()
    local result = C3:Query("mounts | first 5")
    local items = collectToArray(result)
    Assert.lessOrEqual(#items, 5, "first 5 should return at most 5 items")
end)

Framework:Test(suite, "take limits results", function()
    local result = C3:Query("mounts | take 3")
    local items = collectToArray(result)
    Assert.lessOrEqual(#items, 3, "take 3 should return at most 3 items")
end)

Framework:Test(suite, "take 0 returns empty", function()
    local result = C3:Query("mounts | take 0")
    local items = collectToArray(result)
    Assert.equals(0, #items, "take 0 should return empty")
end)

-------------------------------------------------------------------------------
-- Last Operator Tests
-------------------------------------------------------------------------------

Framework:Test(suite, "last returns final item", function()
    local result = C3:Query("mounts | last")
    -- May be nil if no mounts
    if result and type(result) == "table" and result._type then
        Assert.equals("Mount", result._type)
    end
end)

Framework:Test(suite, "last N returns at most N items", function()
    local result = C3:Query("mounts | last 3")
    local items = collectToArray(result)
    Assert.lessOrEqual(#items, 3, "last 3 should return at most 3 items")
end)

-------------------------------------------------------------------------------
-- Skip Operator Tests
-------------------------------------------------------------------------------

Framework:Test(suite, "skip removes first N items", function()
    local all = collectToArray(C3:Query("mounts | take 5"))
    local skipped = collectToArray(C3:Query("mounts | take 5 | skip 2"))

    if #all >= 3 then
        Assert.equals(#all - 2, #skipped, "skip 2 should remove 2 items")
    end
end)

Framework:Test(suite, "skip 0 returns all items", function()
    local all = collectToArray(C3:Query("mounts | take 3"))
    local skipped = collectToArray(C3:Query("mounts | take 3 | skip 0"))
    Assert.equals(#all, #skipped, "skip 0 should not remove items")
end)

-------------------------------------------------------------------------------
-- Sort Operator Tests
-------------------------------------------------------------------------------

Framework:Test(suite, "sort by name works", function()
    local result = C3:Query("mounts | take 10 | sort name")
    local items = collectToArray(result)

    if #items >= 2 then
        -- Verify sorted order
        for i = 1, #items - 1 do
            local a = items[i].name or ""
            local b = items[i + 1].name or ""
            Assert.isTrue(a:lower() <= b:lower(), "items should be sorted by name ascending")
        end
    end
end)

Framework:Test(suite, "sort desc reverses order", function()
    -- Note: --desc or -d flag, not positional argument
    local result = C3:Query("mounts | take 10 | sort name --desc")
    local items = collectToArray(result)

    if #items >= 2 then
        for i = 1, #items - 1 do
            local a = items[i].name or ""
            local b = items[i + 1].name or ""
            Assert.isTrue(a:lower() >= b:lower(), "items should be sorted descending")
        end
    end
end)

Framework:Test(suite, "sort by numeric field works", function()
    local result = C3:Query("bags | take 10 | sort quality")
    -- Just verify no error
    Assert.notNil(result)
end)

-------------------------------------------------------------------------------
-- Reverse Operator Tests
-------------------------------------------------------------------------------

Framework:Test(suite, "reverse changes order", function()
    local original = collectToArray(C3:Query("mounts | take 5"))
    local reversed = collectToArray(C3:Query("mounts | take 5 | reverse"))

    if #original >= 2 and #reversed >= 2 then
        -- First item of original should be last of reversed
        Assert.equals(original[1].name, reversed[#reversed].name,
            "reverse should flip order")
    end
end)

-------------------------------------------------------------------------------
-- Where Operator Tests
-------------------------------------------------------------------------------

Framework:Test(suite, "where filters items", function()
    local allCount = getCountValue(C3:Query("mounts | count")) or 0
    local filteredResult = C3:Query("mounts | where isFlying | count")
    local filteredCount = getCountValue(filteredResult) or 0

    Assert.lessOrEqual(filteredCount, allCount, "filtered count should be <= total")
end)

Framework:Test(suite, "where with numeric comparison", function()
    local result = C3:Query("bags | where quality >= 3 | count")
    Assert.notNil(result)
    Assert.hasType(result, "Number")
end)

Framework:Test(suite, "where with equality comparison", function()
    local result = C3:Query("bags | where quality == 4 | count")
    Assert.notNil(result)
end)

Framework:Test(suite, "where with string comparison", function()
    -- Test with a property that might have string values
    local result = C3:Query("spells | where school == 'Fire' | count")
    Assert.notNil(result)
end)

Framework:Test(suite, "where with boolean and", function()
    local result = C3:Query("mounts | where isFlying and isFavorite | count")
    Assert.notNil(result)
end)

Framework:Test(suite, "where with boolean or", function()
    local result = C3:Query("mounts | where isFlying or isFavorite | count")
    Assert.notNil(result)
end)

Framework:Test(suite, "where with not operator", function()
    local result = C3:Query("mounts | where not isFavorite | count")
    Assert.notNil(result)
end)

Framework:Test(suite, "where with contains operator (~)", function()
    local result = C3:Query("mounts | where name ~ 'Horse' | count")
    Assert.notNil(result)
end)

-------------------------------------------------------------------------------
-- Select Operator Tests
-------------------------------------------------------------------------------

Framework:Test(suite, "select projects specific fields", function()
    local result = C3:Query("bags | first | select name,quality")
    -- Should execute without error
    Assert.notNil(result)
end)

Framework:Test(suite, "select single field works", function()
    local result = C3:Query("mounts | first | select name")
    Assert.notNil(result)
end)

-------------------------------------------------------------------------------
-- Group Operator Tests
-------------------------------------------------------------------------------

Framework:Test(suite, "group by field works", function()
    local result = C3:Query("bags | group qualityName")
    local items = collectToArray(result)

    for _, group in ipairs(items) do
        if type(group) == "table" and group._type == "Group" then
            Assert.notNil(group.key, "group should have key")
            Assert.notNil(group.count, "group should have count")
        end
    end
end)

Framework:Test(suite, "group by quality works", function()
    local result = C3:Query("bags | group quality")
    Assert.notNil(result)
end)

-------------------------------------------------------------------------------
-- Unique Operator Tests
-------------------------------------------------------------------------------

Framework:Test(suite, "unique removes duplicates", function()
    local result = C3:Query("bags | unique qualityName | count")
    Assert.notNil(result)
end)

Framework:Test(suite, "unique on mounts by isFlying", function()
    local result = C3:Query("mounts | unique isFlying | count")
    local count = getCountValue(result)
    -- Should be at most 2 (true/false)
    if count then
        Assert.lessOrEqual(count, 2, "unique isFlying should have at most 2 values")
    end
end)

-------------------------------------------------------------------------------
-- Sum Operator Tests
-------------------------------------------------------------------------------

Framework:Test(suite, "sum on numeric field works", function()
    local result = C3:Query("currencies | sum quantity")
    Assert.notNil(result)
end)

Framework:Test(suite, "sum returns Number type", function()
    local result = C3:Query("bags | sum count")
    if result and result._type then
        Assert.hasType(result, "Number")
    end
end)

-------------------------------------------------------------------------------
-- Pipeline Chaining Tests
-------------------------------------------------------------------------------

Framework:Test(suite, "multiple operators chain correctly", function()
    local result = C3:Query("mounts | where isFlying | sort name | take 5 | count")
    local count = getCountValue(result)
    Assert.notNil(count)
    Assert.lessOrEqual(count, 5, "chained pipeline should respect take limit")
end)

Framework:Test(suite, "complex chain with where and sort", function()
    local result = C3:Query("bags | where quality > 1 | sort quality desc | first 3")
    Assert.notNil(result)
end)

Framework:Test(suite, "chain with group and count", function()
    local result = C3:Query("mounts | group isFlying | count")
    Assert.notNil(result)
end)

-------------------------------------------------------------------------------
-- Grep Operator Tests
-------------------------------------------------------------------------------

Framework:Test(suite, "grep filters by pattern", function()
    -- Test basic grep on mounts by name containing 'Horse'
    local result = C3:Query("mounts | grep Horse | count")
    Assert.notNil(result)
    Assert.hasType(result, "Number")
end)

Framework:Test(suite, "grep with case insensitive flag", function()
    -- Test grep -i flag
    local result = C3:Query("mounts | grep -i horse | count")
    Assert.notNil(result)
end)

Framework:Test(suite, "grep returns subset of results", function()
    local allCount = getCountValue(C3:Query("mounts | count")) or 0
    local grepCount = getCountValue(C3:Query("mounts | grep Drake | count")) or 0
    Assert.lessOrEqual(grepCount, allCount, "grep should return subset of items")
end)

Framework:Test(suite, "grep on empty pattern returns all", function()
    -- Grep with broad pattern
    local result = C3:Query("mounts | take 5 | grep . | count")
    local count = getCountValue(result)
    if count then
        Assert.lessOrEqual(count, 5, "grep should work with any pattern")
    end
end)

return suite
