require 'rails_helper'

RSpec.describe Macros::ExecutionService, type: :service do
  let(:account) { create(:account) }
  let(:conversation) { create(:conversation, account: account) }
  let(:user) { create(:user, account: account) }
  let(:macro) { create(:macro, account: account) }
  let(:service) { described_class.new(macro, conversation, user) }

  before do
    create(:inbox_member, user: user, inbox: conversation.inbox)
  end

  describe '#perform' do
    context 'when actions are present' do
      before do
        allow(macro).to receive(:actions).and_return([
                                                       { action_name: 'assign_agent', action_params: ['self'] },
                                                       { action_name: 'add_private_note', action_params: ['Test note'] },
                                                       { action_name: 'send_message', action_params: ['Test message'] },
                                                       { action_name: 'send_attachment', action_params: [1, 2] }
                                                     ])
      end

      it 'executes the actions' do
        expect(service).to receive(:assign_agent).with(['self']).and_call_original
        expect(service).to receive(:add_private_note).with(['Test note']).and_call_original
        expect(service).to receive(:send_message).with(['Test message']).and_call_original
        expect(service).to receive(:send_attachment).with([1, 2]).and_call_original

        service.perform
      end

      context 'when an action raises an error' do
        let(:exception_tracker) { instance_spy(ChatwootExceptionTracker) }

        before do
          allow(ChatwootExceptionTracker).to receive(:new).and_return(exception_tracker)
        end

        it 'captures the exception' do
          allow(service).to receive(:assign_agent).and_raise(StandardError.new('Random error'))
          expect(exception_tracker).to receive(:capture_exception)

          service.perform
        end
      end
    end
  end

  describe '#assign_agent' do
    context 'when agent_ids contains self' do
      it 'updates the conversation assignee to the current user' do
        service.send(:assign_agent, ['self'])
        expect(conversation.reload.assignee).to eq(user)
      end
    end

    context 'when agent_ids does not contain self' do
      let(:other_user) { create(:user, account: account) }

      before do
        create(:inbox_member, user: other_user, inbox: conversation.inbox)
      end

      it 'calls the super method' do
        service.send(:assign_agent, [other_user.id])
        expect(conversation.reload.assignee).to eq(other_user)
      end
    end
  end

  describe '#add_private_note' do
    context 'when conversation is not a tweet' do
      it 'creates a new private message' do
        expect do
          service.send(:add_private_note, ['Test private note'])
        end.to change(Message, :count).by(1)

        message = Message.last
        expect(message.content).to eq('Test private note')
        expect(message.private).to be(true)
      end
    end

    context 'when conversation is a tweet' do
      before { allow(service).to receive(:conversation_a_tweet?).and_return(true) }

      it 'does not create a new message' do
        expect do
          service.send(:add_private_note, ['Test private note'])
        end.not_to change(Message, :count)
      end
    end
  end

  describe '#send_message' do
    context 'when conversation is not a tweet' do
      it 'creates a new public message' do
        expect do
          service.send(:send_message, ['Test message'])
        end.to change(Message, :count).by(1)

        message = Message.last
        expect(message.content).to eq('Test message')
        expect(message.private).to be(false)
      end
    end

    context 'when conversation is a tweet' do
      before { allow(service).to receive(:conversation_a_tweet?).and_return(true) }

      it 'does not create a new message' do
        expect do
          service.send(:send_message, ['Test message'])
        end.not_to change(Message, :count)
      end
    end
  end

  describe '#send_attachment' do
    before do
      macro.files.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
      macro.save!
    end

    context 'when conversation is not a tweet and macro has files attached' do
      before { allow(service).to receive(:conversation_a_tweet?).and_return(false) }

      it 'creates a new message with attachments' do
        expect do
          service.send(:send_attachment, [macro.files.first.blob_id])
        end.to change(Message, :count).by(1)

        message = Message.last
        expect(message.attachments).to be_present
      end
    end

    context 'when conversation is a tweet or macro has no files attached' do
      before { allow(service).to receive(:conversation_a_tweet?).and_return(true) }

      it 'does not create a new message' do
        expect do
          service.send(:send_attachment, [macro.files.first.blob_id])
        end.not_to change(Message, :count)
      end
    end
  end
end
