require 'spec_helper'

describe AffiliatePartner do

  let!(:user) { FactoryGirl.create(:user) }
  let(:partner) {  FactoryGirl.create(:affiliate_partner) }
  let!(:legalese) { FactoryGirl.create(:affiliate_legalese) }
  let(:jam_track) {FactoryGirl.create(:jam_track) }

  describe "unpaid" do
    it "succeeds with no data" do
      AffiliatePartner.unpaid.length.should eq(0)
    end

    it "finds one unpaid partner" do
      quarter = FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner: partner, closed:true, paid:false, due_amount_in_cents: AffiliatePartner::PAY_THRESHOLD)
      AffiliatePartner.unpaid.should eq([partner])
    end

    it "finds one unpaid partner with two quarters that exceed threshold" do
      # this $5 quarter is not enough to make the threshold
      quarter = FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner: partner, year:2016, closed:true, paid:false, due_amount_in_cents: AffiliatePartner::PAY_THRESHOLD / 2)
      AffiliatePartner.unpaid.should eq([])

      # this should get the user over the hump
      quarter = FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner: partner, year:2015, closed:true, paid:false, due_amount_in_cents: AffiliatePartner::PAY_THRESHOLD / 2)
      AffiliatePartner.unpaid.should eq([partner])
    end

    it "does not find paid or closed quarters" do
      quarter = FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner: partner, year:2016, closed:true, paid:true, due_amount_in_cents: AffiliatePartner::PAY_THRESHOLD)
      AffiliatePartner.unpaid.should eq([])

      quarter = FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner: partner, year:2015, closed:false, paid:true, due_amount_in_cents: AffiliatePartner::PAY_THRESHOLD)
      AffiliatePartner.unpaid.should eq([])
    end
  end

  it "user-partner association" do
    user_partner = FactoryGirl.create(:user, affiliate_partner: partner)
    user_partner.affiliate_partner.should_not be_nil
    user_partner.affiliate_partner.present?.should be_true
  end

  it 'has user referrals' do
    pending "bug in rails counter-cache"
    expect(partner.referral_user_count).to eq(0)
    uu = FactoryGirl.create(:user)
    uu.affiliate_referral = partner
    uu.save
    partner.reload
    expect(uu.affiliate_referral).to eq(partner)
    expect(partner.user_referrals.count).to eq(1)
    expect(partner.referral_user_count).to eq(1) # THIS CURRENTLY EQUALS 2 DUE TO BUG IN RAILS (https://github.com/rails/rails/pull/14849)
    expect(partner.user_referrals[0]).to eq(uu)
  end

  it 'groups referrals properly' do
    FactoryGirl.create(:user, :created_at => Time.now - 7.days, :affiliate_referral_id => partner.id)
    FactoryGirl.create(:user, :created_at => Time.now - 7.days, :affiliate_referral_id => partner.id)
    FactoryGirl.create(:user, :created_at => Time.now - 6.days, :affiliate_referral_id => partner.id)
    FactoryGirl.create(:user, :created_at => Time.now - 6.days, :affiliate_referral_id => partner.id)
    FactoryGirl.create(:user, :created_at => Time.now - 3.days, :affiliate_referral_id => partner.id)
    recent = FactoryGirl.create(:user, :created_at => Time.now - 2.days, :affiliate_referral_id => partner.id)
    partner.reload
    expect(partner.referral_user_count).to eq(6)

    by_date = partner.referrals_by_date
    expect(by_date.count).to eq(4)
    keys = by_date.keys
    expect(keys.first).to eq(Date.parse((Time.now.utc - 2.days).to_s))
    expect(by_date[keys.first]).to eq(1)
    expect(keys.last).to eq(Date.parse((Time.now.utc - 7.days).to_s))
    expect(by_date[keys.last]).to eq(2)
  end

  it 'updates address correctly' do 
    addy = partner.address.clone
    addy[AffiliatePartner::KEY_ADDR1] = Faker::Address.street_address
    addy[AffiliatePartner::KEY_ADDR2] = Faker::Address.secondary_address
    addy[AffiliatePartner::KEY_CITY] = Faker::Address.city
    addy[AffiliatePartner::KEY_STATE] = Faker::Address.state_abbr
    addy[AffiliatePartner::KEY_COUNTRY] = Faker::Address.country
    partner.update_address_value(AffiliatePartner::KEY_ADDR1, addy[AffiliatePartner::KEY_ADDR1])
    partner.update_address_value(AffiliatePartner::KEY_ADDR2, addy[AffiliatePartner::KEY_ADDR2])
    partner.update_address_value(AffiliatePartner::KEY_CITY, addy[AffiliatePartner::KEY_CITY])
    partner.update_address_value(AffiliatePartner::KEY_STATE, addy[AffiliatePartner::KEY_STATE])
    partner.update_address_value(AffiliatePartner::KEY_COUNTRY, addy[AffiliatePartner::KEY_COUNTRY])

    expect(partner.address[AffiliatePartner::KEY_ADDR1]).to eq(addy[AffiliatePartner::KEY_ADDR1])
    expect(partner.address[AffiliatePartner::KEY_ADDR2]).to eq(addy[AffiliatePartner::KEY_ADDR2])
    expect(partner.address[AffiliatePartner::KEY_CITY]).to eq(addy[AffiliatePartner::KEY_CITY])
    expect(partner.address[AffiliatePartner::KEY_STATE]).to eq(addy[AffiliatePartner::KEY_STATE])
    expect(partner.address[AffiliatePartner::KEY_COUNTRY]).to eq(addy[AffiliatePartner::KEY_COUNTRY])
  end

  it 'associates legalese' do 
    
  end

  describe "should_attribute_sale?" do

    it "user with no affiliate relationship" do
      shopping_cart = ShoppingCart.create user, jam_track, 1
      user.should_attribute_sale?(shopping_cart).should be_false
    end

    it "user with an affiliate relationship buying a jamtrack" do
      user.affiliate_referral = partner
      user.save!
      shopping_cart = ShoppingCart.create user, jam_track, 1, false
      user.should_attribute_sale?(shopping_cart).should eq({fee_in_cents:20})
    end

    it "user with an affiliate relationship (with a custom rate) buying a jamtrack" do
      user.affiliate_referral = partner
      user.save!
      partner.rate = 0.25
      partner.save!
      shopping_cart = ShoppingCart.create user, jam_track, 1, false
      user.should_attribute_sale?(shopping_cart).should eq({fee_in_cents:50})
    end

    it "user with an affiliate relationship redeeming a jamtrack" do
      user.affiliate_referral = partner
      user.save!
      shopping_cart = ShoppingCart.create user, jam_track, 1, true
      user.should_attribute_sale?(shopping_cart).should eq({fee_in_cents:0})
    end

    it "user with an expired affiliate relationship redeeming a jamtrack" do
      user.affiliate_referral = partner
      user.created_at = (365 * 2 + 1).days.ago
      user.save!
      shopping_cart = ShoppingCart.create user, jam_track, 1, false
      user.should_attribute_sale?(shopping_cart).should be_false
    end
  end

  describe "created_within_affiliate_window" do
    it "user created very recently" do
      partner.created_within_affiliate_window(user, Time.now).should be_true
    end

    it "user created 2 years, 1 day asgo" do
      days_future = 365 * 2 + 1

      partner.created_within_affiliate_window(user, days_future.days.from_now).should be_false
    end
  end

  describe "tally_up" do
    let(:partner1) {FactoryGirl.create(:affiliate_partner)}
    let(:partner2) {FactoryGirl.create(:affiliate_partner)}
    let(:payment1) {FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner: partner1)}
    let(:user_partner1) { FactoryGirl.create(:user, affiliate_referral: partner1)}
    let(:user_partner2) { FactoryGirl.create(:user, affiliate_referral: partner2)}
    let(:sale) {Sale.create_jam_track_sale(user_partner1)}

    describe "ensure_quarters_exist" do

      it "runs OK with no data" do
        AffiliatePartner.ensure_quarters_exist(2015, 0)
        AffiliateQuarterlyPayment.count.should eq(0)
      end

      it "creates new slots" do
        partner1.touch
        partner2.touch
        AffiliatePartner.ensure_quarters_exist(2015, 0)
        AffiliateQuarterlyPayment.count.should eq(2)
        quarter = partner1.quarters.first
        quarter.year.should eq(2015)
        quarter.quarter.should eq(0)
        quarter = partner2.quarters.first
        quarter.year.should eq(2015)
        quarter.quarter.should eq(0)
      end

      it "creates one slot, ignoring other" do
        partner1.touch
        partner2.touch
        payment1.touch
        AffiliateQuarterlyPayment.count.should eq(1)
        AffiliatePartner.ensure_quarters_exist(2015, 0)
        AffiliateQuarterlyPayment.count.should eq(2)
        quarter = partner1.quarters.first
        quarter.year.should eq(2015)
        quarter.quarter.should eq(0)
        quarter = partner2.quarters.first
        quarter.year.should eq(2015)
        quarter.quarter.should eq(0)
      end
    end

    describe "close_quarters" do
      it "runs OK with no data" do
        AffiliateQuarterlyPayment.count.should eq(0)
        AffiliatePartner.close_quarters(2015, 1)
      end

      it "ignores current quarter" do
        partner1.touch
        partner2.touch
        AffiliatePartner.ensure_quarters_exist(2015, 0)
        AffiliateQuarterlyPayment.count.should eq(2)
        AffiliatePartner.close_quarters(2015, 0)
        AffiliateQuarterlyPayment.where(closed: true).count.should eq(0)
      end

      it "closes previous quarter" do
        partner1.touch
        partner2.touch
        AffiliatePartner.ensure_quarters_exist(2015, 0)
        AffiliateQuarterlyPayment.count.should eq(2)
        AffiliatePartner.close_quarters(2015, 1)
        AffiliateQuarterlyPayment.where(closed: true).count.should eq(2)
      end

      it "closes previous quarter (edge case)" do
        partner1.touch
        partner2.touch
        AffiliatePartner.ensure_quarters_exist(2014, 3)
        AffiliateQuarterlyPayment.count.should eq(2)
        AffiliatePartner.close_quarters(2015, 0)
        AffiliateQuarterlyPayment.where(closed: true).count.should eq(2)
      end
    end

    describe "tally_partner_totals" do
      it "runs OK with no data" do
        AffiliatePartner.tally_partner_totals
        AffiliatePartner.count.should eq(0)
      end

      it "updates partner when there is no data to tally" do
        partner1.touch
        partner1.cumulative_earnings_in_cents.should eq(0)
        partner1.referral_user_count.should eq(0)
        AffiliatePartner.tally_partner_totals
        partner1.reload
        partner1.cumulative_earnings_in_cents.should eq(0)
        partner1.referral_user_count.should eq(0)
      end

      it "updates referral_user_count" do
        FactoryGirl.create(:user, affiliate_referral: partner1)
        AffiliatePartner.tally_partner_totals
        partner1.reload
        partner1.referral_user_count.should eq(1)
        partner1.cumulative_earnings_in_cents.should eq(0)

        FactoryGirl.create(:user, affiliate_referral: partner2)
        AffiliatePartner.tally_partner_totals
        partner1.reload
        partner1.referral_user_count.should eq(1)
        partner1.cumulative_earnings_in_cents.should eq(0)
        partner2.reload
        partner2.referral_user_count.should eq(1)
        partner2.cumulative_earnings_in_cents.should eq(0)

        FactoryGirl.create(:user, affiliate_referral: partner2)
        AffiliatePartner.tally_partner_totals
        partner1.reload
        partner1.referral_user_count.should eq(1)
        partner1.cumulative_earnings_in_cents.should eq(0)
        partner2.reload
        partner2.referral_user_count.should eq(2)
        partner2.cumulative_earnings_in_cents.should eq(0)
      end

      it "updates cumulative_earnings_in_cents" do
        FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner:partner1, year:2015, quarter:0, due_amount_in_cents: 0, closed:true, paid:true)
        AffiliatePartner.tally_partner_totals
        partner1.reload
        partner1.referral_user_count.should eq(0)
        partner1.cumulative_earnings_in_cents.should eq(0)

        FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner:partner2, year:2015, quarter:0, due_amount_in_cents: 10, closed:true, paid:true)
        AffiliatePartner.tally_partner_totals
        partner1.reload
        partner1.referral_user_count.should eq(0)
        partner1.cumulative_earnings_in_cents.should eq(0)
        partner2.reload
        partner2.referral_user_count.should eq(0)
        partner2.cumulative_earnings_in_cents.should eq(10)

        FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner:partner2, year:2015, quarter:1, due_amount_in_cents: 100, closed:true, paid:true)
        AffiliatePartner.tally_partner_totals
        partner1.reload
        partner1.referral_user_count.should eq(0)
        partner1.cumulative_earnings_in_cents.should eq(0)
        partner2.reload
        partner2.referral_user_count.should eq(0)
        partner2.cumulative_earnings_in_cents.should eq(110)

        FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner:partner1, year:2015, quarter:1, due_amount_in_cents: 100, closed:true, paid:true)
        AffiliatePartner.tally_partner_totals
        partner1.reload
        partner1.referral_user_count.should eq(0)
        partner1.cumulative_earnings_in_cents.should eq(100)
        partner2.reload
        partner2.referral_user_count.should eq(0)
        partner2.cumulative_earnings_in_cents.should eq(110)

        # a paid=false quarterly payment does not yet reflect in cumulative earnings
        FactoryGirl.create(:affiliate_quarterly_payment, affiliate_partner:partner1, year:2015, quarter:2, due_amount_in_cents: 1000, closed:false, paid:false)
        AffiliatePartner.tally_partner_totals
        partner1.reload
        partner1.referral_user_count.should eq(0)
        partner1.cumulative_earnings_in_cents.should eq(100)
        partner2.reload
        partner2.referral_user_count.should eq(0)
        partner2.cumulative_earnings_in_cents.should eq(110)
      end
    end

    describe "total_quarters" do
      it "runs OK with no data" do
        AffiliateQuarterlyPayment.count.should eq(0)
        AffiliatePartner.total_quarters(2015, 0)
      end

      it "totals 0 with no sales data" do
        partner1.touch
        partner2.touch
        AffiliatePartner.ensure_quarters_exist(2015, 0)
        AffiliateQuarterlyPayment.count.should eq(2)
        AffiliatePartner.total_quarters(2015, 0)
        quarter = partner1.quarters.first
        quarter.due_amount_in_cents.should eq(0)
        quarter.last_updated.should_not be_nil
        quarter = partner2.quarters.first
        quarter.due_amount_in_cents.should eq(0)
        quarter.last_updated.should_not be_nil
      end

      it "totals with sales data" do
        partner1.touch
        partner2.touch


        # create a freebie for partner1
        shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, true
        freebie_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil)
        freebie_sale.affiliate_referral_fee_in_cents.should eq(0)
        freebie_sale.created_at = Date.new(2015, 1, 1)
        freebie_sale.affiliate_distributions.first.created_at = freebie_sale.created_at
        freebie_sale.affiliate_distributions.first.save!
        freebie_sale.save!



        AffiliatePartner.ensure_quarters_exist(2015, 0)
        AffiliateQuarterlyPayment.count.should eq(2)
        AffiliatePartner.total_quarters(2015, 0)
        quarter = partner1.quarters.first
        quarter.due_amount_in_cents.should eq(0)
        quarter.jamtracks_sold.should eq(0)
        quarter = partner2.quarters.first
        quarter.due_amount_in_cents.should eq(0)
        quarter.jamtracks_sold.should eq(0)

        # create a real sale for partner1
        shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false
        real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil)
        real_sale.affiliate_referral_fee_in_cents.should eq(20)
        real_sale.created_at = Date.new(2015, 1, 1)
        real_sale.save!
        real_sale.affiliate_distributions.first.created_at = real_sale.created_at
        real_sale.affiliate_distributions.first.save!
        AffiliatePartner.ensure_quarters_exist(2015, 0)
        AffiliatePartner.total_quarters(2015, 0)
        quarter = partner1.quarters.first
        quarter.due_amount_in_cents.should eq(20)
        quarter.jamtracks_sold.should eq(1)
        quarter = partner2.quarters.first
        quarter.due_amount_in_cents.should eq(0)
        quarter.jamtracks_sold.should eq(0)

        # create a real sale for partner2
        shopping_cart = ShoppingCart.create user_partner2, jam_track, 1, false
        real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil)
        real_sale.affiliate_referral_fee_in_cents.should eq(20)
        real_sale.created_at = Date.new(2015, 1, 1)
        real_sale.save!
        real_sale.affiliate_distributions.first.created_at = real_sale.created_at
        real_sale.affiliate_distributions.first.save!

        AffiliatePartner.ensure_quarters_exist(2015, 0)
        AffiliatePartner.total_quarters(2015, 0)
        quarter = partner1.quarters.first
        quarter.due_amount_in_cents.should eq(20)
        quarter.jamtracks_sold.should eq(1)
        quarter = partner2.quarters.first
        quarter.due_amount_in_cents.should eq(20)
        quarter.jamtracks_sold.should eq(1)

        # create a real sale for partner1
        shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false
        real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil)
        real_sale.affiliate_referral_fee_in_cents.should eq(20)
        real_sale.created_at = Date.new(2015, 1, 1)
        real_sale.save!
        real_sale.affiliate_distributions.first.created_at = real_sale.created_at
        real_sale.affiliate_distributions.first.save!
        AffiliatePartner.ensure_quarters_exist(2015, 0)
        AffiliatePartner.total_quarters(2015, 0)
        quarter = partner1.quarters.first
        quarter.due_amount_in_cents.should eq(40)
        quarter.jamtracks_sold.should eq(2)
        quarter = partner2.quarters.first
        quarter.due_amount_in_cents.should eq(20)
        quarter.jamtracks_sold.should eq(1)


        # create a real sale for a non-affiliated user
        shopping_cart = ShoppingCart.create user, jam_track, 1, false
        real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil)
        real_sale.affiliate_referral_fee_in_cents.should be_nil
        real_sale.created_at = Date.new(2015, 1, 1)
        real_sale.save!
        AffiliatePartner.ensure_quarters_exist(2015, 0)
        AffiliatePartner.total_quarters(2015, 0)
        quarter = partner1.quarters.first
        quarter.due_amount_in_cents.should eq(40)
        quarter.jamtracks_sold.should eq(2)
        quarter = partner2.quarters.first
        quarter.due_amount_in_cents.should eq(20)
        quarter.jamtracks_sold.should eq(1)

        # create a real sale but in previous quarter (should no have effect on the quarter being computed)
        shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false
        real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil)
        real_sale.affiliate_referral_fee_in_cents.should eq(20)
        real_sale.created_at = Date.new(2014, 12, 31)
        real_sale.save!
        real_sale.affiliate_distributions.first.created_at = real_sale.created_at
        real_sale.affiliate_distributions.first.save!
        AffiliatePartner.ensure_quarters_exist(2015, 0)
        AffiliatePartner.total_quarters(2015, 0)
        quarter = partner1.quarters.first
        quarter.due_amount_in_cents.should eq(40)
        quarter.jamtracks_sold.should eq(2)
        quarter = partner2.quarters.first
        quarter.due_amount_in_cents.should eq(20)
        quarter.jamtracks_sold.should eq(1)

        # create a real sale but in later quarter (should no have effect on the quarter being computed)
        shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false
        real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil)
        real_sale.affiliate_referral_fee_in_cents.should eq(20)
        real_sale.created_at = Date.new(2015, 4, 1)
        real_sale.save!
        real_sale.affiliate_distributions.first.created_at = real_sale.created_at
        real_sale.affiliate_distributions.first.save!
        real_sale_later = real_sale
        AffiliatePartner.ensure_quarters_exist(2015, 0)
        AffiliatePartner.total_quarters(2015, 0)
        quarter = partner1.quarters.first
        quarter.due_amount_in_cents.should eq(40)
        quarter.jamtracks_sold.should eq(2)
        quarter = partner2.quarters.first
        quarter.due_amount_in_cents.should eq(20)
        quarter.jamtracks_sold.should eq(1)

        # create a real sale but then refund it
        shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false
        real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil)
        real_sale.affiliate_referral_fee_in_cents.should eq(20)
        real_sale.created_at = Date.new(2015, 3, 31)
        real_sale.save!
        real_sale.affiliate_distributions.first.created_at = real_sale.created_at
        real_sale.affiliate_distributions.first.save!
        AffiliatePartner.ensure_quarters_exist(2015, 0)
        AffiliatePartner.total_quarters(2015, 0)
        quarter = partner1.quarters.first
        quarter.due_amount_in_cents.should eq(60)
        quarter.jamtracks_sold.should eq(3)
        quarter = partner2.quarters.first
        quarter.due_amount_in_cents.should eq(20)
        # now refund it
        real_sale.affiliate_refunded_at = Date.new(2015, 3, 1)
        real_sale.affiliate_refunded = true
        real_sale.save!
        real_sale.affiliate_distributions.first.affiliate_refunded_at = real_sale.affiliate_refunded_at
        real_sale.affiliate_distributions.first.affiliate_refunded = real_sale.affiliate_refunded
        real_sale.affiliate_distributions.first.save!
        AffiliatePartner.ensure_quarters_exist(2015, 0)
        AffiliatePartner.total_quarters(2015, 0)
        quarter = partner1.quarters.first
        quarter.due_amount_in_cents.should eq(40)
        quarter = partner2.quarters.first
        quarter.due_amount_in_cents.should eq(20)
        quarter.jamtracks_sold.should eq(1)


        # create the 2nd quarter, which should add up the sale created a few bits up
        AffiliatePartner.ensure_quarters_exist(2015, 1)
        AffiliatePartner.total_quarters(2015, 1)
        payment = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(1, 2015, partner1.id)
        payment.due_amount_in_cents.should eq(20)

        # and now refund it in the 3rd quarter
        real_sale_later.affiliate_refunded_at = Date.new(2015, 7, 1)
        real_sale_later.affiliate_refunded = true
        real_sale_later.save!
        real_sale_later.affiliate_distributions.first.affiliate_refunded_at = real_sale_later.affiliate_refunded_at
        real_sale_later.affiliate_distributions.first.affiliate_refunded = real_sale_later.affiliate_refunded
        real_sale_later.affiliate_distributions.first.save!
        AffiliatePartner.total_quarters(2015, 1)
        payment = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(1, 2015, partner1.id)
        payment.due_amount_in_cents.should eq(20)
        payment.jamtracks_sold.should eq(1)

        # now catch the one refund in the 3rd quarter
        AffiliatePartner.ensure_quarters_exist(2015, 2)
        AffiliatePartner.total_quarters(2015, 2)
        payment = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(2, 2015, partner1.id)
        payment.due_amount_in_cents.should eq(-20)
        payment.jamtracks_sold.should eq(-1)

      end
    end

    describe "tally_up complete" do
      it "runs OK with no data" do
        AffiliatePartner.tally_up(Date.new(2015, 1, 1))
      end

      it "successive runs" do
        GenericState.singleton.affiliate_tallied_at.should be_nil
        AffiliatePartner.tally_up(Date.new(2015, 1, 1))
        GenericState.singleton.affiliate_tallied_at.should_not be_nil
        AffiliateQuarterlyPayment.count.should eq(0)

        # partner is created
        partner1.touch

        AffiliatePartner.tally_up(Date.new(2015, 1, 1))
        AffiliateQuarterlyPayment.count.should eq(2)
        AffiliateMonthlyPayment.count.should eq(6)

        quarter_previous = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(3, 2014, partner1.id)
        quarter_previous.due_amount_in_cents.should eq(0)
        quarter_previous.closed.should be_true
        quarter = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(0, 2015, partner1.id)
        quarter.due_amount_in_cents.should eq(0)
        quarter.closed.should be_false

        month_previous= AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(10, 2014, partner1.id)
        month_previous.due_amount_in_cents.should eq(0)
        month_previous.closed.should be_true
        month_previous.jamtracks_sold.should eq(0)
        month_previous= AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(11, 2014, partner1.id)
        month_previous.due_amount_in_cents.should eq(0)
        month_previous.closed.should be_true
        month_previous.jamtracks_sold.should eq(0)
        month_previous= AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(12, 2014, partner1.id)
        month_previous.due_amount_in_cents.should eq(0)
        month_previous.closed.should be_true
        month_previous.jamtracks_sold.should eq(0)
        month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(1, 2015, partner1.id)
        month_previous.due_amount_in_cents.should eq(0)
        month_previous.closed.should be_true
        month_previous.jamtracks_sold.should eq(0)
        month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(2, 2015, partner1.id)
        month_previous.due_amount_in_cents.should eq(0)
        month_previous.closed.should be_true
        month.jamtracks_sold.should eq(0)
        month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(3, 2015, partner1.id)
        month_previous.due_amount_in_cents.should eq(0)
        month_previous.closed.should be_true
        month_previous.jamtracks_sold.should eq(0)


        shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false
        real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil)
        real_sale.affiliate_referral_fee_in_cents.should eq(20)
        real_sale.created_at = Date.new(2015, 4, 1)
        real_sale.save!
        real_sale.affiliate_distributions.first.created_at = real_sale.created_at
        real_sale.affiliate_distributions.first.save!

        AffiliatePartner.tally_up(Date.new(2015, 4, 1))
        AffiliateQuarterlyPayment.count.should eq(3)
        quarter = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(0, 2015, partner1.id)
        quarter.due_amount_in_cents.should eq(0)
        quarter.jamtracks_sold.should eq(0)
        quarter.closed.should be_true
        quarter2 = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(1, 2015, partner1.id)
        quarter2.due_amount_in_cents.should eq(20)
        quarter2.jamtracks_sold.should eq(1)
        quarter2.closed.should be_false

        month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(1, 2015, partner1.id)
        month.due_amount_in_cents.should eq(0)
        month.jamtracks_sold.should eq(0)
        month.closed.should be_true
        month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(2, 2015, partner1.id)
        month.due_amount_in_cents.should eq(0)
        month.jamtracks_sold.should eq(0)
        month.closed.should be_true
        month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(3, 2015, partner1.id)
        month.due_amount_in_cents.should eq(0)
        month.jamtracks_sold.should eq(0)
        month.closed.should be_true
        month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(4, 2015, partner1.id)
        month.due_amount_in_cents.should eq(20)
        month.jamtracks_sold.should eq(1)
        month.closed.should be_false
        month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(5, 2015, partner1.id)
        month.due_amount_in_cents.should eq(0)
        month.jamtracks_sold.should eq(0)
        month.closed.should be_false
        month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(6, 2015, partner1.id)
        month.due_amount_in_cents.should eq(0)
        month.jamtracks_sold.should eq(0)
        month.closed.should be_false

        # now sneak in a purchase in the 1st quarter, which makes no sense, but proves that closed quarters are not touched

        shopping_cart = ShoppingCart.create user_partner1, jam_track, 1, false
        real_sale = SaleLineItem.create_from_shopping_cart(sale, shopping_cart, nil, nil, nil)
        real_sale.affiliate_referral_fee_in_cents.should eq(20)
        real_sale.created_at = Date.new(2015, 1, 1)
        real_sale.save!
        real_sale.affiliate_distributions.first.created_at = real_sale.created_at
        real_sale.affiliate_distributions.first.save!

        AffiliatePartner.tally_up(Date.new(2015, 4, 2))
        quarter = AffiliateQuarterlyPayment.find_by_quarter_and_year_and_affiliate_partner_id!(0, 2015, partner1.id)
        quarter.due_amount_in_cents.should eq(0)
        quarter.jamtracks_sold.should eq(0)
        quarter.closed.should be_true

        month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(1, 2015, partner1.id)
        month.due_amount_in_cents.should eq(0)
        month.closed.should be_true
        month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(2, 2015, partner1.id)
        month.due_amount_in_cents.should eq(0)
        month.jamtracks_sold.should eq(0)
        month.closed.should be_true
        month = AffiliateMonthlyPayment.find_by_month_and_year_and_affiliate_partner_id!(3, 2015, partner1.id)
        month.due_amount_in_cents.should eq(0)
        month.jamtracks_sold.should eq(0)
        month.closed.should be_true
      end
    end

    describe "tally_traffic_totals" do

      it "runs OK with no data" do
        AffiliatePartner.tally_traffic_totals(Date.yesterday, Date.today)
      end

      it "can deal with simple signup case" do
        user_partner1.touch

        day0 = user_partner1.created_at.to_date

        # simulate what happens when this scheduled job first ever runs
        AffiliatePartner.tally_traffic_totals(nil, day0)
        AffiliateTrafficTotal.count.should eq(1)
        traffic_total_day_before = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day0 - 1, user_partner1.affiliate_referral_id)
        traffic_total_day_before.visits.should eq(0)
        traffic_total_day_before.signups.should eq(0)

        # then simulate when it runs on the same day as it ran on the day before
        AffiliatePartner.tally_traffic_totals(day0, day0)
        AffiliateTrafficTotal.count.should eq(1)
        traffic_total_day_before = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day0 - 1, user_partner1.affiliate_referral_id)
        traffic_total_day_before.visits.should eq(0)
        traffic_total_day_before.signups.should eq(0)

        # now run it on the next day, which should catch the signup event

        day1 = day0 + 1
        AffiliatePartner.tally_traffic_totals(day0, day1)
        AffiliateTrafficTotal.count.should eq(2)
        traffic_total_day_before = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day0 - 1, user_partner1.affiliate_referral_id)
        traffic_total_day_before.visits.should eq(0)
        traffic_total_day_before.signups.should eq(0)
        traffic_total_day0 = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day0, user_partner1.affiliate_referral_id)
        traffic_total_day0.visits.should eq(0)
        traffic_total_day0.signups.should eq(1)

        # add in a visit
        visit = FactoryGirl.create(:affiliate_referral_visit, affiliate_partner: user_partner1.affiliate_referral)

        # it won't get seen though because we've moved on
        AffiliatePartner.tally_traffic_totals(day1, day1)
        AffiliateTrafficTotal.count.should eq(2)
        traffic_total_day_before = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day0 - 1, user_partner1.affiliate_referral_id)
        traffic_total_day_before.visits.should eq(0)
        traffic_total_day_before.signups.should eq(0)
        traffic_total_day0 = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day0, user_partner1.affiliate_referral_id)
        traffic_total_day0.visits.should eq(0)
        traffic_total_day0.signups.should eq(1)

        # manipulate the visit created_at so we can record it
        visit.created_at = day1
        visit.save!
        day2 = day1 + 1
        AffiliatePartner.tally_traffic_totals(day1, day2)
        AffiliateTrafficTotal.count.should eq(3)
        traffic_total_day_before = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day0 - 1, user_partner1.affiliate_referral_id)
        traffic_total_day_before.visits.should eq(0)
        traffic_total_day_before.signups.should eq(0)
        traffic_total_day0 = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day0, user_partner1.affiliate_referral_id)
        traffic_total_day0.visits.should eq(0)
        traffic_total_day0.signups.should eq(1)
        traffic_total_day1 = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day1, user_partner1.affiliate_referral_id)
        traffic_total_day1.visits.should eq(1)
        traffic_total_day1.signups.should eq(0)

        # now create 2 records on day 2 for visits and signups both, and a partner with their own visit and signup, and do a final check

        user_partner2.touch

        visit2 = FactoryGirl.create(:affiliate_referral_visit, affiliate_partner: user_partner1.affiliate_referral)
        visit3 = FactoryGirl.create(:affiliate_referral_visit, affiliate_partner: user_partner1.affiliate_referral)
        visit_partner2 = FactoryGirl.create(:affiliate_referral_visit, affiliate_partner: user_partner2.affiliate_referral)
        visit2.created_at = day2
        visit3.created_at = day2
        visit_partner2.created_at = day2
        visit2.save!
        visit3.save!
        visit_partner2.save!

        user2 = FactoryGirl.create(:user, affiliate_referral:user_partner1.affiliate_referral)
        user3 = FactoryGirl.create(:user, affiliate_referral:user_partner1.affiliate_referral)
        user2.created_at = day2
        user3.created_at = day2
        user_partner2.created_at = day2
        user2.save!
        user3.save!
        user_partner2.save!


        day3 = day2 + 1
        AffiliatePartner.tally_traffic_totals(day2, day3)
        AffiliateTrafficTotal.count.should eq(5)
        traffic_total_day_before = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day0 - 1, user_partner1.affiliate_referral_id)
        traffic_total_day_before.visits.should eq(0)
        traffic_total_day_before.signups.should eq(0)
        traffic_total_day0 = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day0, user_partner1.affiliate_referral_id)
        traffic_total_day0.visits.should eq(0)
        traffic_total_day0.signups.should eq(1)
        traffic_total_day1 = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day1, user_partner1.affiliate_referral_id)
        traffic_total_day1.visits.should eq(1)
        traffic_total_day1.signups.should eq(0)
        traffic_total_day2 = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day2, user_partner1.affiliate_referral_id)
        traffic_total_day2.visits.should eq(2)
        traffic_total_day2.signups.should eq(2)
        traffic_total_day2 = AffiliateTrafficTotal.find_by_day_and_affiliate_partner_id(day2, user_partner2.affiliate_referral_id)
        traffic_total_day2.visits.should eq(1)
        traffic_total_day2.signups.should eq(1)
      end
    end

    describe "boundary_dates" do
      it "1st quarter" do
        start_date, end_date = AffiliatePartner.boundary_dates(2015, 0)
        start_date.should eq(Date.new(2015, 1, 1))
        end_date.should eq(Date.new(2015, 3, 31))
      end

      it "2nd quarter" do
        start_date, end_date = AffiliatePartner.boundary_dates(2015, 1)
        start_date.should eq(Date.new(2015, 4, 1))
        end_date.should eq(Date.new(2015, 6, 30))
      end

      it "3rd quarter" do
        start_date, end_date = AffiliatePartner.boundary_dates(2015, 2)
        start_date.should eq(Date.new(2015, 7, 1))
        end_date.should eq(Date.new(2015, 9, 30))
      end

      it "4th quarter" do
        start_date, end_date = AffiliatePartner.boundary_dates(2015, 3)
        start_date.should eq(Date.new(2015, 10, 1))
        end_date.should eq(Date.new(2015, 12, 31))
      end
    end

    describe "edge case" do
      it "year change" do
        partner.touch
        last_day_of_year = Date.new(2015, 12, 31)
        first_day_of_next_year = Date.new(2016, 01, 01)
        AffiliatePartner.tally_up(last_day_of_year)
        AffiliatePartner.tally_up(first_day_of_next_year)
        quarterly_payment = AffiliateQuarterlyPayment.where(year: 2016, quarter: 0, affiliate_partner_id: partner.id).first
        quarterly_payment.closed.should be_false
        AffiliatePartner.tally_up(Date.new(2016, 01, 02))
        quarterly_payment.reload
        quarterly_payment.closed.should be_false
      end
    end


    describe "boundary_dates_for_month" do
      it "invalid month" do
        expect{AffiliatePartner.boundary_dates_for_month(2015, 0)}.to raise_error
      end

      it "January" do
        start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 1)
        start_date.should eq(Date.new(2015, 1, 1))
        end_date.should eq(Date.new(2015, 1, 31))
      end

      it "February" do
        start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 2)
        start_date.should eq(Date.new(2015, 2, 1))
        end_date.should eq(Date.new(2015, 2, 28))
      end

      it "March" do
        start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 3)
        start_date.should eq(Date.new(2015, 3, 1))
        end_date.should eq(Date.new(2015, 3, 31))
      end

      it "April" do
        start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 4)
        start_date.should eq(Date.new(2015, 4, 1))
        end_date.should eq(Date.new(2015, 4, 30))
      end

      it "May" do
        start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 5)
        start_date.should eq(Date.new(2015, 5, 1))
        end_date.should eq(Date.new(2015, 5, 31))
      end

      it "June" do
        start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 6)
        start_date.should eq(Date.new(2015, 6, 1))
        end_date.should eq(Date.new(2015, 6, 30))
      end

      it "July" do
        start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 7)
        start_date.should eq(Date.new(2015, 7, 1))
        end_date.should eq(Date.new(2015, 7, 31))
      end

      it "August" do
        start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 8)
        start_date.should eq(Date.new(2015, 8, 1))
        end_date.should eq(Date.new(2015, 8, 31))
      end

      it "September" do
        start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 9)
        start_date.should eq(Date.new(2015, 9, 1))
        end_date.should eq(Date.new(2015, 9, 30))
      end

      it "October" do
        start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 10)
        start_date.should eq(Date.new(2015, 10, 1))
        end_date.should eq(Date.new(2015, 10, 31))
      end

      it "November" do
        start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 11)
        start_date.should eq(Date.new(2015, 11, 1))
        end_date.should eq(Date.new(2015, 11, 30))
      end

      it "December" do
        start_date, end_date = AffiliatePartner.boundary_dates_for_month(2015, 12)
        start_date.should eq(Date.new(2015, 12, 1))
        end_date.should eq(Date.new(2015, 12, 31))
      end

      it "February in a leap year" do
        start_date, end_date = AffiliatePartner.boundary_dates_for_month(2016, 2)
        start_date.should eq(Date.new(2016, 2, 1))
        end_date.should eq(Date.new(2016, 2, 29))
      end
    end
  end

end
