require("async.test")

local Promise = require("promise")

describe("Promise.all", function()
  async_it("resolves when all promises resolve", function(done)
    local promise1 = Promise:new(function(resolve)
      resolve(1)
    end)
    local promise2 = Promise:new(function(resolve)
      resolve(2)
    end)
    local promise3 = Promise:new(function(resolve)
      resolve(3)
    end)

    Promise.all({ promise1, promise2, promise3 })
      :and_then(function(results)
        assert.are.same({ 1, 2, 3 }, results)
        done()
      end)
      :catch(function()
        error("Promise.all should not reject when all promises resolve.")
      end)
  end)

  async_it("rejects when any promise rejects", function(done)
    local promise1 = Promise:new(function(resolve)
      resolve(1)
    end)
    local promise2 = Promise:new(function(_, reject)
      reject("Error in promise 2")
    end)
    local promise3 = Promise:new(function(resolve)
      resolve(3)
    end)

    Promise.all({ promise1, promise2, promise3 })
      :and_then(function()
        error("Promise.all should not resolve when one promise rejects.")
      end)
      :catch(function(reason)
        assert.are.equal("Error in promise 2", reason)
        done()
      end)
  end)

  async_it("resolves immediately with an empty array", function(done)
    Promise.all({})
      :and_then(function(results)
        assert.are.same({}, results)
        done()
      end)
      :catch(function()
        error("Promise.all should not reject for an empty array.")
      end)
  end)

  async_it("rejects with the first rejection reason in mixed resolve/reject", function(done)
    local promise1 = Promise:new(function(resolve)
      resolve(1)
    end)
    local promise2 = Promise:new(function(_, reject)
      reject("Error in promise 2")
    end)
    local promise3 = Promise:new(function(resolve)
      resolve(3)
    end)

    Promise.all({ promise1, promise2, promise3 })
      :and_then(function()
        error("Promise.all should not resolve when one promise rejects.")
      end)
      :catch(function(reason)
        assert.are.equal("Error in promise 2", reason)
        done()
      end)
  end)

  async_it("handles delayed promises correctly", function(done)
    local promise1 = Promise:new(function(resolve)
      vim.defer_fn(function()
        resolve(1)
      end, 100)
    end)

    local promise2 = Promise:new(function(resolve)
      vim.defer_fn(function()
        resolve(2)
      end, 50)
    end)

    local promise3 = Promise:new(function(resolve)
      vim.defer_fn(function()
        resolve(3)
      end, 150)
    end)

    Promise.all({ promise1, promise2, promise3 })
      :and_then(function(results)
        assert.are.same({ 1, 2, 3 }, results)
        done()
      end)
      :catch(function()
        error("Promise.all should not reject when all promises resolve.")
      end)
  end)

  async_it("resolves non-promise values immediately", function(done)
    local value1 = 1
    local promise1 = Promise:new(function(resolve)
      resolve(2)
    end)
    local value2 = 3

    Promise.all({ value1, promise1, value2 })
      :and_then(function(results)
        assert.are.same({ 1, 2, 3 }, results)
        done()
      end)
      :catch(function()
        error("Promise.all should not reject for non-promise values.")
      end)
  end)

  async_it("resolves in the order of promises provided", function(done)
    local promise1 = Promise:new(function(resolve)
      vim.defer_fn(function()
        resolve(1)
      end, 150)
    end)

    local promise2 = Promise:new(function(resolve)
      vim.defer_fn(function()
        resolve(2)
      end, 100)
    end)

    local promise3 = Promise:new(function(resolve)
      vim.defer_fn(function()
        resolve(3)
      end, 50)
    end)

    Promise.all({ promise1, promise2, promise3 })
      :and_then(function(results)
        assert.are.same({ 1, 2, 3 }, results)
        done()
      end)
      :catch(function()
        error("Promise.all should not reject when all promises resolve.")
      end)
  end)

  async_it("rejects immediately on the first promise rejection", function(done)
    local promise1 = Promise:new(function(_, reject)
      vim.defer_fn(function()
        reject("Error in promise 1")
      end, 50)
    end)

    local promise2 = Promise:new(function(resolve)
      vim.defer_fn(function()
        resolve(2)
      end, 100)
    end)

    local promise3 = Promise:new(function(resolve)
      vim.defer_fn(function()
        resolve(3)
      end, 150)
    end)

    Promise.all({ promise1, promise2, promise3 })
      :and_then(function()
        error("Promise.all should not resolve when one promise rejects.")
      end)
      :catch(function(reason)
        assert.are.equal("Error in promise 1", reason)
        done()
      end)
  end)
end)
