# frozen_string_literal: true

require 'pathname'
require_relative '../../spec_helper'
require_lib 'reek/configuration/app_configuration'
require_lib 'reek/configuration/directory_directives'
require_lib 'reek/configuration/default_directive'
require_lib 'reek/configuration/excluded_paths'

RSpec.describe Reek::Configuration::AppConfiguration do
  describe 'factory methods' do
    around do |example|
      Dir.mktmpdir do |tmp|
        Dir.chdir(tmp) do
          example.run
        end
      end
    end

    let(:expected_exclude_file_names) do
      %w(exclude_me.rb exclude_me_too.rb)
    end

    let(:expected_exclude_directories) do
      %w(directory_with_trailing_slash/ directory_without_trailing_slash)
    end

    let(:expected_excluded_paths) do
      (expected_exclude_file_names + expected_exclude_directories).map { |path| Pathname(path) }
    end

    let(:expected_default_directive) do
      { Reek::SmellDetectors::IrresponsibleModule => { 'enabled' => false } }
    end

    let(:expected_directory_directives) do
      { Pathname('directory_with_some_ruby_files') => {
        Reek::SmellDetectors::UtilityFunction => { 'enabled' => false }
      } }
    end

    describe '#from_path' do
      let(:configuration_path) { 'config.reek' }
      let(:configuration) do
        <<~YAML
          ---
          detectors:
            IrresponsibleModule:
              enabled: false
          directories:
            "directory_with_some_ruby_files":
              UtilityFunction:
                enabled: false
          exclude_paths:
            - "exclude_me.rb"
            - "exclude_me_too.rb"
            - "directory_with_trailing_slash/"
            - "directory_without_trailing_slash"
        YAML
      end

      before do
        File.write configuration_path, configuration
        FileUtils.touch expected_exclude_file_names
        FileUtils.mkdir expected_exclude_directories
      end

      it 'properly loads configuration and processes it' do
        config = described_class.from_path configuration_path

        expect(config.send(:excluded_paths)).to eq(expected_excluded_paths)
        expect(config.send(:default_directive)).to eq(expected_default_directive)
        expect(config.send(:directory_directives)).to eq(expected_directory_directives)
      end
    end

    describe '#from_hash' do
      before do
        FileUtils.touch expected_exclude_file_names
        FileUtils.mkdir expected_exclude_directories
      end

      let(:default_directive_value) do
        { Reek::DETECTORS_KEY => {
          'IrresponsibleModule' => { 'enabled' => false }
        } }
      end

      let(:directory_directives_value) do
        { Reek::DIRECTORIES_KEY => {
          'directory_with_some_ruby_files' => {
            'UtilityFunction' => { 'enabled' => false }
          }
        } }
      end

      let(:exclude_paths_value) do
        { Reek::EXCLUDE_PATHS_KEY => (expected_exclude_file_names + expected_exclude_directories) }
      end

      let(:combined_value) do
        directory_directives_value.
          merge(default_directive_value).
          merge(exclude_paths_value)
      end

      it 'sets the configuration a unified simple data structure' do
        config = described_class.from_hash(combined_value)

        expect(config.send(:excluded_paths)).to eq(expected_excluded_paths)
        expect(config.send(:default_directive)).to eq(expected_default_directive)
        expect(config.send(:directory_directives)).to eq(expected_directory_directives)
      end
    end
  end

  describe '#default' do
    it 'returns a blank AppConfiguration' do
      config = described_class.default
      expect(config).to be_instance_of described_class
      expect(config.send(:excluded_paths)).to eq([])
      expect(config.send(:default_directive)).to eq({})
      expect(config.send(:directory_directives)).to eq({})
    end
  end

  describe '#directive_for' do
    context 'with multiple directory directives and no default directive present' do
      let(:source_via) { 'samples/some_files/dummy1.rb' }
      let(:baz_config)  { { IrresponsibleModule: { enabled: false } } }
      let(:bang_config) { { Attribute: { enabled: true } } }
      let(:expected_result) { { Reek::SmellDetectors::Attribute => { enabled: true } } }

      let(:directory_directives) do
        {
          Reek::DIRECTORIES_KEY => {
            'samples/some_files'  => bang_config,
            'samples/other_files' => baz_config
          }
        }
      end

      it 'returns the corresponding directive' do
        configuration = described_class.from_hash directory_directives
        expect(configuration.directive_for(source_via)).to eq expected_result
      end
    end

    context 'with directory directive and default directive present' do
      let(:directory) { 'spec/samples/two_smelly_files/' }
      let(:source_via) { "#{directory}/dummy.rb" }

      let(:configuration_as_hash) do
        {
          Reek::DIRECTORIES_KEY => {
            directory => { TooManyStatements: { max_statements: 8 } }
          },
          Reek::DETECTORS_KEY   => {
            IrresponsibleModule: { enabled: false },
            TooManyStatements:   { max_statements: 15 }
          }
        }
      end

      it 'returns the directory directive with the default directive reverse-merged' do
        configuration = described_class.from_hash configuration_as_hash
        actual = configuration.directive_for(source_via)

        expect(actual[Reek::SmellDetectors::IrresponsibleModule]).to be_truthy
        expect(actual[Reek::SmellDetectors::TooManyStatements]).to be_truthy
        expect(actual[Reek::SmellDetectors::TooManyStatements][:max_statements]).to eq(8)
      end
    end

    context 'with a path not covered by a directory directive but a default directive present' do
      let(:source_via) { 'samples/some_files/dummy.rb' }

      let(:configuration_as_hash) do
        {
          Reek::DETECTORS_KEY   => {
            IrresponsibleModule: { enabled: false }
          },
          Reek::DIRECTORIES_KEY => {
            'samples/other_files' => { Attribute: { enabled: false } }
          }
        }
      end

      let(:expected_result) { { Reek::SmellDetectors::IrresponsibleModule => { enabled: false } } }

      it 'returns the default directive' do
        configuration = described_class.from_hash configuration_as_hash
        expect(configuration.directive_for(source_via)).to eq expected_result
      end
    end
  end
end
