# frozen_string_literal: true

describe Nanoc::Deploying::Deployers::Git, :stdio do
  subject { deployer.run }

  let(:deployer) { described_class.new(output_dir, options, dry_run:) }

  let(:output_dir) { 'output/' }
  let(:options) { remote_options.merge(branch_options).merge(forced_options) }
  let(:dry_run) { false }

  let(:remote_options) { {} }
  let(:branch_options) { {} }
  let(:forced_options) { {} }

  def run_and_get_stdout(*args)
    TTY::Command.new.run(*args).out
  end

  def add_changes_to_remote
    system('git', 'init', '--quiet', '--initial-branch=master', 'rere_tmp')

    Dir.chdir('rere_tmp') do
      system('git', 'config', 'user.name', 'Zebra Platypus')
      system('git', 'config', 'user.email', 'zebra@platypus.example.com')
      system('git', 'remote', 'add', 'origin', '../rere')

      File.write('evil.txt', 'muaha')
      system('git', 'add', 'evil.txt')
      system('git', 'commit', '--quiet', '-m', 'muaha')

      system('git', 'checkout', '--quiet', '-b', 'giraffe')
      system('git', 'push', '--quiet', 'origin', 'master')
      system('git', 'push', '--quiet', 'origin', 'giraffe')
    end
  end

  def rev_list
    run_and_get_stdout('git', 'rev-list', '--objects', '--all')
  end

  shared_examples 'branch configured properly' do
    context 'clean working copy' do
      it 'does not commit, but still pushes' do
        expect { subject }
          .to output(/Deploying via Git to branch “#{branch}” on remote “#{remote}”…/)
          .to_stdout
      end
    end

    context 'non-clean working copy' do
      before do
        Dir.chdir(output_dir) { File.write('hello.txt', 'Hi there') }
      end

      shared_examples 'successful push' do
        context 'no dry run' do
          it 'outputs status' do
            expect { subject }
              .to output(/Deploying via Git to branch “#{branch}” on remote “#{remote}”…/)
              .to_stdout
          end

          it 'makes a change in the local repo' do
            expect { subject }
              .to change { Dir.chdir(output_dir) { rev_list } }
              .from(not_match(/^[a-f0-9]{40} hello\.txt$/))
              .to(match(/^[a-f0-9]{40} hello\.txt$/))

            expect(Dir.chdir(output_dir) { run_and_get_stdout('git', 'show', branch) })
              .to match(/^Author: Nanoc <>$/)
          end

          it 'makes a change in the remote repo' do
            expect { subject }
              .to change { Dir.chdir('rere') { rev_list } }
              .from(not_match(/^[a-f0-9]{40} hello\.txt$/))
              .to(match(/^[a-f0-9]{40} hello\.txt$/))
          end
        end

        context 'dry run' do
          let(:dry_run) { true }

          it 'makes a change in the local repo' do
            expect { subject }
              .not_to change { Dir.chdir(output_dir) { rev_list } }
          end

          it 'makes a change in the remote repo' do
            expect { subject }
              .not_to change { Dir.chdir('rere') { rev_list } }
          end
        end
      end

      context 'forced' do
        let(:forced_options) { { forced: true } }

        context 'remote has no other changes' do
          include_examples 'successful push'
        end

        context 'remote has other changes' do
          before { add_changes_to_remote }

          include_examples 'successful push'
        end
      end

      context 'not forced (implicit)' do
        let(:forced_options) { {} }

        context 'remote has no other changes' do
          include_examples 'successful push'
        end

        context 'remote has other changes' do
          before { add_changes_to_remote }

          it 'raises' do
            expect { subject }.to raise_error(TTY::Command::ExitError)
          end
        end
      end

      context 'not forced (explicit)' do
        let(:forced_options) { { forced: false } }

        context 'remote has no other changes' do
          include_examples 'successful push'
        end

        context 'remote has other changes' do
          before { add_changes_to_remote }

          it 'raises' do
            expect { subject }.to raise_error(TTY::Command::ExitError)
          end
        end
      end
    end
  end

  shared_examples 'remote configured properly' do
    before do
      system('git', 'init', '--bare', '--quiet', '--initial-branch=master', 'rere')
    end

    context 'default branch' do
      context 'branch does not exist' do
        it 'raises' do
          expect { subject }.to raise_error(
            Nanoc::Deploying::Deployers::Git::Errors::BranchDoesNotExist,
            'The branch to deploy, master, does not exist.',
          )
        end
      end

      context 'branch exists' do
        before do
          Dir.chdir(output_dir) do
            system('git', 'commit', '--quiet', '-m', 'init', '--allow-empty')
          end
        end

        let(:branch) { 'master' }

        include_examples 'branch configured properly'
      end
    end

    context 'custom branch' do
      let(:branch) { 'giraffe' }
      let(:branch_options) { { branch: } }

      context 'branch does not exist' do
        it 'raises' do
          expect { subject }.to raise_error(
            Nanoc::Deploying::Deployers::Git::Errors::BranchDoesNotExist,
            'The branch to deploy, giraffe, does not exist.',
          )
        end
      end

      context 'branch exists' do
        before do
          Dir.chdir(output_dir) do
            system('git', 'commit', '--quiet', '-m', 'init', '--allow-empty')
            system('git', 'branch', 'giraffe')
          end
        end

        include_examples 'branch configured properly'
      end
    end
  end

  context 'output dir does not exist' do
    it 'raises' do
      expect { subject }.to raise_error(
        Nanoc::Deploying::Deployers::Git::Errors::OutputDirDoesNotExist,
        'The directory to deploy, output/, does not exist.',
      )
    end
  end

  context 'output dir exists' do
    before do
      FileUtils.mkdir_p(output_dir)
    end

    context 'output dir is not a Git repo' do
      it 'raises' do
        expect { subject }.to raise_error(
          Nanoc::Deploying::Deployers::Git::Errors::OutputDirIsNotAGitRepo,
          'The directory to deploy, output/, is not a Git repository.',
        )
      end
    end

    context 'output dir is a Git repo' do
      before do
        Dir.chdir(output_dir) do
          system('git', 'init', '--quiet', '--initial-branch=master')

          system('git', 'config', 'user.name', 'Donkey Giraffe')
          system('git', 'config', 'user.email', 'donkey@giraffe.example.com')
        end
      end

      context 'default remote' do
        context 'remote does not exist' do
          it 'raises' do
            expect { subject }.to raise_error(
              Nanoc::Deploying::Deployers::Git::Errors::RemoteDoesNotExist,
              'The remote to deploy to, origin, does not exist.',
            )
          end
        end

        context 'remote exists' do
          before do
            Dir.chdir(output_dir) do
              system('git', 'remote', 'add', 'origin', '../rere')
            end
          end

          let(:remote) { 'origin' }

          include_examples 'remote configured properly'
        end
      end

      context 'custom remote (name)' do
        let(:remote_options) { { remote: 'donkey' } }

        context 'remote does not exist' do
          it 'raises' do
            expect { subject }.to raise_error(
              Nanoc::Deploying::Deployers::Git::Errors::RemoteDoesNotExist,
              'The remote to deploy to, donkey, does not exist.',
            )
          end
        end

        context 'remote exists' do
          before do
            Dir.chdir(output_dir) do
              system('git', 'remote', 'add', 'donkey', '../rere')
            end
          end

          let(:remote) { 'donkey' }

          include_examples 'remote configured properly'
        end
      end

      context 'custom remote (file:// URL)' do
        let(:remote_options) { { remote: } }

        let(:remote) { "file://#{Dir.getwd}/rere" }

        include_examples 'remote configured properly'
      end
    end
  end

  describe '#remote_is_name?' do
    def val(remote)
      deployer.send(:remote_is_name?, remote)
    end

    it 'recognises names' do
      expect(val('denis')).to be(true)
    end

    it 'recognises URLs' do
      expect(val('git@github.com:/foo')).to be(false)
      expect(val('http://example.com/donkey.git')).to be(false)
      expect(val('https://example.com/donkey.git')).to be(false)
      expect(val('ssh://example.com/donkey.git')).to be(false)
      expect(val('file:///example.com/donkey.git')).to be(false)
    end
  end
end
