use strict;
use warnings;
use Test::More;
use Test::Requires::Git;
use Test::Git;
use File::Temp qw( tempdir );
use File::Spec;
use File::Path;
use Cwd qw( cwd realpath );
use Git::Repository;

test_requires_git '1.6.0';

my $version = Git::Repository->version;

plan tests => my $tests + my $between + my $extra;

# clean up the environment
delete @ENV{qw( GIT_DIR GIT_WORK_TREE )};
$ENV{LC_ALL} = 'C';
$ENV{GIT_CONFIG_NOSYSTEM} = 1;
delete $ENV{XDG_CONFIG_HOME};
delete $ENV{HOME};
my $home = cwd();

# a place to put a git repository
my $tmp = realpath( tempdir( CLEANUP => 1 ) );

# some dirname generating routine
my $i;

sub next_dir { return File::Spec->catdir( $tmp, ++$i ); }

sub test_repo {
    my ( $r, $gitdir, $dir, $options ) = @_;

    # normalize actual paths, but do not die under Win32
    eval { $gitdir = realpath($gitdir) } if defined $gitdir;
    eval { $dir    = realpath($dir) }    if defined $dir;

    local $Test::Builder::Level = $Test::Builder::Level + 1;
    isa_ok( $r, 'Git::Repository' );
    is( $r->git_dir,   $gitdir, '... correct git_dir' );
    is( $r->work_tree, $dir,    '... correct work_tree' );
    is_deeply( $r->options, $options, "... correct options" );
}

my ( $dir, $r );
$dir = next_dir;

# PASS - non-existent directory
BEGIN { $tests += 5 }
my $gitdir = File::Spec->catdir( $dir, '.git' );
mkpath $dir;
chdir $dir;
ok( $r = eval {
        $r = Git::Repository->run( 'init', { cwd => $dir } );
        Git::Repository->new( { cwd => $dir } );
    },
    "init => $i"
);
diag $@ if $@;
test_repo( $r, $gitdir, $dir, { cwd => $dir } );
chdir $home;

# PASS - new() on a normal repository
BEGIN { $tests += 5 }
ok( $r = eval { Git::Repository->new( git_dir => $gitdir ); },
    "new( git_dir => $i )" );
diag $@ if $@;
test_repo( $r, $gitdir, $dir, {} );

# PASS - new() on a normal repository
BEGIN { $tests += 5 }
ok( $r = eval { Git::Repository->new( work_tree => $dir ); },
    "new( work_tree => $i )" );
diag $@ if $@;
test_repo( $r, $gitdir, $dir, {} );

# PASS - new() on a subdir of the working copy
BEGIN { $tests += 5 }
my $subdir = File::Spec->catdir( $dir, 'sub' );
mkpath $subdir;
ok( $r = eval { Git::Repository->new( work_tree => $subdir ); },
    "new( work_tree => $i/sub )" );
diag $@ if $@;
test_repo( $r, $gitdir, $dir, {} );

# PASS - new() without arguments
BEGIN { $tests += 5 }
chdir $dir;
ok( $r = eval { Git::Repository->new(); }, "new() => $i" );
diag $@ if $@;
chdir $home;
test_repo( $r, $gitdir, $dir, {} );

# PASS - new() without arguments from subdir
BEGIN { $tests += 5 }
chdir $subdir;
ok( $r = eval { Git::Repository->new(); }, "new() => $i/sub" );
diag $@ if $@;
test_repo( $r, $gitdir, $dir, {} );
chdir $home;

# PASS - new() with both arguments from subdir
BEGIN { $tests += 5 }
chdir $subdir;
ok( $r = eval {
        Git::Repository->new( work_tree => $dir, git_dir => $gitdir );
    },
    "new( work_tree => $i, git_dir => $i/.git ) => $i/sub"
);
diag $@ if $@;
test_repo( $r, $gitdir, $dir, {} );
chdir $home;

my $old;
SKIP: {
    skip "cloning an empty repo dies for 1.7.0.rc1 <= git <= 1.7.0.2, we have $version",
        $between
        if Git::Repository->version_le('1.7.0.2')
            && Git::Repository->version_ge('1.7.0.rc1');

    # PASS - clone an existing repo and warns
    BEGIN { $between += 5 }
    $old = $dir;
    $dir = next_dir;
    ok( $r = eval {
            Git::Repository->run( clone => $old => $dir, { quiet => 1 } );
            Git::Repository->new( work_tree => $dir );
        },
        "clone => @{[ $i - 1 ]} => $i"
    );
    diag $@ if $@;
    test_repo( $r, File::Spec->catdir( $dir, '.git' ), $dir, {} );

    # PASS - clone an existing repo as bare and warns
    # relative target path
    BEGIN { $between += 5 }
    $old = $dir;
    $dir = next_dir;
    chdir $tmp;
    ok( $r = eval {
            Git::Repository->run( clone => '--bare', $old => $i, { quiet => 1} );
            Git::Repository->new( git_dir => $i );
        },
        "clone => --bare, @{[ $i - 1 ]} => $i"
    );
    diag $@ if $@;
    chdir $home;
    test_repo( $r, $dir, undef, {} );

    # PASS - clone an existing repo as bare and warns
    # absolute target path
    BEGIN { $between += 5 }
    SKIP: {
        $old = $dir;
        $dir = next_dir;
        skip 'git clone --bare fails with absolute target path', 5
            if $^O eq 'MSWin32';
        ok( $r = eval {
                Git::Repository->run( clone => '--bare', $old => $dir, { quiet => 1 } );
                Git::Repository->new( git_dir => $dir );
            },
            "clone => --bare, @{[ $i - 1 ]} => $i"
        );
        diag $@ if $@;
        test_repo( $r, $dir, undef, {} );
    }
}

# FAIL - clone a non-existing repo
BEGIN { $tests += 3 }
$old = next_dir;
$dir = next_dir;
ok( !(  $r = eval {
            Git::Repository->run( clone => $old => $dir );
            Git::Repository->new( work_tree => $dir );
        }
    ),
    "clone => @{[ $i - 1 ]} => $i - FAILED"
);
is( $r, undef,
    "clone => @{[ $i - 1 ]} => $i - did not create a repository" );
like( $@, qr/^fatal: /m, 'fatal error from git' );

# PASS - init a bare repository
BEGIN { $tests += 5 }
$dir = next_dir;
mkpath $dir;
chdir $dir;
ok( $r = eval {
        Git::Repository->run(qw( init --bare ));
        Git::Repository->new();
    },
    "clone => @{[ $i - 1 ]} - $i"
);
diag $@ if $@;
test_repo( $r, $dir, undef, {} );
chdir $home;

# PASS - new() on a bare repository
BEGIN { $tests += 5 }
ok( $r = eval { Git::Repository->new( git_dir => $dir ); },
    "new( git_dir => $i )" );
diag $@ if $@;
test_repo( $r, $dir, undef, {} );

# PASS - non-existent directory, not a .git GIT_DIR
# no --work-tree mean it's bare
BEGIN { $tests += 5 }
$dir = next_dir;
mkpath $dir;
chdir $dir;
$gitdir = File::Spec->catdir( $dir, '.notgit' );
my $options
    = { cwd => $dir, env => { GIT_DIR => File::Spec->abs2rel($gitdir) } };
ok( $r = eval {
        Git::Repository->run( 'init', $options );
        Git::Repository->new($options);
    },
    "init - cwd => $i, GIT_DIR => '.notgit'"
);
diag $@ if $@;
chdir $home;
test_repo( $r, $gitdir, undef, $options );

BEGIN { $tests += 5 }
ok( $r = eval { Git::Repository->new( git_dir => $gitdir ); },
    "new( git_dir => $i )" );
diag $@ if $@;
test_repo( $r, $gitdir, undef, {} );

# PASS - non-existent directory, not a .git GIT_DIR
# now provide a --work-tree
BEGIN { $tests += 5 }
$dir = next_dir;
mkpath $dir;
chdir $dir;
$gitdir = File::Spec->catdir( $dir, '.notgit' );
$options = { cwd => $dir, env => { GIT_DIR => File::Spec->abs2rel($gitdir) } };
ok( $r = eval {
        Git::Repository->run( "--work-tree=$dir", 'init', $options );
        Git::Repository->new( work_tree => $dir, $options );
    },
    "init - cwd => $i, GIT_DIR => '.notgit'"
);
diag $@ if $@;
test_repo( $r, $gitdir, $dir, $options );
chdir $home;

# PASS - non-existent directory, not a .git GIT_DIR
# provide a --work-tree, and start in a subdir
BEGIN { $tests += 5 }
$dir = next_dir;
mkpath $dir;
$gitdir = File::Spec->catdir( $dir, '.notgit' );
$subdir = File::Spec->catdir( $dir, 'sub' );
mkpath $subdir;
chdir $subdir;
$options = {
    cwd => $subdir,
    env => {
        GIT_DIR       => File::Spec->abs2rel( $gitdir, $subdir ),
        GIT_WORK_TREE => File::Spec->abs2rel( $dir,    $subdir )
    }
};
ok( $r = eval {
        Git::Repository->run( 'init', $options );
        Git::Repository->new($options);
    },
    "init - cwd => $i, GIT_DIR => '.notgit'"
);
diag $@ if $@;
chdir $home;
test_repo( $r, $gitdir, $dir, $options );

# these tests requires git version >= 1.6.5
SKIP: {
    skip "these tests require git >= 1.6.5, but we only have $version", $extra
        if Git::Repository->version_lt('1.6.5');

    # FAIL - init a dir that is a file
    BEGIN { $extra += 3 }
    $dir = next_dir;
    { open my $fh, '>', $dir; }    # creates an empty file
    ok( !(  $r = eval {
                Git::Repository->run( init => $dir );
                Git::Repository->new( work_tree => $dir );
            }
        ),
        "init => $i - FAILED"
    );
    is( $r, undef, "init => $i - did not create a repository" );
    like( $@, qr/^fatal: /m, 'fatal error from git' );

    # PASS - init on an existing repository
    BEGIN { $extra += 10 }
    $dir = next_dir;
    $gitdir = File::Spec->catdir( $dir, '.git' );
    ok( $r = eval {
            Git::Repository->run( init => $dir );
            Git::Repository->new( work_tree => $dir );
        },
        "init => $i"
    );
    diag $@ if $@;
    test_repo( $r, $gitdir, $dir, {} );

    ok( $r = eval {
            Git::Repository->run( init => $dir );
            Git::Repository->new( work_tree => $dir );
        },
        "init => $i - again"
    );
    diag $@ if $@;
    test_repo( $r, $gitdir, $dir, {} );
}

