# frozen_string_literal: true

require 'rails_helper'

describe Planting do
  let(:crop)         { FactoryBot.create(:tomato)                                                            }
  let(:garden_owner) { FactoryBot.create(:member, login_name: 'hatupatu')                                    }
  let(:garden)       { FactoryBot.create(:garden, owner: garden_owner, name: 'Springfield Community Garden') }
  let(:planting)     { FactoryBot.create(:planting, crop:, garden:, owner: garden.owner) }

  describe 'planting lifespan predictions' do
    context 'no predications data yet' do
      describe 'planting planted, not finished' do
        let(:planting) { FactoryBot.create(:planting, planted_at: 30.days.ago, finished_at: nil, finished: false) }

        it { expect(planting.crop.median_lifespan).to be_nil }
        it { expect(planting.expected_lifespan).to be_nil }
        it { expect(planting.age_in_days).to eq(30) }
        it { expect(planting.percentage_grown).to be_nil }
      end

      describe 'planting not planted yet' do
        let(:planting) { FactoryBot.create(:planting, planted_at: nil, finished_at: nil, finished: false) }

        it { expect(planting.crop.median_lifespan).to be_nil }
        it { expect(planting.expected_lifespan).to be_nil }
        it { expect(planting.age_in_days).to be_nil }
        it { expect(planting.percentage_grown).to eq(0) }
      end

      describe 'planting finished, no planted_at' do
        let(:planting) { FactoryBot.create(:planting, planted_at: nil, finished_at: 1.day.ago, finished: true) }

        it { expect(planting.crop.median_lifespan).to be_nil }
        it { expect(planting.expected_lifespan).to be_nil }
        it { expect(planting.age_in_days).to be_nil }
        it { expect(planting.percentage_grown).to eq(100) }
      end

      describe 'planting all finished' do
        let(:planting) { FactoryBot.create(:planting, planted_at: 30.days.ago, finished_at: 10.days.ago, finished: true) }

        it { expect(planting.crop.median_lifespan).to be_nil }
        it { expect(planting.expected_lifespan).to eq(20) }
        it { expect(planting.age_in_days).to eq(20) }
        it { expect(planting.percentage_grown).to eq(100) }
      end

      describe 'planting finishing in the future' do
        let(:planting) { FactoryBot.create(:planting, planted_at: 30.days.ago, finished_at: 10.days.from_now, finished: false) }

        it { expect(planting.expected_lifespan).to eq(40) }
        it { expect(planting.age_in_days).to eq(30) }
        it { expect(planting.percentage_grown).to eq(75) }
      end
    end

    context 'lots of data' do
      before do
        FactoryBot.create(:planting, crop: planting.crop, planted_at: 10.days.ago)
        FactoryBot.create(:planting, crop: planting.crop, planted_at: 100.days.ago, finished_at: 50.days.ago)
        FactoryBot.create(:planting, crop: planting.crop, planted_at: 100.days.ago, finished_at: 51.days.ago)
        FactoryBot.create(:planting, crop: planting.crop, planted_at: 2.years.ago, finished_at: 50.days.ago)
        FactoryBot.create(:planting, crop: planting.crop, planted_at: 150.days.ago, finished_at: 100.days.ago)
        planting.crop.update_lifespan_medians
      end

      it { expect(planting.crop.median_lifespan).to eq 50 }

      describe 'planting 30 days ago, not finished' do
        let(:planting) { FactoryBot.create(:planting, planted_at: 30.days.ago) }

        # 30 / 50 = 60%
        it { expect(planting.percentage_grown).to eq 60.0 }
        # planted 30 days ago
        it { expect(planting.age_in_days).to eq 30 }
        # means 20 days to go
        it { expect(planting.finish_predicted_at).to eq Time.zone.today + 20.days }
      end

      describe 'child crop uses parent data' do
        let(:child_crop)     { FactoryBot.create(:crop, parent: crop, name: 'child')                   }
        let(:child_planting) { FactoryBot.create(:planting, crop: child_crop, planted_at: 30.days.ago) }

        # not data for this crop
        it { expect(child_crop.median_lifespan).to be_nil }
        # 30 / 50 = 60%
        it { expect(child_planting.percentage_grown).to eq 60.0 }
        # planted 30 days ago
        it { expect(child_planting.age_in_days).to eq 30 }
        # means 20 days to go
        it { expect(child_planting.finish_predicted_at).to eq Time.zone.today + 20.days }
      end

      describe 'planting not planted yet' do
        let(:planting) { FactoryBot.create(:planting, planted_at: nil, finished_at: nil) }

        it { expect(planting.percentage_grown).to eq 0 }
      end

      describe 'planting finished 10 days, but was never planted' do
        let(:planting) { FactoryBot.create(:planting, planted_at: nil, finished_at: 10.days.ago) }

        it { expect(planting.percentage_grown).to eq 100 }
      end

      describe 'planted 30 days ago, finished 10 days ago' do
        let(:planting) { FactoryBot.create(:planting, planted_at: 30.days.ago, finished_at: 10.days.ago) }

        it { expect(planting.age_in_days).to eq 20 }
        it { expect(planting.percentage_grown).to eq 100 }
      end
    end
  end

  describe 'planting first harvest preductions' do
    context 'no data' do
      let(:planting) { FactoryBot.create(:planting) }

      it { expect(planting.crop.median_days_to_first_harvest).to be_nil }
      it { expect(planting.crop.median_days_to_last_harvest).to be_nil }
      it { expect(planting.days_to_first_harvest).to be_nil }
      it { expect(planting.days_to_last_harvest).to be_nil }
      it { expect(planting.expected_lifespan).to be_nil }
    end

    context 'lots of data' do
      let(:crop) { FactoryBot.create(:crop) }
      # this is a method so it creates a new one each time

      def one_hundred_day_old_planting
        FactoryBot.create(:planting, crop:, planted_at: 100.days.ago)
      end
      before do
        # 50 days to harvest
        FactoryBot.create(:harvest, harvested_at: 50.days.ago, crop: planting.crop,
                                    planting: one_hundred_day_old_planting)
        # 20 days to harvest
        FactoryBot.create(:harvest, harvested_at: 80.days.ago, crop: planting.crop,
                                    planting: one_hundred_day_old_planting)
        # 10 days to harvest
        FactoryBot.create(:harvest, harvested_at: 90.days.ago, crop: planting.crop,
                                    planting: one_hundred_day_old_planting)

        planting.crop.plantings.each(&:update_harvest_days!)
        planting.crop.update_lifespan_medians
        planting.crop.update_harvest_medians
      end

      it { expect(crop.median_days_to_first_harvest).to eq(20) }

      describe 'sets median time to harvest' do
        let(:planting) { FactoryBot.create(:planting, crop:, planted_at: Time.zone.today) }

        it { expect(planting.first_harvest_predicted_at).to eq(Time.zone.today + 20.days) }
      end

      describe 'harvest still growing' do
        let(:planting) { FactoryBot.create(:planting, crop:, planted_at: Time.zone.today) }

        it { expect(planting.before_harvest_time?).to be true }
        it { expect(planting.harvest_time?).to be false }
      end

      describe 'harvesting ready now' do
        let(:planting) { FactoryBot.create(:planting, crop:, planted_at: 21.days.ago) }

        it { expect(planting.first_harvest_predicted_at).to eq(1.day.ago.to_date) }
        it { expect(planting.before_harvest_time?).to be false }
        it { expect(planting.harvest_time?).to be true }
      end
    end

    describe 'planting has no harvests' do
      let(:planting) { FactoryBot.create(:planting) }

      before do
        planting.update_harvest_days!
        planting.crop.update_harvest_medians
      end

      it { expect(planting.days_to_first_harvest).to be_nil }
      it { expect(planting.days_to_last_harvest).to be_nil }
    end

    describe 'planting has first harvest' do
      let(:planting) { FactoryBot.create(:planting, planted_at: 100.days.ago) }

      before do
        FactoryBot.create(:harvest,
                          planting:,
                          crop:         planting.crop,
                          harvested_at: 10.days.ago)
        planting.update_harvest_days!
        planting.crop.update_harvest_medians
      end

      it { expect(planting.days_to_first_harvest).to eq(90) }
      it { expect(planting.days_to_last_harvest).to be_nil }
      it { expect(planting.crop.median_days_to_first_harvest).to eq(90) }
      it { expect(planting.crop.median_days_to_last_harvest).to be_nil }
    end

    describe 'planting has last harvest' do
      let(:planting) { FactoryBot.create(:planting, planted_at: 100.days.ago, finished_at: 1.day.ago, finished: true) }

      before do
        FactoryBot.create(:harvest, planting:, crop: planting.crop, harvested_at: 90.days.ago)
        FactoryBot.create(:harvest, planting:, crop: planting.crop, harvested_at: 10.days.ago)
        planting.update_harvest_days!
        planting.crop.update_harvest_medians
      end

      it { expect(planting.days_to_first_harvest).to eq(10) }
      it { expect(planting.days_to_last_harvest).to eq(90) }
      it { expect(planting.crop.median_days_to_first_harvest).to eq(10) }
      it { expect(planting.crop.median_days_to_last_harvest).to eq(90) }
    end
  end

  describe 'planting perennial' do
    let(:crop) { FactoryBot.create(:crop, name: 'feijoa', perennial: true) }

    it { expect(planting.perennial?).to be true }

    describe 'no harvest to predict from' do
      it { expect(planting.harvest_months).to eq({}) }
    end

    describe 'harvests used to predict' do
      before do
        FactoryBot.create(:harvest, planting:, crop:, harvested_at: '1 May 2019')
        FactoryBot.create(:harvest, planting:, crop:, harvested_at: '18 June 2019')
        FactoryBot.create_list(:harvest, 4, planting:, crop:, harvested_at: '18 August 2019')
      end

      it { expect(planting.harvest_months).to eq(5 => 1, 6 => 1, 8 => 4) }
    end

    describe 'nearby plantings used to predict' do
      # Note the locations used need to be stubbed in geocoder
      let(:garden) { FactoryBot.create(:garden, location: 'Edinburgh', owner: garden_owner) }

      before do
        # Near by planting with harvests
        nearby_garden = FactoryBot.create(:garden, location: 'Greenwich, UK')
        nearby_planting = FactoryBot.create(:planting,
                                            crop:,
                                            garden:     nearby_garden,
                                            owner:      nearby_garden.owner,
                                            planted_at: '1 January 2000')
        FactoryBot.create(:harvest, planting: nearby_planting, crop:,
                                    harvested_at: '1 May 2019')
        FactoryBot.create(:harvest, planting: nearby_planting, crop:,
                                    harvested_at: '18 June 2019')
        FactoryBot.create_list(:harvest, 4, planting: nearby_planting, crop:,
                                            harvested_at: '18 August 2008')

        # far away planting harvests
        faraway_garden = FactoryBot.create(:garden, location: 'Amundsen-Scott Base, Antarctica')
        faraway_planting = FactoryBot.create(:planting, garden: faraway_garden, crop:,
                                                        owner: faraway_garden.owner, planted_at: '16 May 2001')

        FactoryBot.create_list(:harvest, 4, planting: faraway_planting, crop:,
                                            harvested_at: '18 December 2006')
      end

      it { expect(planting.harvest_months).to eq(5 => 1, 6 => 1, 8 => 4) }
    end
  end

  it 'has an owner' do
    planting.owner.should be_an_instance_of Member
  end

  it "generates a location" do
    planting.location.should eq garden.location
  end

  it "has a slug" do
    planting.slug.should match(/^hatupatu-springfield-community-garden-tomato$/)
  end

  it 'sorts in reverse creation order' do
    @planting2 = FactoryBot.create(:planting)
    described_class.first.should eq @planting2
  end

  describe '#planted?' do
    it "is false for future plantings" do
      planting = FactoryBot.create(:planting, planted_at: Time.zone.today + 1)
      expect(planting.planted?).to be(false)
    end

    it "is false for never planted" do
      planting = FactoryBot.create(:planting, planted_at: nil)
      expect(planting.planted?).to be(false)
    end

    it "is true for past plantings" do
      planting = FactoryBot.create(:planting, planted_at: Time.zone.today - 1)
      expect(planting.planted?).to be(true)
    end
  end

  context 'delegation' do
    it 'system name' do
      planting.crop_name.should eq planting.crop.name
    end

    it 'wikipedia url' do
      planting.crop_en_wikipedia_url.should eq planting.crop.en_wikipedia_url
    end

    it 'default scientific name' do
      planting.crop_default_scientific_name.should eq planting.crop.default_scientific_name
    end

    it 'plantings count' do
      planting.crop_plantings_count.should eq planting.crop.plantings_count
    end
  end

  context 'quantity' do
    it 'allows integer quantities' do
      @planting = FactoryBot.build(:planting, quantity: 99)
      @planting.should be_valid
    end

    it "doesn't allow decimal quantities" do
      @planting = FactoryBot.build(:planting, quantity: 99.9)
      @planting.should_not be_valid
    end

    it "doesn't allow non-numeric quantities" do
      @planting = FactoryBot.build(:planting, quantity: 'foo')
      @planting.should_not be_valid
    end

    it "allows blank quantities" do
      @planting = FactoryBot.build(:planting, quantity: nil)
      @planting.should be_valid
      @planting = FactoryBot.build(:planting, quantity: '')
      @planting.should be_valid
    end
  end

  context 'sunniness' do
    let(:planting) { FactoryBot.create(:sunny_planting) }

    it 'has a sunniness value' do
      planting.sunniness.should eq 'sun'
    end

    it 'all three valid sunniness values should work' do
      ['sun', 'shade', 'semi-shade', nil, ''].each do |s|
        @planting = FactoryBot.build(:planting, sunniness: s)
        @planting.should be_valid
      end
    end

    it 'refuses invalid sunniness values' do
      @planting = FactoryBot.build(:planting, sunniness: 'not valid')
      @planting.should_not be_valid
      @planting.errors[:sunniness].should include("not valid is not a valid sunniness value")
    end
  end

  context 'planted from' do
    it 'has a planted_from value' do
      @planting = FactoryBot.create(:seed_planting)
      @planting.planted_from.should eq 'seed'
    end

    it 'all valid planted_from values should work' do
      [
        'seed', 'seedling', 'cutting', 'root division',
        'runner', 'bare root plant', 'advanced plant',
        'graft', 'layering', 'bulb', 'root/tuber', nil, ''
      ].each do |p|
        @planting = FactoryBot.build(:planting, planted_from: p)
        @planting.should be_valid
      end
    end

    it 'refuses invalid planted_from values' do
      @planting = FactoryBot.build(:planting, planted_from: 'not valid')
      @planting.should_not be_valid
      @planting.errors[:planted_from].should include("not valid is not a valid planting method")
    end
  end

  # we decided that all the tests for the planting/photo association would
  # be done on this side, not on the photos side
  context 'photos' do
    let(:planting) { FactoryBot.create(:planting) }
    let(:photo) { FactoryBot.create(:photo, owner_id: planting.owner_id) }

    before { planting.photos << photo }

    it 'has a photo' do
      expect(planting.photos.first).to eq photo
    end

    it 'is found in has_photos scope' do
      expect(described_class.has_photos).to include(planting)
    end

    it 'deletes association with photos when photo is deleted' do
      photo.destroy
      planting.reload
      expect(planting.photos).to be_empty
    end

    it 'has a default photo' do
      expect(planting.default_photo).to eq photo
    end

    it 'chooses the most recent photo' do
      @photo2 = FactoryBot.create(:photo, owner: planting.owner)
      planting.photos << @photo2
      expect(planting.default_photo).to eq @photo2
    end
  end

  context 'interesting plantings' do
    describe 'picks up interesting plantings' do
      before do
        # plantings have members created implicitly for them
        # each member is different, hence these are all interesting
        @planting1 = FactoryBot.create(:planting, :with_photo, planted_at: 5.days.ago)
        @planting2 = FactoryBot.create(:planting, :with_photo, planted_at: 4.days.ago)
        @planting3 = FactoryBot.create(:planting, :with_photo, planted_at: 3.days.ago)
        @planting4 = FactoryBot.create(:planting, :with_photo, planted_at: 2.days.ago)
      end

      it { expect(described_class.interesting).to eq([@planting4, @planting3, @planting2, @planting1]) }
    end

    context "default arguments" do
      it 'ignores plantings without photos' do
        # first, an interesting planting
        @planting = FactoryBot.create(:planting)
        @planting.photos << FactoryBot.create(:photo, owner: @planting.owner)
        @planting.save

        # this one doesn't have a photo
        @no_photo_planting = FactoryBot.create(:planting)

        expect(described_class.interesting).to include @planting
        expect(described_class.interesting).not_to include @no_photo_planting
      end

      it 'ignores plantings with the same owner' do
        # this planting is older
        @planting1 = FactoryBot.create(:planting, created_at: 1.day.ago)
        @planting1.photos << FactoryBot.create(:photo, owner_id: @planting1.owner_id)
        @planting1.save

        # this one is newer, and has the same owner, through the garden
        @planting2 = FactoryBot.create(:planting,
                                       created_at: 1.minute.ago,
                                       garden:     @planting1.garden,
                                       owner:      @planting1.owner)
        @planting2.photos << FactoryBot.create(:photo, owner: @planting2.owner)
        @planting2.save

        # result: the newer one is interesting, the older one isn't
        expect(described_class.interesting).to include @planting2
        expect(described_class.interesting).not_to include @planting1
      end
    end

    context "with howmany argument" do
      it "only returns the number asked for" do
        @plantings = FactoryBot.create_list(:planting, 10)
        @plantings.each do |p|
          p.photos << FactoryBot.create(:photo, owner: p.owner)
        end
        expect(described_class.interesting.limit(3).count).to eq 3
      end
    end
  end # interesting plantings

  context "finished" do
    it 'has finished fields' do
      @planting = FactoryBot.create(:finished_planting)
      @planting.finished.should be true
      @planting.finished_at.should be_an_instance_of Date
    end

    it 'has finished scope' do
      @p = FactoryBot.create(:planting)
      @f = FactoryBot.create(:finished_planting)
      described_class.finished.should include @f
      described_class.finished.should_not include @p
    end

    it 'has current scope' do
      @p = FactoryBot.create(:planting)
      @f = FactoryBot.create(:finished_planting)
      described_class.current.should include @p
      described_class.current.should_not include @f
    end

    context "finished date validation" do
      it 'requires finished date after planting date' do
        @f = FactoryBot.build(:finished_planting, planted_at: '2014-01-01', finished_at: '2013-01-01')
        @f.should_not be_valid
      end

      it 'allows just the planted date' do
        @f = FactoryBot.build(:planting, planted_at: '2013-01-01', finished_at: nil)
        @f.should be_valid
      end

      it 'allows just the finished date' do
        @f = FactoryBot.build(:planting, finished_at: '2013-01-01', planted_at: nil)
        @f.should be_valid
      end
    end
  end

  context "failed" do
    let(:failed_planting) { FactoryBot.create(:planting, failed: true) }

    it 'has a failed field' do
      expect(failed_planting.failed).to be true
    end

    it 'has a failed scope' do
      @p = FactoryBot.create(:planting)
      @f = FactoryBot.create(:planting, failed: true)
      described_class.failed.should include @f
      described_class.failed.should_not include @p
    end

    it 'is not included in the active scope' do
      @p = FactoryBot.create(:planting)
      @f = FactoryBot.create(:planting, failed: true)
      described_class.active.should include @p
      described_class.active.should_not include @f
    end

    it 'cannot be finished and failed' do
      @f = FactoryBot.build(:planting, finished: true, failed: true)
      @f.should_not be_valid
    end

    it 'is not finished' do
      @f = FactoryBot.build(:planting, finished: true, failed: true)
      expect(@f.finished?).to be false
    end
  end

  it 'excludes deleted members' do
    expect(described_class.joins(:owner).all).to include(planting)
    planting.owner.destroy
    expect(described_class.joins(:owner).all).not_to include(planting)
  end

  context 'ancestry' do
    let(:parent_seed) { FactoryBot.create(:seed) }
    let(:planting) { FactoryBot.create(:planting, parent_seed:) }

    it "planting has a parent seed" do
      expect(planting.parent_seed).to eq(parent_seed)
    end

    it "seed has a child planting" do
      expect(parent_seed.child_plantings).to eq [planting]
    end

    describe 'grandchildren' do
      let(:grandchild_seed) { FactoryBot.create(:seed, parent_planting: planting) }

      it { expect(grandchild_seed.parent_planting).to eq planting }
      it { expect(grandchild_seed.parent_planting.parent_seed).to eq parent_seed }
    end
  end

  describe 'active scope' do
    let(:member) { FactoryBot.create(:member) }
    let!(:planting) do
      FactoryBot.create(:planting, owner: member, garden: member.gardens.first)
    end
    let!(:finished_planting) do
      FactoryBot.create(:finished_planting, owner: member, garden: member.gardens.first)
    end
    let!(:failed_planting) do
      FactoryBot.create(:planting, failed: true, owner: member, garden: member.gardens.first)
    end

    it { expect(member.plantings.active).to include(planting) }
    it { expect(member.plantings.active).not_to include(finished_planting) }
    it { expect(member.plantings.active).not_to include(failed_planting) }
  end

  describe 'homepage', :search do
    subject { described_class.homepage_records(100) }

    let!(:interesting_planting) { FactoryBot.create(:planting, :reindex, :with_photo) }
    let!(:finished_interesting_planting) { FactoryBot.create(:finished_planting, :reindex, :with_photo) }
    let!(:planting) { FactoryBot.create(:planting, :reindex) }

    before { described_class.reindex }

    it { expect(subject.count).to eq 2 }
    it { expect(subject.map(&:id)).to eq([interesting_planting.id.to_s, finished_interesting_planting.id.to_s]) }
  end
end
