RSpec.describe Licensee::License do
  let(:license_count) { 36 }
  let(:hidden_license_count) { 24 }
  let(:featured_license_count) { 3 }
  let(:pseudo_license_count) { 2 }
  let(:non_featured_license_count) do
    license_count - featured_license_count - hidden_license_count
  end

  let(:mit) { described_class.find('mit') }
  let(:cc_by) { described_class.find('cc-by-4.0') }
  let(:unlicense) { described_class.find('unlicense') }
  let(:other) { described_class.find('other') }
  let(:no_license) { described_class.find('no-license') }
  let(:gpl) { described_class.find('gpl-3.0') }
  let(:lgpl) { described_class.find('lgpl-3.0') }
  let(:content_hash) { '46cdc03462b9af57968df67b450cc4372ac41f53' }

  let(:license_dir) do
    File.expand_path 'vendor/choosealicense.com/_licenses', project_root
  end

  context 'listing licenses' do
    let(:licenses) { described_class.all(arguments) }

    it 'returns the license keys' do
      expect(described_class.keys.count).to eql(license_count)
      expect(described_class.keys).to include(mit.key)
      expect(described_class.keys).to include('other')
    end

    context 'without any arguments' do
      let(:arguments) { {} }

      it 'returns the licenses' do
        expect(licenses).to all be_a(Licensee::License)
        expect(licenses.count).to eql(license_count - hidden_license_count)
      end

      it "doesn't include hidden licenses" do
        expect(licenses).to all(satisfy { |license| !license.hidden? })
      end

      it 'includes featured licenses' do
        expect(licenses).to include(mit)
        expect(licenses).to_not include(cc_by)
      end
    end

    context 'hidden licenses' do
      let(:arguments) { { hidden: true } }

      it 'includes hidden licenses' do
        expect(licenses).to include(cc_by)
        expect(licenses).to include(mit)
        expect(licenses.count).to eql(license_count)
      end
    end

    context 'featured licenses' do
      let(:arguments) { { featured: true } }

      it 'includes only featured licenses' do
        expect(licenses).to include(mit)
        expect(licenses).to_not include(cc_by)
        expect(licenses.count).to eql(featured_license_count)
      end
    end

    context 'non-featured licenses' do
      let(:arguments) { { featured: false } }

      it 'includes only non-featured licenses' do
        expect(licenses).to include(unlicense)
        expect(licenses).to_not include(mit)
        expect(licenses.count).to eql(non_featured_license_count)
      end

      context 'including hidden licenses' do
        let(:arguments) { { featured: false, hidden: true } }

        it 'includes only non-featured licenses' do
          expect(licenses).to include(unlicense)
          expect(licenses).to include(cc_by)
          expect(licenses).to_not include(mit)
          expect(licenses.count).to eql(license_count - featured_license_count)
        end
      end
    end

    context 'psudo licenses' do
      let(:other) { Licensee::License.find('other') }

      context 'by default' do
        let(:arguments) { {} }

        it "doesn't include psudo licenses" do
          expect(licenses).to_not include(other)
        end
      end

      context 'with hidden licenses' do
        let(:arguments) { { hidden: true } }

        it 'includes psudo licenses' do
          expect(licenses).to include(other)
        end
      end

      context 'when explicitly asked' do
        let(:arguments) { { hidden: true, psuedo: true } }

        it 'includes psudo licenses' do
          expect(licenses).to include(other)
        end
      end

      context 'when explicitly excluded' do
        let(:arguments) { { hidden: true, psuedo: false } }

        it "doesn'tincludes psudo licenses" do
          expect(licenses).to_not include(other)
        end
      end
    end
  end

  context 'finding' do
    it 'finds the MIT license' do
      expect(described_class.find('mit')).to eql(mit)
    end

    it 'finds hidden licenses' do
      expect(described_class.find('cc-by-4.0')).to eql(cc_by)
    end

    it 'is case insensitive' do
      expect(described_class.find('MIT')).to eql(mit)
    end
  end

  it 'returns the license dir' do
    expect(described_class.license_dir).to eql(license_dir)
    expect(described_class.license_dir).to be_an_existing_file
  end

  it 'returns license files' do
    expected = license_count - pseudo_license_count
    expect(described_class.license_files.count).to eql(expected)
    expect(described_class.license_files).to all be_an_existing_file
    expect(described_class.license_files).to include(mit.path)
  end

  it 'stores the key when initialized' do
    expect(described_class.new('mit')).to be == mit
    expect(described_class.new('MIT')).to be == mit
  end

  it 'exposes the path' do
    expect(mit.path).to be_an_existing_file
    expect(mit.path).to match(described_class.license_dir)
  end

  it 'exposes the key' do
    expect(mit.key).to eql('mit')
  end

  it 'exposes the SPDX ID' do
    expect(gpl.spdx_id).to eql('GPL-3.0')
  end

  it 'exposes special SPDX ID for pseudo licenses' do
    expect(other.spdx_id).to eql('NOASSERTION')
    expect(no_license.spdx_id).to eql('NONE')
  end

  context '#other?' do
    it 'knows MIT is not other' do
      expect(gpl).to_not be_other
    end

    it 'knows the other license is other?' do
      expect(other).to be_other
    end
  end

  context 'meta' do
    it 'exposes license meta' do
      expect(mit).to respond_to(:meta)
      expect(mit.meta).to respond_to(:title)
      expect(mit.meta['title']).to eql('MIT License')
      expect(mit.meta.title).to eql('MIT License')
    end

    it 'includes defaults' do
      expect(other.meta['hidden']).to eql(true)
    end

    it 'returns the name' do
      expect(mit.name).to eql('MIT License')
    end

    it 'uses the default name when none exists' do
      expect(other.name).to eql('NOASSERTION')
    end

    it 'expoeses the nickname' do
      expect(gpl.nickname).to eql('GNU GPLv3')
    end

    it 'exposes the name without version' do
      expect(mit.name_without_version).to eql('MIT License')
      expect(gpl.name_without_version).to eql('GNU General Public License')
    end

    it 'knows if a license is hidden' do
      expect(mit).to_not be_hidden
      expect(cc_by).to be_hidden
    end

    it 'knows if a license is featured' do
      expect(mit).to be_featured
      expect(unlicense).to_not be_featured
    end

    it 'knows if a license is GPL' do
      expect(mit).to_not be_gpl
      expect(gpl).to be_gpl
    end

    it 'knows a license is lgpl' do
      expect(mit).to_not be_gpl
      expect(lgpl).to be_lgpl
    end

    it 'knows if a license is CC' do
      expect(gpl).to_not be_creative_commons
      expect(cc_by).to be_creative_commons
    end
  end

  context 'content' do
    it 'returns the license content' do
      expect(mit.content).to match('Permission is hereby granted')
    end

    it 'strips leading whitespace' do
      expect(mit.content).to start_with('M')
    end

    it 'computes the hash' do
      expect(mit.content_hash).to eql(content_hash)
    end

    context 'with content stubbed' do
      let(:license) do
        license = described_class.new 'MIT'
        license.instance_variable_set(:@raw_content, content)
        license
      end

      context 'with a horizontal rule' do
        let(:content) do
          "---\nfoo: bar\n---\nSome license\n---------\nsome text\n"
        end

        it 'parses the content' do
          expect(license.content).to eql("Some license\n---------\nsome text\n")
        end
      end
    end
  end

  it 'returns the URL' do
    expect(mit.url).to eql('http://choosealicense.com/licenses/mit/')
  end

  it 'knows equality' do
    expect(mit).to eql(mit)
    expect(gpl).to_not eql(mit)
  end

  it 'knows if a license is a pseudo license' do
    expect(mit).to_not be_pseudo_license
    expect(other).to be_pseudo_license
  end

  it 'fails loudly for invalid license' do
    expect do
      described_class.new('foo').name
    end.to raise_error(Licensee::InvalidLicense)
  end

  it 'returns the rules' do
    expect(mit.rules).to be_a(Licensee::LicenseRules)
    expect(mit.rules).to have_key('permissions')
    expect(mit.rules['permissions'].first).to be_a(Licensee::Rule)
    expect(mit.rules.flatten.count).to eql(7)
  end

  it 'returns rules by tag and group' do
    expect(cc_by.rules).to have_key('limitations')
    rule = cc_by.rules['limitations'].find { |r| r.tag == 'patent-use' }
    expect(rule).to_not be_nil
    expect(rule.description).to include('does NOT grant')

    expect(gpl.rules).to have_key('permissions')
    rule = gpl.rules['permissions'].find { |r| r.tag == 'patent-use' }
    expect(rule).to_not be_nil
    expect(rule.description).to include('an express grant of patent rights')
  end

  context 'fields' do
    it 'returns the license fields' do
      expect(mit.fields.count).to eql(2)
      expect(mit.fields.first.key).to eql('year')
      expect(mit.fields.last.key).to eql('fullname')
      expect(gpl.fields).to be_empty
    end

    context 'muscache' do
      let(:license) do
        license = described_class.new 'MIT'
        content = license.content + '[foo] [bar]'
        license.instance_variable_set(:@content, content)
        license
      end

      it 'returns mustache content' do
        expect(license.content_for_mustache).to match(/{{{year}}}/)
        expect(license.content_for_mustache).to match(/{{{fullname}}}/)
        expect(license.content_for_mustache).to_not match(/\[year\]/)
        expect(license.content_for_mustache).to_not match(/\[fullname\]/)
      end

      it "doesn't mangle other fields" do
        expect(license.content_for_mustache).to match(/\[foo\]/)
        expect(license.content_for_mustache).to_not match(/{{{foo}}}/)
      end
    end
  end

  context 'License.title_regex' do
    Licensee::License.all(hidden: true, psuedo: false).each do |license|
      context "the #{license.title} license" do
        %i[title nickname key].each do |variation|
          next if license.send(variation).nil?

          context "the license #{variation}" do
            let(:license_variation) { license.send(variation).sub('*', 'u') }
            let(:text) { license_variation }

            it 'matches' do
              expect(text).to match(license.title_regex)
            end

            it 'finds by title' do
              expect(described_class.find_by_title(text)).to eql(license)
            end

            if license.title =~ /\bGNU\b/
              context "without 'GNU'" do
                let(:text) { license_variation.sub(/GNU /i, '') }

                it 'still matches' do
                  expect(text).to match(license.title_regex)
                end
              end
            end

            context "with 'the' and 'license'" do
              let(:text) { "The #{license_variation} license" }

              it 'matches' do
                expect(text).to match(license.title_regex)
              end
            end

            if variation == :title
              context 'version notation variations' do
                context "with 'version x.x'" do
                  let(:text) do
                    license_variation.sub(/v?(\d+\.\d+)/i, 'version \1')
                  end

                  it 'matches' do
                    expect(text).to match(license.title_regex)
                  end
                end

                context "with ', version x.x'" do
                  let(:text) do
                    license_variation.sub(/ v?(\d+\.\d+)/i, ', version \1')
                  end

                  it 'matches' do
                    expect(text).to match(license.title_regex)
                  end
                end

                context "with 'vx.x'" do
                  let(:text) do
                    license_variation.sub(/(?:version)? (\d+\.\d+)/i, ' v\1')
                  end

                  it 'matches' do
                    expect(text).to match(license.title_regex)
                  end
                end
              end
            end
          end
        end
      end
    end

    context 'a license with an alt title' do
      let(:text) { 'The Clear BSD license' }
      let(:license) { Licensee::License.find('bsd-3-clause-clear') }

      it 'matches' do
        expect(text).to match(license.title_regex)
      end

      it 'finds by title' do
        expect(described_class.find_by_title(text)).to eql(license)
      end
    end
  end

  context 'to_h' do
    let(:hash) { mit.to_h }
    let(:expected) do
      {
        key:     'mit',
        spdx_id: 'MIT',
        meta:    mit.meta.to_h,
        url:     'http://choosealicense.com/licenses/mit/',
        rules:   mit.rules.to_h,
        fields:  mit.fields.map(&:to_h),
        other:   false,
        gpl:     false,
        lgpl:    false,
        cc:      false
      }
    end

    it 'Converts to a hash' do
      expect(hash).to eql(expected)
    end
  end

  context 'source regex' do
    Licensee::License.all(hidden: true, psuedo: false).each do |license|
      context "the #{license.title} license" do
        let(:source) { URI.parse(license.source) }

        %w[http https].each do |scheme|
          context "with a #{scheme}:// scheme" do
            before { source.scheme = scheme }

            ['www.', ''].each do |prefix|
              context "with '#{prefix}' before the host" do
                before do
                  source.host = "#{prefix}#{source.host.sub(/\Awww\./, '')}"
                end

                ['.html', '.htm', '.txt', ''].each do |suffix|
                  context "with '#{suffix}' after the path" do
                    before do
                      next if license.key == 'wtfpl'
                      regex = /#{Licensee::License::SOURCE_SUFFIX}\z/
                      source.path = source.path.sub(regex, '')
                      source.path = "#{source.path}#{suffix}"
                    end

                    it 'matches' do
                      expect(source.to_s).to match(license.source_regex)
                    end
                  end
                end
              end
            end
          end
        end
      end
    end
  end
end