require 'rails_helper'

RSpec.describe Conversations::UnreadCounts::Store do
  let(:account_id) { 1 }
  let(:inbox_id) { 2 }
  let(:label_id) { 3 }
  let(:user_id) { 4 }
  let(:conversation_id) { 5 }
  let(:team_id) { 6 }

  after do
    described_class.clear_account!(account_id)
  end

  describe 'key builders' do
    it 'builds base keys using the Redis key naming convention' do
      expect(described_class.inbox_key(account_id, inbox_id)).to eq(
        'UNREAD_CONVERSATIONS::V1::ACCOUNT::1::INBOX::2'
      )
      expect(described_class.label_inbox_key(account_id, label_id, inbox_id)).to eq(
        'UNREAD_CONVERSATIONS::V1::ACCOUNT::1::LABEL::3::INBOX::2'
      )
      expect(described_class.team_inbox_key(account_id, team_id, inbox_id)).to eq(
        'UNREAD_CONVERSATIONS::V1::ACCOUNT::1::TEAM::6::INBOX::2'
      )
    end

    it 'builds assignment-aware keys using the Redis key naming convention' do
      expect(described_class.inbox_unassigned_key(account_id, inbox_id)).to eq(
        'UNREAD_CONVERSATIONS::V1::ACCOUNT::1::INBOX::2::UNASSIGNED'
      )
      expect(described_class.inbox_assignee_key(account_id, inbox_id, user_id)).to eq(
        'UNREAD_CONVERSATIONS::V1::ACCOUNT::1::INBOX::2::ASSIGNEE::4'
      )
      expect(described_class.label_inbox_unassigned_key(account_id, label_id, inbox_id)).to eq(
        'UNREAD_CONVERSATIONS::V1::ACCOUNT::1::LABEL::3::INBOX::2::UNASSIGNED'
      )
      expect(described_class.label_inbox_assignee_key(account_id, label_id, inbox_id, user_id)).to eq(
        'UNREAD_CONVERSATIONS::V1::ACCOUNT::1::LABEL::3::INBOX::2::ASSIGNEE::4'
      )
      expect(described_class.team_inbox_unassigned_key(account_id, team_id, inbox_id)).to eq(
        'UNREAD_CONVERSATIONS::V1::ACCOUNT::1::TEAM::6::INBOX::2::UNASSIGNED'
      )
      expect(described_class.team_inbox_assignee_key(account_id, team_id, inbox_id, user_id)).to eq(
        'UNREAD_CONVERSATIONS::V1::ACCOUNT::1::TEAM::6::INBOX::2::ASSIGNEE::4'
      )
    end
  end

  describe 'ready markers' do
    it 'tracks base and assignment readiness independently' do
      expect(described_class.base_ready?(account_id)).to be(false)
      expect(described_class.assignment_ready?(account_id)).to be(false)

      described_class.mark_base_ready!(account_id)
      described_class.mark_assignment_ready!(account_id)

      expect(described_class.base_ready?(account_id)).to be(true)
      expect(described_class.assignment_ready?(account_id)).to be(true)
      expect(ttl_for('UNREAD_CONVERSATIONS::V1::ACCOUNT::1::READY::BASE')).to be_within(5).of(Conversations::UnreadCounts::READY_TTL)
      expect(ttl_for('UNREAD_CONVERSATIONS::V1::ACCOUNT::1::READY::ASSIGNMENT')).to be_within(5).of(Conversations::UnreadCounts::READY_TTL)
    end
  end

  describe 'set operations' do
    it 'adds, counts, and removes base memberships' do
      described_class.add_base_membership(
        account_id: account_id,
        inbox_id: inbox_id,
        label_ids: [label_id],
        team_id: team_id,
        conversation_id: conversation_id
      )

      expect(described_class.counts_for_keys(base_keys)).to eq(
        described_class.inbox_key(account_id, inbox_id) => 1,
        described_class.label_inbox_key(account_id, label_id, inbox_id) => 1,
        described_class.team_inbox_key(account_id, team_id, inbox_id) => 1
      )
      expect(base_keys.map { |key| ttl_for(key) }).to all(be_within(5).of(Conversations::UnreadCounts::SET_TTL))

      described_class.remove_base_membership(
        account_id: account_id,
        inbox_ids: [inbox_id],
        label_ids: [label_id],
        team_ids: [team_id],
        conversation_id: conversation_id
      )

      expect(described_class.counts_for_keys(base_keys).values).to all(eq(0))
    end

    it 'checks memberships for a conversation across keys' do
      described_class.add_base_membership(
        account_id: account_id,
        inbox_id: inbox_id,
        label_ids: [label_id],
        team_id: team_id,
        conversation_id: conversation_id
      )

      expect(described_class.memberships_for_keys(base_keys, conversation_id)).to eq(
        described_class.inbox_key(account_id, inbox_id) => true,
        described_class.label_inbox_key(account_id, label_id, inbox_id) => true,
        described_class.team_inbox_key(account_id, team_id, inbox_id) => true
      )
      expect(described_class.memberships_for_keys(base_keys, 999).values).to all(be(false))
    end

    it 'adds, counts, and removes assignment-aware memberships' do
      described_class.add_assignment_membership(
        account_id: account_id,
        inbox_id: inbox_id,
        label_ids: [label_id],
        assignee_id: user_id,
        team_id: team_id,
        conversation_id: conversation_id
      )

      expect(described_class.counts_for_keys(assignment_keys)).to eq(
        described_class.inbox_assignee_key(account_id, inbox_id, user_id) => 1,
        described_class.label_inbox_assignee_key(account_id, label_id, inbox_id, user_id) => 1,
        described_class.team_inbox_assignee_key(account_id, team_id, inbox_id, user_id) => 1
      )
      expect(assignment_keys.map { |key| ttl_for(key) }).to all(be_within(5).of(Conversations::UnreadCounts::SET_TTL))

      described_class.remove_assignment_membership(
        account_id: account_id,
        inbox_ids: [inbox_id],
        label_ids: [label_id],
        assignee_ids: [user_id],
        team_ids: [team_id],
        conversation_id: conversation_id
      )

      expect(described_class.counts_for_keys(assignment_keys).values).to all(eq(0))
    end

    it 'sets expiry on bulk membership writes' do
      described_class.add_memberships(
        account_id: account_id,
        memberships: [{
          inbox_id: inbox_id,
          label_ids: [label_id],
          team_id: team_id,
          conversation_id: conversation_id
        }]
      )

      expect(base_keys.map { |key| ttl_for(key) }).to all(be_within(5).of(Conversations::UnreadCounts::SET_TTL))
    end

    it 'clears all account memberships' do
      described_class.mark_base_ready!(account_id)
      described_class.mark_assignment_ready!(account_id)
      described_class.add_base_membership(
        account_id: account_id,
        inbox_id: inbox_id,
        label_ids: [label_id],
        team_id: team_id,
        conversation_id: conversation_id
      )
      described_class.add_assignment_membership(
        account_id: account_id,
        inbox_id: inbox_id,
        label_ids: [label_id],
        assignee_id: user_id,
        team_id: team_id,
        conversation_id: conversation_id
      )

      described_class.clear_account!(account_id)

      expect(described_class.base_ready?(account_id)).to be(false)
      expect(described_class.assignment_ready?(account_id)).to be(false)
      expect(described_class.counts_for_keys(base_keys).values).to all(eq(0))
      expect(described_class.counts_for_keys(assignment_keys).values).to all(eq(0))
    end
  end

  def base_keys
    [
      described_class.inbox_key(account_id, inbox_id),
      described_class.label_inbox_key(account_id, label_id, inbox_id),
      described_class.team_inbox_key(account_id, team_id, inbox_id)
    ]
  end

  def assignment_keys
    [
      described_class.inbox_assignee_key(account_id, inbox_id, user_id),
      described_class.label_inbox_assignee_key(account_id, label_id, inbox_id, user_id),
      described_class.team_inbox_assignee_key(account_id, team_id, inbox_id, user_id)
    ]
  end

  def ttl_for(key)
    Redis::Alfred.ttl(key)
  end
end
