# frozen_string_literal: true

RSpec.describe WorkQueue::BoundedQueue do
  subject(:queue) { WorkQueue::BoundedQueue.new(3) }

  let(:task1) { "Task 1" }
  let(:task2) { "Task 2" }
  let(:task3) { "Task 3" }
  let(:task4) { "Task 4" }

  describe "#push" do
    context "when the queue is not full" do
      it "adds the task to the queue" do
        queue.push(task1, force: false)
        expect(queue.size).to eq(1)
      end
    end

    context "when the queue is full" do
      before do
        queue.push(task1, force: false)
        queue.push(task2, force: false)
        queue.push(task3, force: false)
      end

      it "adds the task to the queue if force parameter is true" do
        expect { queue.push(task4, force: true) }.not_to raise_error
        expect(queue.size).to eq(4)
      end

      it "raises an error if the force parameter is false" do
        expect { queue.push(task4, force: false) }.to raise_error(WorkQueue::WorkQueueFull)
      end
    end
  end

  describe "#shift" do
    it "removes and returns the first task from the queue" do
      queue.push(task1, force: false)
      queue.push(task2, force: false)

      expect(queue.shift).to eq(task1)
      expect(queue.shift).to eq(task2)

      expect(queue.size).to eq(0)
      expect(queue).to be_empty
    end

    it "returns nil when the queue is empty" do
      shifted_task = queue.shift
      expect(shifted_task).to be_nil
    end
  end

  describe "#empty?" do
    it "returns true if the queue is empty" do
      expect(queue).to be_empty
    end

    it "returns false if the queue is not empty" do
      queue.push(task1, force: false)
      expect(queue).not_to be_empty
    end
  end

  describe "#size" do
    it "returns the number of tasks in the queue" do
      queue.push(task1, force: false)
      queue.push(task2, force: false)
      expect(queue.size).to eq(2)
    end
  end
end

RSpec.describe WorkQueue::FairQueue do
  subject(:queue) do
    WorkQueue::FairQueue.new(:key, global_limit) { WorkQueue::BoundedQueue.new(per_key_limit) }
  end

  let(:global_limit) { 5 }
  let(:per_key_limit) { 3 }
  let(:key1) { :key1 }
  let(:key2) { :key2 }
  let(:key3) { :key3 }
  let(:task1) { "task1" }
  let(:task2) { "task2" }
  let(:task3) { "task3" }
  let(:task4) { "task4" }
  let(:task5) { "task5" }
  let(:task6) { "task6" }

  describe "#push" do
    context "when no previous tasks exist for the key" do
      it "adds the task to the queue" do
        queue.push({ key: key1, task: task1 }, force: false)
        expect(queue.size).to eq(1)
      end
    end

    context "when the global limit is reached" do
      before do
        queue.push({ key: key1, task: task1 }, force: false)
        queue.push({ key: key2, task: task2 }, force: false)
        queue.push({ key: key3, task: task3 }, force: false)
        queue.push({ key: key1, task: task4 }, force: false)
        queue.push({ key: key2, task: task5 }, force: false)
      end

      it "raises an error if the force parameter is false" do
        expect { queue.push({ key: key3, task: task6 }, force: false) }.to raise_error(
          WorkQueue::WorkQueueFull,
        )
      end

      it "adds the task to the queue if the force parameter is true" do
        queue.push({ key: key3, task: task6 }, force: true)
        expect(queue.size).to eq(6)
      end
    end
  end

  describe "#shift" do
    it "removes and returns tasks in FIFO order when the keys are different" do
      queue.push({ key: key1, task: task1 }, force: false)
      queue.push({ key: key2, task: task2 }, force: false)
      queue.push({ key: key3, task: task3 }, force: false)

      expect(queue.shift).to eq({ key: key1, task: task1 })
      expect(queue.shift).to eq({ key: key2, task: task2 })
      expect(queue.shift).to eq({ key: key3, task: task3 })

      expect(queue.size).to eq(0)
      expect(queue).to be_empty
    end

    it "removes and returns tasks in FIFO order by key when the keys are the same" do
      queue.push({ key: key1, task: task1 }, force: false)
      queue.push({ key: key1, task: task3 }, force: false)
      queue.push({ key: key2, task: task2 }, force: false)
      queue.push({ key: key2, task: task4 }, force: false)

      expect(queue.shift).to eq({ key: key1, task: task1 })
      expect(queue.shift).to eq({ key: key2, task: task2 })
      expect(queue.shift).to eq({ key: key1, task: task3 })
      expect(queue.shift).to eq({ key: key2, task: task4 })

      expect(queue.size).to eq(0)
      expect(queue).to be_empty
    end

    it "returns nil when the queue is empty" do
      shifted_task = queue.shift
      expect(shifted_task).to be_nil
    end
  end

  describe "#empty?" do
    it "returns true if the queue is empty" do
      expect(queue).to be_empty
    end

    it "returns false if the queue is not empty" do
      queue.push({ key: key1, task: task1 }, force: false)
      expect(queue).not_to be_empty
    end
  end

  describe "#size" do
    it "returns the number of tasks in the queue" do
      queue.push({ key: key1, task: task1 }, force: false)
      queue.push({ key: key2, task: task2 }, force: false)
      expect(queue.size).to eq(2)
    end
  end
end

RSpec.describe WorkQueue::ThreadSafeWrapper do
  subject(:queue) { WorkQueue::ThreadSafeWrapper.new(WorkQueue::BoundedQueue.new(3)) }

  let(:task) { "task1" }

  describe "#push" do
    it "delegates the push operation to the inner queue" do
      queue.push(task, force: false)
      expect(queue).not_to be_empty
    end
  end

  describe "#shift" do
    context "when block is true" do
      it "waits until an item is available and then returns it" do
        result = nil
        thread = Thread.new { result = queue.shift(block: true) }
        expect(thread).to be_alive

        queue.push(task, force: false)
        thread.join

        expect(result).to eq(task)
      end
    end

    context "when block is false" do
      it "returns nil immediately if no item is available" do
        shifted_task = queue.shift(block: false)
        expect(shifted_task).to be_nil
      end

      it "returns the first available item if one is present" do
        queue.push(task, force: false)
        shifted_task = queue.shift(block: false)
        expect(shifted_task).to eq(task)
      end
    end
  end
end