# frozen_string_literal: true

require 'rails_helper'

describe Crop do
  context 'all fields present' do
    let(:crop) { FactoryBot.create(:tomato) }

    it 'saves a basic crop' do
      crop.save.should be(true)
    end

    it 'is fetchable from the database' do
      crop.save
      @crop2 = described_class.find_by(name: 'tomato')
      @crop2.en_wikipedia_url.should eq("http://en.wikipedia.org/wiki/Tomato")
      @crop2.slug.should eq("tomato")
    end

    it 'stringifies as the system name' do
      crop.save
      crop.to_s.should eq('tomato')
    end

    it 'has a creator' do
      crop.save
      crop.creator.should be_an_instance_of Member
    end
  end

  context 'invalid data' do
    it 'does not save a crop without a system name' do
      crop = FactoryBot.build(:crop, name: nil)
      expect { crop.save }.to raise_error ActiveRecord::StatementInvalid
    end
  end

  context 'ordering' do
    before do
      @uppercase = FactoryBot.create(:uppercasecrop, created_at: 1.minute.ago)
      @lowercase = FactoryBot.create(:lowercasecrop, created_at: 2.days.ago)
    end

    it 'recent scope sorts by creation date' do
      described_class.recent.first.should == @uppercase
    end
  end

  context 'popularity' do
    let(:tomato)   { FactoryBot.create(:tomato)                 }
    let(:maize)    { FactoryBot.create(:maize)                  }

    before do
      FactoryBot.create_list(:planting, 10, crop: maize)
      FactoryBot.create_list(:planting, 3, crop: tomato)
    end

    it "sorts by most plantings" do
      expect(described_class.popular.first).to eq maize
      FactoryBot.create_list(:planting, 10, crop: tomato)
      expect(described_class.popular.first).to eq tomato
    end
  end

  it 'finds a default scientific name' do
    @crop = FactoryBot.create(:tomato)
    expect(@crop.default_scientific_name).to be_nil
    @sn = FactoryBot.create(:solanum_lycopersicum, crop: @crop)
    @crop.reload
    expect(@crop.default_scientific_name.to_s).to eq @sn.name
  end

  it 'counts plantings' do
    @crop = FactoryBot.create(:tomato)
    expect(@crop.plantings.size).to eq 0
    @planting = FactoryBot.create(:planting, crop: @crop)
    @crop.reload
    expect(@crop.plantings.size).to eq 1
  end

  context "wikipedia url" do
    subject { FactoryBot.build(:tomato, en_wikipedia_url: wikipedia_url) }

    context 'not a url' do
      let(:wikipedia_url) { 'this is not valid' }

      it { expect(subject).not_to be_valid }
    end

    context 'http url' do
      let(:wikipedia_url) { 'http://en.wikipedia.org/wiki/SomePage' }

      it { expect(subject).to be_valid }
    end

    context 'with ssl' do
      let(:wikipedia_url) { 'https://en.wikipedia.org/wiki/SomePage' }

      it { expect(subject).to be_valid }
    end

    context 'with utf8 macrons' do
      let(:wikipedia_url) { 'https://en.wikipedia.org/wiki/Māori' }

      it { expect(subject).to be_valid }
    end

    context 'urlencoded' do
      let(:wikipedia_url) { 'https://en.wikipedia.org/wiki/M%C4%81ori' }

      it { expect(subject).to be_valid }
    end

    context 'with new lines in url' do
      let(:wikipedia_url) { 'http://en.wikipedia.org/wiki/SomePage\n\nBrendaRocks' }

      it { expect(subject).not_to be_valid }
    end

    context "with script tags in url" do
      let(:wikipedia_url) { 'http://en.wikipedia.org/wiki/SomePage<script>alert(\'BrendaRocks\')</script>' }

      it { expect(subject).not_to be_valid }
    end
  end

  context 'varieties' do
    it 'has a crop hierarchy' do
      @tomato = FactoryBot.create(:tomato)
      @roma = FactoryBot.create(:roma, parent_id: @tomato.id)
      expect(@roma.parent).to eq @tomato
      expect(@tomato.varieties).to eq [@roma]
    end

    it 'toplevel scope works' do
      @tomato = FactoryBot.create(:tomato)
      @roma = FactoryBot.create(:roma, parent_id: @tomato.id)
      expect(described_class.toplevel).to eq [@tomato]
    end
  end

  context 'photos' do
    shared_examples 'has default photo' do
      it { expect(described_class.has_photos).to include(crop) }
    end

    let!(:crop) { FactoryBot.create(:tomato) }

    context 'with a planting photo' do
      let!(:photo) { FactoryBot.create(:photo, owner: planting.owner) }
      let!(:planting) { FactoryBot.create(:planting, crop:) }

      before { planting.photos << photo }

      it { expect(crop.default_photo).to eq photo }

      include_examples 'has default photo'
    end

    context 'with a harvest photo' do
      let!(:harvest) { FactoryBot.create(:harvest, crop:) }
      let!(:photo) { FactoryBot.create(:photo, owner: harvest.owner) }

      before { harvest.photos << photo }

      it { expect(crop.default_photo).to eq photo }

      include_examples 'has default photo'

      context 'and planting photo' do
        let(:planting) { FactoryBot.create(:planting, crop:) }
        let!(:planting_photo) { FactoryBot.create(:photo, owner: planting.owner) }

        before { planting.photos << planting_photo }

        it 'prefers the planting photo' do
          expect(crop.default_photo.id).to eq planting_photo.id
        end
      end
    end

    context 'with no plantings or harvests' do
      it 'has no default photo' do
        expect(crop.default_photo).to be_nil
      end

      it { expect(crop.photos.size).to eq 0 }
      it { expect(crop.photos.by_model(Planting).size).to eq 0 }
      it { expect(crop.photos.by_model(Harvest).size).to eq 0 }
      it { expect(crop.photos.by_model(Seed).size).to eq 0 }
    end

    describe 'finding all photos' do
      let(:planting) { FactoryBot.create(:planting, crop:) }
      let(:harvest) { FactoryBot.create(:harvest, crop:) }
      let(:seed)    { FactoryBot.create(:seed, crop:)    }

      before do
        # Add photos to all
        planting.photos << FactoryBot.create(:photo, owner: planting.owner)
        harvest.photos << FactoryBot.create(:photo, owner: harvest.owner)
        seed.photos << FactoryBot.create(:photo, owner: seed.owner)
      end

      it { expect(crop.photos.size).to eq 3 }
      it { expect(crop.photos.by_model(Planting).size).to eq 1 }
      it { expect(crop.photos.by_model(Harvest).size).to eq 1 }
      it { expect(crop.photos.by_model(Seed).size).to eq 1 }
    end
  end

  context 'sunniness' do
    let(:crop) { FactoryBot.create(:tomato) }

    it 'returns a hash of sunniness values' do
      FactoryBot.create(:sunny_planting, crop:)
      FactoryBot.create(:sunny_planting, crop:)
      FactoryBot.create(:semi_shady_planting, crop:)
      FactoryBot.create(:shady_planting, crop:)
      crop.sunniness.should be_an_instance_of Hash
    end

    it 'counts each sunniness value' do
      FactoryBot.create(:sunny_planting, crop:)
      FactoryBot.create(:sunny_planting, crop:)
      FactoryBot.create(:semi_shady_planting, crop:)
      FactoryBot.create(:shady_planting, crop:)
      crop.sunniness.should == { 'sun' => 2, 'shade' => 1, 'semi-shade' => 1 }
    end

    it 'ignores unused sunniness values' do
      FactoryBot.create(:sunny_planting, crop:)
      FactoryBot.create(:sunny_planting, crop:)
      FactoryBot.create(:semi_shady_planting, crop:)
      crop.sunniness.should == { 'sun' => 2, 'semi-shade' => 1 }
    end
  end

  context 'planted_from' do
    let(:crop) { FactoryBot.create(:tomato) }

    it 'returns a hash of sunniness values' do
      FactoryBot.create(:seed_planting, crop:)
      FactoryBot.create(:seed_planting, crop:)
      FactoryBot.create(:seedling_planting, crop:)
      FactoryBot.create(:cutting_planting, crop:)
      crop.planted_from.should be_an_instance_of Hash
    end

    it 'counts each planted_from value' do
      FactoryBot.create(:seed_planting, crop:)
      FactoryBot.create(:seed_planting, crop:)
      FactoryBot.create(:seedling_planting, crop:)
      FactoryBot.create(:cutting_planting, crop:)
      crop.planted_from.should == { 'seed' => 2, 'seedling' => 1, 'cutting' => 1 }
    end

    it 'ignores unused planted_from values' do
      FactoryBot.create(:seed_planting, crop:)
      FactoryBot.create(:seed_planting, crop:)
      FactoryBot.create(:seedling_planting, crop:)
      crop.planted_from.should == { 'seed' => 2, 'seedling' => 1 }
    end
  end

  context 'popular plant parts' do
    let(:crop) { FactoryBot.create(:tomato) }

    it 'returns a hash of plant_part values' do
      crop.popular_plant_parts.should be_an_instance_of Hash
    end

    it 'counts each plant_part value' do
      @fruit = FactoryBot.create(:plant_part)
      @seed = FactoryBot.create(:plant_part, name: 'seed')
      @root = FactoryBot.create(:plant_part, name: 'root')
      @bulb = FactoryBot.create(:plant_part, name: 'bulb')
      @harvest1 = FactoryBot.create(:harvest,
                                    crop:,
                                    plant_part: @fruit)
      @harvest2 = FactoryBot.create(:harvest,
                                    crop:,
                                    plant_part: @fruit)
      @harvest3 = FactoryBot.create(:harvest,
                                    crop:,
                                    plant_part: @seed)
      @harvest4 = FactoryBot.create(:harvest,
                                    crop:,
                                    plant_part: @root)
      crop.popular_plant_parts.should == { [@fruit.id, @fruit.name] => 2,
                                           [@seed.id, @seed.name]   => 1,
                                           [@root.id, @root.name]   => 1 }
    end
  end

  context 'interesting' do
    subject { described_class.interesting }

    # first, a couple of candidate crops
    let(:crop1) { FactoryBot.create(:crop) }
    let(:crop2) { FactoryBot.create(:crop) }

    let(:crop1_planting) { crop1.plantings.first }
    let(:crop2_planting) { crop2.plantings.first }

    let(:member) { FactoryBot.create(:member, login_name: 'pikachu') }

    describe 'lists interesting crops' do
      before do
        # they need 3+ plantings each to be interesting
        FactoryBot.create_list(:planting, 3, crop: crop1, owner: member)
        FactoryBot.create_list(:planting, 3, crop: crop2, owner: member)
        # crops need 3+ photos to be interesting
        crop1_planting.photos = FactoryBot.create_list :photo, 3, owner: member
        crop2_planting.photos = FactoryBot.create_list :photo, 3, owner: member
      end

      it { is_expected.to include crop1 }
      it { is_expected.to include crop2 }
      it { expect(subject.size).to eq 2 }
    end

    describe 'crops without plantings are not interesting' do
      before do
        # only crop1 has plantings
        FactoryBot.create_list(:planting, 3, crop: crop1, owner: member)
        # ... and photos
        crop1_planting.photos = FactoryBot.create_list(:photo, 3, owner: member)
      end

      it { is_expected.to include crop1 }
      it { is_expected.not_to include crop2 }
      it { expect(subject.size).to eq 1 }
    end

    describe 'crops without photos are not interesting' do
      before do
        # both crops have plantings
        FactoryBot.create_list(:planting, 3, crop: crop1, owner: member)
        FactoryBot.create_list(:planting, 3, crop: crop2, owner: member)

        # but only crop1 has photos
        crop1_planting.photos = FactoryBot.create_list(:photo, 3, owner: member)
      end

      it { is_expected.to include crop1 }
      it { is_expected.not_to include crop2 }
      it { expect(subject.size).to eq 1 }
    end
  end

  context "harvests" do
    let!(:crop)    { FactoryBot.create(:crop) }
    let!(:harvest) { FactoryBot.create(:harvest, crop:) }

    it "has harvests" do
      expect(crop.harvests).to eq [harvest]
    end
  end

  it "doesn't duplicate plant_parts" do
    @maize = FactoryBot.create(:maize)
    @pp1 = FactoryBot.create(:plant_part)
    @h1 = FactoryBot.create(:harvest, crop: @maize, plant_part: @pp1)
    @h2 = FactoryBot.create(:harvest, crop: @maize, plant_part: @pp1)
    expect(@maize.plant_parts).to eq [@pp1]
  end

  context "csv loading" do
    before do
      # don't use 'let' for this -- we need to actually create it,
      # regardless of whether it's used.
      @cropbot = FactoryBot.create(:cropbot)
    end

    context "scientific names" do
      it "adds a scientific name to a crop that has none" do
        row = ["parent", "http://en.wikipedia.org/wiki/Parent", "", "Foo bar"]
        tomato = CsvImporter.new.import_crop(row)
        expect(tomato.scientific_names.size).to eq 1
        expect(tomato.default_scientific_name.to_s).to eq "Foo bar"
      end

      it "picks up scientific name from parent crop if available" do
        parent = CsvImporter.new.import_crop(
          ["parent", "http://en.wikipedia.org/wiki/Parent", "", "Parentis cropis"]
        )

        tomato = CsvImporter.new.import_crop(
          ["Tomato", "http://en.wikipedia.org/wiki/Parent", "parent"]
        )

        expect(tomato.parent).to eq parent
        expect(tomato.parent.default_scientific_name.to_s).to eq "Parentis cropis"
        expect(tomato.default_scientific_name.to_s).to eq "Parentis cropis"
      end

      it "doesn't add a duplicate scientific name" do
        row = ["parent", "http://en.wikipedia.org/wiki/Parent", "", "Foo bar, Foo bar"]
        tomato = CsvImporter.new.import_crop(row)
        expect(tomato.scientific_names.size).to eq 1
      end

      it "doesn't add a duplicate scientific name from parent" do
        CsvImporter.new.import_crop(
          ["parent", "http://en.wikipedia.org/wiki/Parent", "", "Parentis cropis"]
        )
        tomato = CsvImporter.new.import_crop(
          ["Tomato", "http://en.wikipedia.org/wiki/Parent", "parent", "Parentis cropis"]
        )
        expect(tomato.scientific_names.size).to eq 1
      end

      it "loads a crop with multiple scientific names" do
        row = ["parent", "http://en.wikipedia.org/wiki/Parent", "", "Foo,Bar"]
        tomato = CsvImporter.new.import_crop(row)
        expect(tomato.scientific_names[0].name).to eq "Foo"
        expect(tomato.scientific_names[1].name).to eq "Bar"
      end

      it "loads multiple scientific names with variant spacing" do
        row = ["parent", "http://en.wikipedia.org/wiki/Parent", "", "Baz,   Quux"]
        tomato = CsvImporter.new.import_crop(row)
        expect(tomato.scientific_names.size).to eq 2
        expect(tomato.scientific_names[0].name).to eq "Baz"
        expect(tomato.scientific_names[1].name).to eq "Quux"
      end
    end # scientific names

    context "alternate names" do
      it "loads an alternate name" do
        row = ["tomato", "http://en.wikipedia.org/wiki/Parent", "", "", "Foo"]
        tomato = CsvImporter.new.import_crop(row)
        expect(tomato.alternate_names.size).to eq 1
        expect(tomato.alternate_names.last.name).to eq "Foo"
      end

      it "adds multiple alternate names" do
        row = ["tomato", "http://en.wikipedia.org/wiki/Parent", "", "", "Foo, Bar"]
        tomato = CsvImporter.new.import_crop(row)
        expect(tomato.alternate_names.size).to eq 2
        expect(tomato.alternate_names[0].name).to eq "Foo"
        expect(tomato.alternate_names[1].name).to eq "Bar"
      end

      it "adds multiple alt names with variant spacing" do
        row = ["tomato", "http://en.wikipedia.org/wiki/Parent", "", "", "Foo,Bar,Baz,   Quux"]
        tomato = CsvImporter.new.import_crop(row)
        expect(tomato.alternate_names.size).to eq 4
        expect(tomato.alternate_names[0].name).to eq "Foo"
        expect(tomato.alternate_names[1].name).to eq "Bar"
        expect(tomato.alternate_names[2].name).to eq "Baz"
        expect(tomato.alternate_names[3].name).to eq "Quux"
      end

      it "Adds a duplicate alternate name for second crop" do
        row = ["tomato", "http://en.wikipedia.org/wiki/tomato", "", "", "Foo"]
        tomato = CsvImporter.new.import_crop(row)
        row = ["tomoto", "http://en.wikipedia.org/wiki/tomoto", "", "", "Foo"]
        tomoto = CsvImporter.new.import_crop(row)
        expect(tomato.alternate_names.size).to eq 1
        expect(tomoto.alternate_names.size).to eq 1
      end
    end # alternate names

    it "loads the simplest possible crop" do
      tomato_row = ["tomato", "http://en.wikipedia.org/wiki/Tomato"]
      tomato = CsvImporter.new.import_crop(tomato_row)

      expect(tomato.name).to eq "tomato"
      expect(tomato.en_wikipedia_url).to eq 'http://en.wikipedia.org/wiki/Tomato'
      expect(tomato.creator).to eq @cropbot
    end

    it "loads a crop with a scientific name" do
      tomato_row = ["tomato", "http://en.wikipedia.org/wiki/Tomato", "", "Solanum lycopersicum"]
      tomato = CsvImporter.new.import_crop(tomato_row)

      expect(tomato.name).to eq "tomato"
      expect(tomato.scientific_names.size).to eq 1
      expect(tomato.scientific_names.last.name).to eq "Solanum lycopersicum"
    end

    it "loads a crop with an alternate name" do
      crop = CsvImporter.new.import_crop(
        ["tomato", "http://en.wikipedia.org/wiki/Tomato", nil, nil, "Foo"]
      )

      expect(crop.name).to eq "tomato"
      expect(crop.alternate_names.size).to eq 1
      expect(crop.alternate_names.last.name).to eq "Foo"
    end

    it "loads a crop with a parent" do
      parent = FactoryBot.create(:crop, name: 'parent')
      crop = CsvImporter.new.import_crop(
        ["tomato", "http://en.wikipedia.org/wiki/Tomato", "parent"]
      )
      expect(crop.parent).to eq parent
    end

    it "loads a crop with a missing parent" do
      tomato_row = "tomato,http://en.wikipedia.org/wiki/Tomato,parent"

      CSV.parse(tomato_row) do |row|
        CsvImporter.new.import_crop(row)
      end

      loaded = described_class.last
      expect(loaded.parent).to be_nil
    end

    it "doesn't add unnecessary duplicate crops" do
      tomato_row = "tomato,http://en.wikipedia.org/wiki/Tomato,,Solanum lycopersicum"

      CSV.parse(tomato_row) do |row|
        CsvImporter.new.import_crop(row)
      end

      loaded = described_class.last
      expect(loaded.name).to eq "tomato"
      expect(loaded.en_wikipedia_url).to eq 'http://en.wikipedia.org/wiki/Tomato'
      expect(loaded.creator).to eq @cropbot
    end
  end

  context "crop-post association" do
    let!(:tomato) { FactoryBot.create(:tomato)                                                  }
    let!(:maize)  { FactoryBot.create(:maize)                                                   }
    let!(:post)   { FactoryBot.create(:post, body: "[maize](crop)[tomato](crop)[tomato](crop)") }

    describe "destroying a crop" do
      before do
        tomato.destroy
      end

      it "deletes the association between post and the crop(tomato)" do
        expect(Post.find(post.id).crops).to eq [maize]
      end

      it "does not delete the posts" do
        expect(Post.find(post.id)).not_to be_nil
      end
    end
  end

  context "destroying a crop" do
    let!(:crop_a) { FactoryBot.create(:crop) }
    let!(:crop_b) { FactoryBot.create(:crop) }

    before do
      CropCompanion.create(crop_a: crop_a, crop_b: crop_b)
      CropCompanion.create(crop_a: crop_b, crop_b: crop_a)
    end

    it "destroys companion links" do
      expect { crop_a.destroy }.to change { CropCompanion.count }.from(2).to(0)
    end
  end

  context "crop rejections" do
    let!(:rejected_reason) do
      FactoryBot.create(:crop, name:                 'tomato',
                               approval_status:      'rejected',
                               reason_for_rejection: 'not edible')
    end
    let!(:rejected_other) do
      FactoryBot.create(:crop, name:                 'tomato',
                               approval_status:      'rejected',
                               reason_for_rejection: 'other',
                               rejection_notes:      'blah blah blah')
    end

    describe "rejecting a crop" do
      it "gives reason if a default option" do
        expect(rejected_reason.rejection_explanation).to eq "not edible"
      end

      it "shows rejection notes if reason was other" do
        expect(rejected_other.rejection_explanation).to eq "blah blah blah"
      end
    end
  end
end
