use strict;
use warnings;
use Test::More;
use Path::Tiny;
use FindBin;
use lib "$FindBin::Bin/lib";
use TestRepo;
use Git::Native;
use Git::Native::Credential;

# Two repos linked by a file:// remote — push and pull a refs/karr/* ref
# end-to-end through Git::Native::Remote. No fork/exec.

my $tmp_bare = Path::Tiny->tempdir;
my $tmp_a    = Path::Tiny->tempdir;
my $tmp_b    = Path::Tiny->tempdir;

# Set up a bare upstream.
my $bare = Git::Native->init( "$tmp_bare", bare => 1 );
ok $bare, 'bare repo initialised';
ok $bare->is_bare, 'is_bare';

# Repo A: write a blob → tree → commit, point a ref at it.
my $a = Git::Native->init("$tmp_a");
my $blob_oid = $a->blob_create_frombuffer("hello from karr\n");
my $tb = $a->tree_builder;
$tb->insert( name => 'data', oid => $blob_oid, mode => 0100644 );
my $tree_oid = $tb->write;

my $commit_oid = $a->commit_create(
  tree    => $tree_oid,
  parents => [],
  message => 'native commit',
);
ok $commit_oid, "commit oid: $commit_oid";

my $ref = $a->reference_create( 'refs/karr/test/data', $commit_oid, force => 1 );
ok $ref, 'ref created in repo A';

# Wire A → bare via file:// remote, push.
my $url = 'file://' . $tmp_bare;
my $remote_a = $a->remote_create( 'origin', $url );
is $remote_a->url, $url, 'remote A url roundtrip';

# Wildcard push — Remote auto-expands.
$remote_a->push( refspecs => ['+refs/karr/*:refs/karr/*'] );
ok 1, 'push completed without die (wildcard auto-expanded)';

# Verify on bare side directly.
my $bare_ref = $bare->reference('refs/karr/test/data');
is $bare_ref->target->hex, $commit_oid->hex, 'bare has the pushed ref';

# Repo B: fetch from bare.
my $b = Git::Native->init("$tmp_b");
my $remote_b = $b->remote_create( 'origin', $url );
$remote_b->fetch( refspecs => ['+refs/karr/*:refs/karr/*'] );
ok 1, 'fetch completed without die';

my $b_ref = $b->reference('refs/karr/test/data');
is $b_ref->target->hex, $commit_oid->hex, 'repo B has the fetched ref';

# Registering a credentials callback must not disturb a transport that needs
# no auth. libgit2 asks for credentials only when the transport reports an
# auth challenge, so over file:// the callback is invoked ZERO times — the
# count below is the assertion, not a formality. (An earlier version of this
# test claimed libgit2 "still asks if registered" and asserted nothing; it
# does not, and the PASSTHROUGH mapping itself is unit-tested without a
# transport in t/52-credential-callback.t.)
my $cred_calls = 0;
$b->reference_delete('refs/karr/test/data');
ok !$b->reference_exists('refs/karr/test/data'), 'ref removed from B before the re-fetch';

my $remote_b2 = $b->remote_anonymous($url);
$remote_b2->fetch(
  refspecs    => ['+refs/karr/*:refs/karr/*'],
  credentials => sub { $cred_calls++; return undef },
);
is $cred_calls, 0, 'file:// fetch never invokes the credentials callback';
is $b->reference('refs/karr/test/data')->target->hex, $commit_oid->hex,
  'the fetch still transferred the ref with a callback registered';

# --- prune semantics ---
# Add a second ref to A, push it; remote now has both.
my $extra_blob = $a->blob_create_frombuffer("extra\n");
my $tb2 = $a->tree_builder;
$tb2->insert( name => 'data', oid => $extra_blob, mode => 0100644 );
my $tree2 = $tb2->write;
my $commit2 = $a->commit_create( tree => $tree2, parents => [], message => 'extra' );
$a->reference_create( 'refs/karr/extra/data', $commit2, force => 1 );
$remote_a->push( refspecs => ['+refs/karr/*:refs/karr/*'] );
my $names_before = $bare->reference_names( glob => 'refs/karr/*' );
is scalar(@$names_before), 2, 'bare has two refs after second push';

# Delete the extra ref locally and push with prune — remote loses it too.
$a->reference_delete('refs/karr/extra/data');
$remote_a->push(
  refspecs => ['+refs/karr/*:refs/karr/*'],
  prune    => 1,
);
my $names_after = $bare->reference_names( glob => 'refs/karr/*' );
is scalar(@$names_after), 1, 'bare has one ref after prune push';
is $names_after->[0], 'refs/karr/test/data', 'pruned ref remained';

# --- fetch --prune: a mirror ref that vanished upstream is dropped locally.
# Re-add a second ref upstream, mirror it into B, then delete it upstream and
# fetch with prune — B must lose its stale copy but keep the live one.
$a->reference_create( 'refs/karr/extra/data', $commit2, force => 1 );
$remote_a->push( refspecs => ['+refs/karr/*:refs/karr/*'] );
$remote_b->fetch( refspecs => ['+refs/karr/*:refs/karr/*'] );
ok $b->reference_exists('refs/karr/extra/data'), 'B mirrored the re-added ref';

$a->reference_delete('refs/karr/extra/data');
$remote_a->push( refspecs => ['+refs/karr/*:refs/karr/*'], prune => 1 );
$remote_b->fetch( refspecs => ['+refs/karr/*:refs/karr/*'], prune => 1 );
ok !$b->reference_exists('refs/karr/extra/data'),
  'fetch --prune dropped the stale mirror ref';
ok $b->reference_exists('refs/karr/test/data'),
  'fetch --prune kept the still-present ref';

done_testing;
