# frozen_string_literal: true

require 'spec_helper'
require 'tmpdir'

module RailsBestPractices
  describe Analyzer do
    subject { described_class.new('.') }

    describe '::new' do
      it 'expands a relative path to an absolute' do
        expect(subject.path).to eq File.expand_path('.')
      end
    end

    describe 'expand_dirs_to_files' do
      it 'expands all files in spec directory' do
        dir = File.dirname(__FILE__)
        expect(subject.expand_dirs_to_files(dir)).to be_include(dir + '/analyzer_spec.rb')
      end
    end

    describe 'file_sort' do
      it 'gets models first, mailers, helpers and then others' do
        files = [
          'app/controllers/users_controller.rb',
          'app/mailers/user_mailer.rb',
          'app/helpers/users_helper.rb',
          'app/models/user.rb',
          'app/views/users/index.html.haml',
          'app/views/users/show.html.slim',
          'lib/user.rb'
        ]
        expect(subject.file_sort(files)).to eq(
          [
            'app/models/user.rb',
            'app/mailers/user_mailer.rb',
            'app/helpers/users_helper.rb',
            'app/controllers/users_controller.rb',
            'app/views/users/index.html.haml',
            'app/views/users/show.html.slim',
            'lib/user.rb'
          ]
        )
      end
    end

    describe 'file_ignore' do
      before do
        @all = [
          'app/controllers/users_controller.rb',
          'app/mailers/user_mailer.rb',
          'app/models/user.rb',
          'app/views/users/index.html.haml',
          'app/views/users/show.html.slim',
          'lib/user.rb'
        ]
        @filtered = [
          'app/controllers/users_controller.rb',
          'app/mailers/user_mailer.rb',
          'app/models/user.rb',
          'app/views/users/index.html.haml',
          'app/views/users/show.html.slim'
        ]
      end

      it 'ignores lib' do
        expect(subject.file_ignore(@all, 'lib/')).to eq(@filtered)
      end

      it 'ignores regexp patterns' do
        expect(subject.file_ignore(@all, /lib/)).to eq(@filtered)
      end
    end

    describe 'output' do
      subject { described_class.new('.', 'format' => format) }

      before do
        allow(subject).to receive(:output_terminal_errors)
        allow(subject).to receive(:output_html_errors)
        allow(subject).to receive(:output_yaml_errors)
        allow(subject).to receive(:output_xml_errors)

        subject.output
      end

      context 'when format is not set' do
        let(:format) { nil }

        it 'runs text output' do
          expect(subject).to have_received(:output_terminal_errors)
        end
      end

      context 'when format is yaml' do
        let(:format) { 'yaml' }

        it 'runs yaml output' do
          expect(subject).to have_received(:output_yaml_errors)
        end
      end

      context 'when format is html' do
        let(:format) { 'html' }

        it 'runs html output' do
          expect(subject).to have_received(:output_html_errors)
        end
      end

      context 'when format is xml' do
        let(:format) { 'xml' }

        it 'runs xml output' do
          expect(subject).to have_received(:output_xml_errors)
        end
      end
    end

    describe 'output_terminal_errors' do
      it 'outputs errors in terminal' do
        check1 = Reviews::LawOfDemeterReview.new
        check2 = Reviews::UseQueryAttributeReview.new
        runner = Core::Runner.new(reviews: [check1, check2])
        check1.add_error 'law of demeter', 'app/models/user.rb', 10
        check2.add_error 'use query attribute', 'app/models/post.rb', 100
        subject.runner = runner
        subject.instance_variable_set('@options', 'without-color' => false)

        $origin_stdout = $stdout
        $stdout = StringIO.new
        subject.output_terminal_errors
        result = $stdout.string
        $stdout = $origin_stdout
        expect(result).to eq(
          [
            "\e[31mapp/models/user.rb:10 - law of demeter\e[0m",
            "\e[31mapp/models/post.rb:100 - use query attribute\e[0m",
            "\e[32m\nPlease go to https://rails-bestpractices.com to see more useful Rails Best Practices.\e[0m",
            "\e[31m\nFound 2 warnings.\e[0m"
          ].join("\n") + "\n"
        )
      end
    end

    describe 'output_json_errors' do
      let(:output_file) { 'rails_best_practices_output.json' }

      subject do
        described_class.new('.', 'format' => 'json', 'output-file' => output_file)
      end

      let(:check1) { Reviews::LawOfDemeterReview.new }
      let(:check2) { Reviews::UseQueryAttributeReview.new }
      let(:runner) { Core::Runner.new(reviews: [check1, check2]) }
      let(:result) { File.read(output_file) }

      before do
        check1.add_error('law of demeter', 'app/models/user.rb', 10)
        check2.add_error('use query attribute', 'app/models/post.rb', 100)
        subject.runner = runner
        subject.output
      end

      after do
        File.delete(output_file) if File.exist?(output_file)
      end

      it 'saves output as json into output file' do
        expect(result).to eq '[{"filename":"app/models/user.rb","line_number":"10","message":"law of demeter"},{"filename":"app/models/post.rb","line_number":"100","message":"use query attribute"}]'
      end
    end

    describe 'parse_files' do
      it 'does not filter out all files when the path contains "vendor"' do
        Dir.mktmpdir do |random_dir|
          Dir.mkdir(File.join(random_dir, 'vendor'))
          Dir.mkdir(File.join(random_dir, 'vendor', 'my_project'))
          File.open(File.join(random_dir, 'vendor', 'my_project', 'my_file.rb'), 'w') { |file| file << 'woot' }
          analyzer = described_class.new(File.join(random_dir, 'vendor', 'my_project'))
          expect(analyzer.parse_files).to be_include File.join(random_dir, 'vendor', 'my_project', 'my_file.rb')
        end
      end

      it 'does not filter out all files when the path contains "spec"' do
        Dir.mktmpdir do |random_dir|
          Dir.mkdir(File.join(random_dir, 'spec'))
          Dir.mkdir(File.join(random_dir, 'spec', 'my_project'))
          File.open(File.join(random_dir, 'spec', 'my_project', 'my_file.rb'), 'w') { |file| file << 'woot' }
          analyzer = described_class.new(File.join(random_dir, 'spec', 'my_project'))
          expect(analyzer.parse_files).to be_include File.join(random_dir, 'spec', 'my_project', 'my_file.rb')
        end
      end

      it 'does not filter out all files when the path contains "test"' do
        Dir.mktmpdir do |random_dir|
          Dir.mkdir(File.join(random_dir, 'test'))
          Dir.mkdir(File.join(random_dir, 'test', 'my_project'))
          File.open(File.join(random_dir, 'test', 'my_project', 'my_file.rb'), 'w') { |file| file << 'woot' }
          analyzer = described_class.new(File.join(random_dir, 'test', 'my_project'))
          expect(analyzer.parse_files).to be_include File.join(random_dir, 'test', 'my_project', 'my_file.rb')
        end
      end

      it 'does not filter out all files when the path contains "features"' do
        Dir.mktmpdir do |random_dir|
          Dir.mkdir(File.join(random_dir, 'test'))
          Dir.mkdir(File.join(random_dir, 'test', 'my_project'))
          File.open(File.join(random_dir, 'test', 'my_project', 'my_file.rb'), 'w') { |file| file << 'woot' }
          analyzer = described_class.new(File.join(random_dir, 'test', 'my_project'))
          expect(analyzer.parse_files).to be_include File.join(random_dir, 'test', 'my_project', 'my_file.rb')
        end
      end

      it 'does not filter out all files when the path contains "tmp"' do
        Dir.mktmpdir do |random_dir|
          Dir.mkdir(File.join(random_dir, 'tmp'))
          Dir.mkdir(File.join(random_dir, 'tmp', 'my_project'))
          File.open(File.join(random_dir, 'tmp', 'my_project', 'my_file.rb'), 'w') { |file| file << 'woot' }
          analyzer = described_class.new(File.join(random_dir, 'tmp', 'my_project'))
          expect(analyzer.parse_files).to be_include File.join(random_dir, 'tmp', 'my_project', 'my_file.rb')
        end
      end
    end
  end
end
