# frozen_string_literal: true

require 'spec_helper'

describe 'optional_default' do
  let(:msg) { 'Optional parameter defaults to something other than undef' }

  %w[define class].each do |type|
    context "#{type} with Optional parameter defaulting to string on single line" do
      let(:code) { "#{type} test(Optional $foo = 'test') { }" }

      it 'detects a single problem' do
        expect(problems).to have(1).problem
      end

      col = (type == 'class' ? 21 : 22)
      it 'creates a warning' do
        expect(problems).to contain_warning(msg).on_line(1).in_column(col)
      end

      context 'with trailing comma' do
        let(:code) { "#{type} test(Optional $foo = 'test',) { }" }

        it 'detects a single problem' do
          expect(problems).to have(1).problem
        end

        col = (type == 'class' ? 21 : 22)
        it 'creates a warning' do
          expect(problems).to contain_warning(msg).on_line(1).in_column(col)
        end
      end
    end

    context "#{type} with Optional parameter defaulting to string on multiple lines" do
      let(:code) do
        <<~CODE
          #{type} test(
            Optional $foo = 'test'
          ){
          }
        CODE
      end

      it 'detects a single problem' do
        expect(problems).to have(1).problem
      end

      it 'creates a warning' do
        expect(problems).to contain_warning(msg).on_line(2).in_column(12)
      end
    end

    context "#{type} with Optional parameter defaulting to undef on single line" do
      let(:code) { "#{type} test(Optional $foo = undef) { }" }

      it 'detects no problems' do
        expect(problems).to have(0).problem
      end

      context 'with trailing comma' do
        let(:code) { "#{type} test(Optional $foo = undef) { }" }

        it 'detects no problems' do
          expect(problems).to have(0).problem
        end
      end
    end

    context "#{type} with Optional[String[1]] parameter defaulting to string on single line" do
      let(:code) { "#{type} test(Optional[String[1]] $foo = 'test') { }" }

      it 'detects a single problem' do
        expect(problems).to have(1).problem
      end

      col = (type == 'class' ? 32 : 33)
      it 'creates a warning' do
        expect(problems).to contain_warning(msg).on_line(1).in_column(col)
      end
    end

    context "#{type} with a mandatory parameter followed by an Optional[Hash] parameter with a non undef default" do
      let(:code) do
        <<~CODE
          #{type} test(
            String         $foo,
            Optional[Hash] $bar = {
              'a' => 'b',
              'c' => 'd'
            },
          ){
          }
        CODE
      end

      it 'detects a single problem' do
        expect(problems).to have(1).problem
      end

      it 'creates a warning' do
        expect(problems).to contain_warning(msg).on_line(3).in_column(18)
      end
    end

    context "#{type} with a mandatory parameter followed by an Optional[Hash] parameter that default to the result of a function" do
      let(:code) do
        <<~CODE
          #{type} test(
            String         $foo,
            Optional[Hash] $bar = some::func('foo'),
          ){
          }
        CODE
      end

      it 'detects no problems' do
        expect(problems).to have(0).problem
      end
    end

    context "#{type} with a mandatory parameter followed by an Optional[Hash] parameter that default to the result of a function called with dot notation" do
      let(:code) do
        <<~CODE
          #{type} test(
            String         $foo,
            Optional[Hash] $bar = 'foo'.func,
          ){
          }
        CODE
      end

      it 'detects no problems' do
        expect(problems).to have(0).problem
      end
    end

    context "Complex #{type} with multiple issues and comments" do
      let(:code) do
        <<~CODE
          #{type} test (
            $a, # No type or default
            String                  $b   = 'somestring',
            Optional[String[1]]     $c   = 'foobar', # This should generate a warning
            Optional[Enum[
              'a',
              'b',
              ]
            ]                       $fuz = 'a', # As should this
            Optional                $d   = $foo::param::c,
            Optional[Array[String]] $e   = [] # Another warning here (also note no trailing comma!)
          ){
            notice('test')
          }
        CODE
      end

      it 'detects 3 problems' do
        expect(problems).to have(3).problem
      end

      it { expect(problems).to contain_warning(msg).on_line(4).in_column(27) }
      it { expect(problems).to contain_warning(msg).on_line(9).in_column(27) }
      it { expect(problems).to contain_warning(msg).on_line(11).in_column(27) }
    end

    context "#{type} with Optional parameter with array operation" do
      let(:code) do
        <<~CODE
          #{type} test(
            Optional[Array] $hostnames = ['foo.example.com', 'bar.example.com'] - $facts['fqdn'],
          ){
          }
        CODE
      end

      it 'detects a single problem' do
        expect(problems).to have(1).problem
      end

      it { expect(problems).to contain_warning(msg).on_line(2).in_column(19) }
    end
  end
  context 'with class with Optional parameter with no apparent default' do
    let(:code) { 'class test(Optional $foo) { }' }

    # There's a good chance the parameter default is actually in hiera as `~`.
    it 'detects no problems' do
      expect(problems).to have(0).problem
    end
  end

  context 'with defined type with Optional parameter with no default' do
    let(:code) { 'define test(Optional $foo) { }' }

    it 'detects a single problem' do
      expect(problems).to have(1).problem
    end
  end
end