require 'fwtoolkit/git_client'
require 'tmpdir'

describe GitClient do
  before do
    @project_folder = Dir.mktmpdir
    @repo = GitClient::Repository.new @project_folder
    raise "Can't create temp folder" unless @project_folder
  end

  after do
    FileUtils.rm_rf(@project_folder)
  end

  it "initialize and empty repository on an new folder" do
    @repo.init
    File.exists?(File.join(@project_folder, '.git')).should be_true
  end

  it "fails to initialise a repository on a folder containing a git repo" do
    Dir.mkdir File.join(@project_folder, '.git')
    expect { @repo.init }.to raise_error(GitClient::GitError)
  end

  it "correctly identify if a folder contains a git repository" do
    @repo.initialized?.should be_false
    Dir.mkdir File.join(@project_folder, '.git')
    @repo.initialized?.should be_true
  end

  it "identifies the current branch" do
    create_sample_repo
    @repo.current_branch.should == 'master'
  end

  it "identifies existing and non existing branches" do
    create_sample_repo
    @repo.branch_exist?('master').should be_true
    @repo.branch_exist?('deleted-branch').should be_false
  end

  it "switches to an existing different branch" do
    create_sample_repo
    @repo.switch_branch 'test1'
    @repo.current_branch.should == 'test1'
  end

  it "switches to a new branch creating it" do
    create_sample_repo
    @repo.switch_branch 'test-new'
    @repo.current_branch.should == 'test-new'
  end

  it "complains when switching to a non existent branch" do
    create_empty_repo
    expect { @repo.switch_branch 'fake_branch', false }.to raise_error GitClient::GitError
  end

  it "identifies a clean working directory" do
    create_empty_repo
    @repo.workdir_clean?.should == true
    FileUtils.cd(@project_folder) { FileUtils.touch 'new_file.c' }
    @repo.workdir_clean?.should == false
  end

  it "identifies untracked files" do
    create_sample_repo
    @repo.status[:untracked].include?('untracked.c').should be_true
  end

  it "identifies not up to date files on the index" do
    create_sample_repo
    change_the_file 'added.c'
    @repo.status[:idx_not_up_to_date].include?('added.c').should be_true
  end

  it "identifies files added to the index" do
    create_sample_repo
    @repo.status[:idx_added].include?('added.c').should be_true
  end

  it "identifies files modified on the index" do
    create_sample_repo
    change_the_file 'added_and_committed.c'
    add_the_file 'added_and_committed.c'
    @repo.status[:idx_modified].include?('added_and_committed.c').should be_true
  end

  it "identifies files deleted on the index" do
    create_sample_repo
    remove_committed_file_from_git
    @repo.status[:idx_deleted].include?('added_and_committed.c').should be_true
  end

  it "identifies files renamed on the index" do
    create_sample_repo
    move_committed_file
    @repo.status[:idx_renamed].include?('added_and_committed.c').should be_true
  end

  it "adds files to the index" do
    create_sample_repo
    FileUtils.cd(@project_folder) { FileUtils.touch 'new_file.c' }
    @repo.add_files_to_index 'new_file.c'
    tracked_file = false
    FileUtils.cd(@project_folder) { `git ls-files new_file.c --error-unmatch`; tracked_file = $?.success? }
    tracked_file.should be_true
  end

  it "list all remotes when they exist" do
    create_sample_repo
    remotes = @repo.remotes
    remotes[:origin].should eq 'git@github.com:/Company/repo.git'
    remotes[:upstream].should eq 'git@github.com:/Company/cool-repo.git'
  end

  it "returns an empty remote list when none exists" do
    create_empty_repo
    @repo.remotes.should be_empty
  end

  it "list all submodules when they exists" do
    create_repo_with_submodules
    submodules = @repo.submodules
    submodules['my_module'].should eq 'git://github.com/dummy_user/my_module.git'
    submodules['sub/my_module2'].should eq 'git://github.com/dummy_user/my_module2.git'
  end

  it "returns an empty submodules list when none exists" do
    create_empty_repo
    @repo.submodules.should be_empty
  end

  it "commits to the repository" do
    create_sample_repo
    commit_hash = @repo.commit "My commit message"
    valid_commit?(commit_hash).should be_true
  end

  it "merges into the current branch" do
    create_mergable_repo
    file_exists_in_repo?('test1_file.c').should be_false
    @repo.merge('test1')
    file_exists_in_repo?('test1_file.c').should be_true
    last_commit_merge?.should be_true

  end

  it "merges into a specific branch" do
    create_mergable_repo
    file_exists_in_repo?('test1_file.c').should be_false
    @repo.merge('test1', 'test2')
    last_commit_merge?.should be_true

    @repo.current_branch.should eq "test2"
    file_exists_in_repo?('test1_file.c').should be_true
  end

  it "complains when trying to merge from a non existing branch" do
    create_mergable_repo
    expect { @repo.merge 'fake_branch' }.to raise_error GitClient::GitError
  end

  it "complains when trying to merge into a non existing branch" do
    create_mergable_repo
    expect { @repo.merge 'test1', 'fake_branch' }.to raise_error GitClient::GitError
  end

  it "rebases into the current branch" do
    create_mergable_repo
    file_exists_in_repo?('test1_file.c').should be_false
    @repo.rebase('test1')
    file_exists_in_repo?('test1_file.c').should be_true
  end

  it "rebases into a specific branch" do
    create_mergable_repo
    file_exists_in_repo?('test1_file.c').should be_false
    @repo.rebase('test1', 'test2')
    @repo.current_branch.should eq "test2"

    file_exists_in_repo?('test1_file.c').should be_true
  end

  it "complains when trying to rebase from a non existing branch" do
    create_mergable_repo
    expect { @repo.rebase 'fake_branch' }.to raise_error GitClient::GitError
  end

   it "complains when trying to rebase into a non existing branch" do
    create_mergable_repo
    expect { @repo.rebase 'test1', 'fake_branch' }.to raise_error GitClient::GitError
  end

  it "saves the stash" do
    create_sample_repo
    add_the_file 'untracked.c'
    @repo.save_stash
    @repo.status.should be_empty
  end

  it "pops a stash" do
    create_repo_with_stashed_data
    @repo.workdir_clean?.should be_true
    @repo.pop_stash
    @repo.workdir_clean?.should be_false
  end

  private

  def create_empty_repo
    FileUtils.cd(@project_folder) do
      `git init`
    end
  end

  def create_sample_repo
    create_empty_repo
    FileUtils.cd(@project_folder) do
      write_content_to_file 'added_and_committed.c', '//my c line'
      write_content_to_file 'added.c', '//my c line new'
      write_content_to_file 'untracked.c', '//my c line untracked'
      `git add added_and_committed.c`
      `git commit -m"My first commit"`
      `git add added.c`
      `git branch test1`
      `git branch test2`
      `git remote add origin git@github.com:/Company/repo.git`
      `git remote add upstream git@github.com:/Company/cool-repo.git`
    end
  end

  def create_repo_with_submodules
    create_sample_repo
    write_content_to_file '.gitmodules', <<-GITMODULES
      [submodule "my_module"]
          path = my_module
          url = "git://github.com/dummy_user/my_module.git"
      [submodule "my_module2"]
          path = sub/my_module2
          url = "git://github.com/dummy_user/my_module2.git"
      GITMODULES
  end

  def create_mergable_repo
    create_empty_repo
    FileUtils.cd(@project_folder) do
      FileUtils.touch('master_file.c')
      `git add .`
      `git commit -m"First Commit"`
      `git checkout -b test1 2>&1`
      `git branch test2`
      write_content_to_file 'test1_file.c', '//my c line on test1'
      `git add test1_file.c`
      `git commit -m "Test1 branchs commit"`
      `git checkout master 2>&1`
    end
  end

  def create_repo_with_stashed_data
    create_sample_repo
    add_the_file 'untracked.c'
    FileUtils.cd(@project_folder) do
      `git stash save`
    end
  end

  def write_content_to_file(filename, content)
    FileUtils.cd(@project_folder) do
       File.open(filename, 'w') {|f| f.write(content) }
    end
  end

  def change_the_file(filename)
    write_content_to_file(filename,'//another c line')
  end

  def add_the_file(filename)
    FileUtils.cd(@project_folder) do
      `git add #{filename}`
    end
  end

  def remove_committed_file_from_git
    FileUtils.cd(@project_folder) do
      `git rm added_and_committed.c`
    end
  end

  def move_committed_file
    FileUtils.cd(@project_folder) do
      FileUtils.mv('added_and_committed.c', 'moved.c')
      `git rm added_and_committed.c`
      `git add moved.c`
    end
  end

  def valid_commit?(sha1)
     FileUtils.cd(@project_folder) do
       `git merge-base #{sha1} master`
     end
     $?.success?
  end

  def last_commit_merge?
    last_commit_sha = nil
    FileUtils.cd(@project_folder) do
      last_commit_sha = `git rev-list --merges HEAD~1..HEAD`
    end
    $?.success? &&  last_commit_sha && last_commit_sha.length > 0
  end

  def file_exists_in_repo?(filename)
    File.exists? File.join(@project_folder, filename)
  end
end