# frozen_string_literal: true

RSpec.describe Licensee::License do
  let(:license_count) { 47 }
  let(:hidden_license_count) { 34 }
  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) { license_hashes['mit'] }

  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(described_class)
        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).not_to include(cc_by)
        expect(licenses).not_to include(other)
      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).not_to include(cc_by)
        expect(licenses).not_to include(other)
        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).not_to include(mit)
        expect(licenses).not_to include(other)
        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).not_to include(mit)
          expect(licenses.count).to eql(license_count - featured_license_count)
        end
      end
    end

    context 'pseudo licenses' do
      let(:other) { described_class.find('other') }

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

        it "doesn't include pseudo licenses" do
          expect(licenses).not_to include(other)
        end
      end

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

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

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

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

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

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

      context 'mispelled' do
        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).not_to include(other)
          end
        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

  describe '#other?' do
    it 'knows MIT is not other' do
      expect(gpl).not_to 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 be(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('Other')
      expect(no_license.name).to eql('No license')
    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).not_to be_hidden
      expect(cc_by).to be_hidden
    end

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

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

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

    it 'knows if a license is CC' do
      expect(gpl).not_to 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(described_class.find('mit')).to eql(mit)
    expect(described_class.find('mit')).to eq(mit)
    expect(gpl).not_to eql(mit)
  end

  it 'returns false when compared to a boolean' do
    expect(described_class.find('mit')).not_to be(true)
  end

  it 'knows if a license is a pseudo license' do
    expect(mit).not_to 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 be(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).not_to 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).not_to 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 be(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).not_to match(/\[year\]/)
        expect(license.content_for_mustache).not_to match(/\[fullname\]/)
      end

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

  context 'License.title_regex' do
    namey = %i[title nickname key]
    described_class.all(hidden: true, pseudo: false).each do |license|
      context "the #{license.title} license" do
        namey.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 /\bGNU\b/.match?(license.title)
              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) { described_class.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
    schemes = %w[http https]
    prefixes = ['www.', '']
    suffixes = ['.html', '.htm', '.txt', '']
    described_class.all(hidden: true, pseudo: false).each do |license|
      context "the #{license.title} license" do
        let(:source) { URI.parse(license.source) }

        schemes.each do |scheme|
          context "with a #{scheme}:// scheme" do
            before { source.scheme = scheme }

            prefixes.each do |prefix|
              context "with '#{prefix}' before the host" do
                before do
                  source.host = "#{prefix}#{source.host.delete_prefix('www.')}"
                end

                suffixes.each do |suffix|
                  context "with '#{suffix}' after the path" do
                    before do
                      next if license.key == 'wtfpl'

                      regex = /#{Licensee::License::SOURCE_SUFFIX}\z/o
                      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
