require 'rails_helper'

describe Invoice do
  it 'raises on amount=' do
    expect { build(:invoice, :membership, amount: 1) }.to raise_error(NoMethodError)
  end

  it 'raises on balance=' do
    expect { build(:invoice, balance: 1) }.to raise_error(NoMethodError)
  end

  it 'raises on memberships_amount=' do
    expect { build(:invoice, memberships_amount: 1) }
      .to raise_error(NoMethodError)
  end

  it 'raises on remaining_memberships_amount=' do
    expect { build(:invoice, remaining_memberships_amount: 1) }
      .to raise_error(NoMethodError)
  end

  it 'generates and sets pdf after creation' do
    invoice = create(:invoice, :annual_fee)
    expect(invoice.pdf_file).to be_attached
    expect(invoice.pdf_file.byte_size).to be_positive
  end

  context 'with mail template' do
    it 'sends email when send_email is true on creation' do
      expect { create(:invoice, :annual_fee, :unprocessed) }
        .not_to change { InvoiceMailer.deliveries.size }

      expect { create(:invoice, :annual_fee, :unprocessed, send_email: true) }
        .to change { InvoiceMailer.deliveries.size }.by(1)
    end

    it 'closes invoice before sending email' do
      member = create(:member, annual_fee: 42)
      create(:payment, amount: 100, member: member)
      invoice = create(:invoice, :unprocessed, :annual_fee, member: member, send_email: true)

      mail = InvoiceMailer.deliveries.last
      expect(mail.subject).to eq "Nouvelle facture ##{invoice.id}"
      expect(mail.html_part.body).to include 'cette facture est considérée comme payée'
    end
  end

  it 'updates membership activity_participations_accepted' do
    member = create(:member)
    membership = create(:membership, member: member)
    invoice = build(:invoice,
      member: member,
      paid_missing_activity_participations: 2,
      activity_price: 60)

    expect { invoice.save! }.to change { membership.reload.activity_participations_accepted }.by(2)
    expect { invoice.reload.cancel! }.to change { membership.reload.activity_participations_accepted }.by(-2)
  end

  context 'when annual fee only' do
    let(:invoice) { create(:invoice, :annual_fee) }

    specify { expect(invoice.annual_fee).to be_present }
    specify { expect(invoice.object_type).to eq 'AnnualFee' }
    specify { expect(invoice.memberships_amount).to be_nil }
    specify { expect(invoice.amount).to eq invoice.annual_fee }
  end

  context 'when membership' do
    let(:invoice) { create(:invoice, :membership) }
    let(:amount) { invoice.member.memberships.first.price }

    specify { expect(invoice.annual_fee).to be_nil }
    specify { expect(invoice.object_type).to eq 'Membership' }
    specify { expect(invoice.memberships_amount).to eq amount  }
    specify { expect(invoice.paid_memberships_amount).to be_zero }
    specify { expect(invoice.remaining_memberships_amount).to eq amount }
    specify { expect(invoice.amount).to eq invoice.memberships_amount }

    context 'when paid_memberships_amount set' do
      let(:invoice) do
        create(:invoice, :membership, paid_memberships_amount: 99)
      end

      specify { expect(invoice.memberships_amount).to eq amount - 99 }
      specify { expect(invoice.paid_memberships_amount).to eq 99 }
      specify { expect(invoice.remaining_memberships_amount).to eq amount - 99 }
      specify { expect(invoice.amount).to eq invoice.memberships_amount }
    end

    context 'when membership_amount_fraction set' do
      let(:invoice) do
        create(:invoice, :membership, membership_amount_fraction: 3)
      end

      specify { expect(invoice.memberships_amount).to eq amount / 3.0 }
      specify { expect(invoice.paid_memberships_amount).to be_zero }
      specify { expect(invoice.remaining_memberships_amount).to eq amount }
      specify { expect(invoice.amount).to eq invoice.memberships_amount }
    end

    context 'when annual_fee present as well' do
      let(:invoice) do
        create(:invoice, :annual_fee, :membership)
      end

      specify { expect(invoice.annual_fee).to be_present }
      specify do
        expect(invoice.amount)
          .to eq invoice.memberships_amount + invoice.annual_fee
      end
    end
  end

  context 'when activity_participation' do
    it 'validates activity_price presence when paid_missing_activity_participations is present' do
      invoice = build(:invoice, paid_missing_activity_participations: 1, activity_price: nil)
      expect(invoice).not_to have_valid(:activity_price)
    end

    it 'sets object_type to ActivityParticipation with paid_missing_activity_participations' do
      invoice = create(:invoice, :manual,
        paid_missing_activity_participations: 2,
        activity_price: 21)

      expect(invoice.object_type).to eq 'ActivityParticipation'
      expect(invoice.paid_missing_activity_participations).to eq 2
      expect(invoice.amount).to eq 42
    end
  end

  context 'when acp_share' do
    it 'sets object_type to ACPShare with acp_shares_number' do
      Current.acp.update!(share_price: 250)
      invoice = create(:invoice, :manual, acp_shares_number: -2)

      expect(invoice.object_type).to eq 'ACPShare'
      expect(invoice.acp_shares_number).to eq -2
      expect(invoice.amount).to eq -500
    end
  end

  context 'when other' do
    it 'sets items and round to five cents each item' do
      invoice = create(:invoice, :manual,
        items_attributes: {
          '0' => { description: 'Un truc cool pas cher', amount: '10.11' },
          '1' => { description: 'Un truc cool pluc cher', amount: '32.33' }
        })

      expect(invoice.object_type).to eq 'Other'
      expect(invoice.items.first.amount).to eq 10.1
      expect(invoice.items.last.amount).to eq 32.35
      expect(invoice.amount).to eq 42.45
    end
  end

  describe '#send!' do
    let(:invoice) { create(:invoice, :annual_fee, :not_sent) }

    it 'delivers email' do
      expect { invoice.send! }
        .to change { InvoiceMailer.deliveries.size }.by(1)
      mail = InvoiceMailer.deliveries.last
      expect(mail.subject).to eq "Nouvelle facture ##{invoice.id}"
    end

    it 'touches sent_at' do
      expect { invoice.send! }.to change(invoice, :sent_at).from(nil)
    end

    it 'sets invoice as open' do
      expect { invoice.send! }.to change(invoice, :state).to('open')
    end

    it 'does nothing when already sent' do
      invoice.touch(:sent_at)
      expect { invoice.send! }
        .not_to change { InvoiceMailer.deliveries.size }
    end

    it 'does nothing when member has no email' do
      invoice.member.update(emails: '')
      expect { invoice.send! }
        .not_to change { InvoiceMailer.deliveries.size }
      expect(invoice.reload.sent_at).to be_nil
    end
  end

  describe '#mark_as_sent!' do
    let(:invoice) { create(:invoice, :annual_fee, :not_sent) }

    it 'does not deliver email' do
      expect { invoice.mark_as_sent! }
        .not_to change { InvoiceMailer.deliveries.size }
    end

    it 'touches sent_at' do
      expect { invoice.mark_as_sent! }.to change(invoice, :sent_at).from(nil)
    end

    it 'sets invoice as open' do
      expect { invoice.send! }.to change(invoice, :state).to('open')
    end
  end

  describe 'set_memberships_vat_amount', freeze: '01-06-2018' do
    it 'does not set it for non-membership invoices' do
      invoice = create(:invoice, :annual_fee)
      expect(invoice.memberships_vat_amount).to be_nil
    end

    it 'does not set it when ACP as no VAT set' do
      Current.acp.update!(vat_membership_rate: nil)

      invoice = create(:invoice, :membership)
      expect(invoice.memberships_vat_amount).to be_nil
    end

    it 'sets the vat_amount for membership invoice and ACP with rate set' do
      Current.acp.update!(vat_membership_rate: 7.7, vat_number: 'XXX')
      invoice = create(:invoice, :membership)

      expect(invoice.memberships_gross_amount).to eq 120
      expect(invoice.memberships_net_amount).to eq BigDecimal(111.42, 6)
      expect(invoice.memberships_vat_amount).to eq BigDecimal(8.58, 4)
    end
  end

  describe '#handle_acp_shares_change!' do
    before { Current.acp.update!(share_price: 250) }

    it 'changes inactive member state to support' do
      member = create(:member, :inactive)
      expect { create(:invoice, member: member, acp_shares_number: 1) }
        .to change { member.reload.state }.from('inactive').to('support')
    end

    it 'changes support member state to inactive' do
      member = create(:member, :support_acp_share, acp_shares_number: 1)
      expect { create(:invoice, member: member, acp_shares_number: -1) }
        .to change { member.reload.state }.from('support').to('inactive')
    end
  end

  describe '#can_destroy?' do
    it 'can destroy not sent invoice' do
      invoice = create(:invoice, :annual_fee, :not_sent)
      expect(invoice.can_destroy?).to eq true
    end

    it 'can not destroy not sent invoice with payments' do
      invoice = create(:invoice, :annual_fee, :not_sent)
      create(:payment, invoice: invoice)
      expect(invoice.can_destroy?).to eq false
    end

    it 'can not destroy open invoice' do
      invoice = create(:invoice, :annual_fee, :open)
      expect(invoice.can_destroy?).to eq false
    end
  end

  specify '#overpaid?' do
    invoice = create(:invoice, :manual,
      items_attributes: {
        '0' => { description: 'Un truc cool pas cher', amount: '100' }
      })
    create(:invoice, :manual,
      member: invoice.member,
      items_attributes: {
        '0' => { description: 'Un truc cool pas cher', amount: '400' }
      })

    create(:payment, invoice: invoice, amount: 100)

    expect {
      create(:payment, invoice: invoice, amount: 100)
    }.to change { invoice.reload.overpaid? }.to(true)
  end

  describe '#send_overpaid_notification_to_admins!' do
    let(:member) { create(:member, name: 'Martha') }
    let(:invoice) {
      create(:invoice, :manual,
        id: 42,
        member: member,
        items_attributes: {
          '0' => { description: 'Un truc cool pas cher', amount: '100' }
        })
    }

    specify 'send notification and touch overpaid_notification_sent_at' do
      create(:payment, invoice: invoice, amount: 110)
      admin = create(:admin, name: 'John', notifications: %w[invoice_overpaid])

      expect { invoice.send_overpaid_notification_to_admins! }
        .to change { invoice.reload.overpaid_notification_sent_at }.from(nil)
        .and change { AdminMailer.deliveries.size }.by(1)

      mail = AdminMailer.deliveries.last
      expect(mail.subject).to eq 'Facture #42 payée en trop'
      expect(mail.to).to eq [admin.email]
      expect(mail.body.encoded).to include 'Salut John,'
      expect(mail.body.encoded).to include 'Martha'
    end

    specify 'when not overpaid' do
      create(:payment, invoice: invoice, amount: 100)

      expect { invoice.send_overpaid_notification_to_admins! }
        .not_to change { invoice.reload.overpaid_notification_sent_at }
      expect(AdminMailer.deliveries.size).to be_zero
    end

    specify 'when already notified' do
      create(:payment, invoice: invoice, amount: 110)
      admin = create(:admin, notifications: %w[invoice_overpaid])

      invoice.send_overpaid_notification_to_admins!
      expect(AdminMailer.deliveries.size).to eq 1

      expect {
        expect {
          invoice.reload.send_overpaid_notification_to_admins!
        }.not_to change { invoice.reload.overpaid_notification_sent_at }
      }.not_to change { AdminMailer.deliveries.size }
    end
  end
end
