<?php

declare(strict_types=1);
namespace Tests\git2;
use git2\git;
use git2\git_direction;
use git2\git_error_code;
use git2\git_remote;
use git2\git_remote_autotag_option_t;
use git2\git_remote_callbacks;
use git2\git_remote_create_options;
use git2\git_remote_head;
use git2\git_strarray;

final class RemoteTest extends GitTestCase
{

	public function testCreateAndConnect(): void
	{
		$rDir = $this->mkdir('remote');
		$this->assertOK(git::repository_init($repo, $rDir, true));
		$lDir = $this->mkdir('local');
		$this->assertOK(git::repository_init($repo, $lDir, false));
		$this->assertOK(git::remote_create($remote, $repo, 'origin', $rDir));
		$this->assertFalse(git::remote_connected($remote));
		$this->assertOK(git::remote_connect($remote, git_direction::FETCH));
		$this->assertTrue(git::remote_connected($remote));
		$this->assertOK(git::remote_disconnect($remote));
		$this->assertSame($repo, git::remote_owner($remote));
	}

	public function testAdd(): void
	{
		git::repository_init($repo, $dir = $this->mkdir('add'), false);
		git::remote_create($remote, $repo, 'origin', $dir . '/.git');

		$this->assertOK(git::remote_add_fetch($repo, 'origin', '+refs/notes/*:refs/origin/notes/*'));
		git::remote_lookup($remote, $repo, 'origin');
		$this->assertOK(git::remote_get_fetch_refspecs($refspecs, $remote));
		$this->assertInstanceOf(git_strarray::class, $refspecs);
		$this->assertCount(2, $refspecs);
		$this->assertSame('+refs/notes/*:refs/origin/notes/*', $refspecs[1]);

		$this->assertOK(git::remote_add_push($repo, 'origin', '+refs/notes/*:refs/origin/notes/*'));
		git::remote_lookup($remote, $repo, 'origin');
		$this->assertOK(git::remote_get_push_refspecs($refspecs2, $remote));
		$this->assertInstanceOf(git_strarray::class, $refspecs2);
		$this->assertCount(1, $refspecs2);
		$this->assertSame('+refs/notes/*:refs/origin/notes/*', $refspecs2[0]);

		$refspec = git::remote_get_refspec($remote, 1);
		$this->assertNotNull($refspecs);
		$this->assertSame('+refs/notes/*:refs/origin/notes/*', git::refspec_string($refspec));

		$this->assertSame(3, git::remote_refspec_count($remote));
	}

	public function testAutotag(): void
	{
		git::repository_init($repo, $dir = $this->mkdir('autotag'), false);
		git::remote_create($remote, $repo, 'origin', $dir . '/.git');
		$this->assertSame(git_remote_autotag_option_t::AUTO, git::remote_autotag($remote));
	}

	public function testCreateAnnonymous(): void
	{
		git::repository_init($repo, $dir = $this->mkdir('create_annonymous'), false);
		$this->assertOK(git::remote_create_anonymous($remote, $repo, $dir . '/.git'));
		$this->assertNull(git::remote_name($remote));
	}

	public function testCreateDetached(): void
	{
		$this->assertOK(git::remote_create_detached($remote, $this->mkdir('create_detached')));
		$this->assertNull(git::remote_owner($remote));
	}

	public function testCreateWithFetchspec(): void
	{
		git::repository_init($repo, $dir = $this->mkdir('create_with_fethspec'), false);
		$this->assertOK(git::remote_create_with_fetchspec($remote, $repo, 'origin', $dir . '/.git', '+refs/heads/*:refs/heads/*'));
	}

	public function testCreateWithOpts(): void
	{
		git::repository_init($repo, $dir = $this->mkdir('create_with_opts'), false);
		$this->assertOK(git::remote_create_options_init($opts, git_remote_create_options::VERSION));
		$this->assertOK(git::remote_create_with_opts($remote, $dir . '/.git', $opts));
	}

	public function testDefaultBranch(): void
	{
		git::repository_init($repo, $dir = $this->mkdir('default_branch'), false);
		git::repository_index($index, $repo);
		git::index_write_tree($treeId, $index);
		git::tree_lookup($tree, $repo, $treeId);
		git::signature_now($aut, 'John Doe', 'john.doe@example.com');
		git::signature_now($com, 'Jane Doe', 'jane.doe@example.com');
		git::commit_create_v($id, $repo, 'HEAD', $aut, $com, null, 'Hello world!', $tree);
		git::remote_create($remote, $repo, 'origin', $dir . '/.git');
		git::remote_connect($remote, git_direction::FETCH);
		$this->assertOK(git::remote_default_branch($buf, $remote));
		$this->assertSame('refs/heads/master', (string) $buf);
	}

	public function testDelete(): void
	{
		git::repository_init($repo, $dir = $this->mkdir('add'), false);
		git::remote_create($remote, $repo, 'origin', $dir . '/.git');
		$this->assertOK(git::remote_lookup($_, $repo, 'origin'));
		$this->assertOK(git::remote_delete($repo, 'origin'));
		$this->assertSame(git_error_code::ENOTFOUND, git::remote_lookup($_, $repo, 'origin'));
	}

	public function testDownload(): void
	{
		git::repository_init($repo, $dir = $this->mkdir('download'), false);
		git::repository_index($index, $repo);
		git::index_write_tree($treeId, $index);
		git::tree_lookup($tree, $repo, $treeId);
		git::signature_now($aut, 'John Doe', 'john.doe@example.com');
		git::signature_now($com, 'Jane Doe', 'jane.doe@example.com');
		git::commit_create_v($id, $repo, 'HEAD', $aut, $com, null, 'Hello world!', $tree);
		git::remote_create($remote, $repo, 'origin', $dir . '/.git');
		$this->assertOK(git::remote_download($remote, null, null));
		$this->assertOK(git::remote_fetch($remote, null, null, null));
		$this->assertOK(git::remote_prune_refs($remote));
		$this->assertOK(git::remote_prune($remote, null));
	}

	public function testDup(): void
	{
		git::remote_create_detached($remote, $this->mkdir('dup'));
		$this->assertOK(git::remote_dup($dup, $remote));
		$this->assertInstanceOf(git_remote::class, $dup);
	}

	public function testList(): void
	{
		git::repository_init($repo, $dir = $this->mkdir('list'), false);
		git::remote_create($remote, $repo, 'origin', $dir . '/.git');
		$this->assertOK(git::remote_list($list, $repo));
		$this->assertInstanceOf(git_strarray::class, $list);
		$this->assertCount(1, $list);
		$this->assertSame('origin', $list[0]);
	}

	public function testLs(): void
	{
		git::repository_init($repo, $dir = $this->mkdir('download'), false);
		git::repository_index($index, $repo);
		git::index_write_tree($treeId, $index);
		git::tree_lookup($tree, $repo, $treeId);
		git::signature_now($aut, 'John Doe', 'john.doe@example.com');
		git::signature_now($com, 'Jane Doe', 'jane.doe@example.com');
		git::commit_create_v($id, $repo, 'HEAD', $aut, $com, null, 'Hello world!', $tree);
		git::remote_create($remote, $repo, 'origin', $dir . '/.git');
		git::remote_connect($remote, git_direction::FETCH);
		$this->assertOK(git::remote_ls($heads, $remote)); /** @var $heads git_remote_head[] */
		$this->assertCount(2, $heads);
		$this->assertSame('HEAD', $heads[0]->name);
		$this->assertSame('refs/heads/master', $heads[1]->name);
	}

	public function testNameIsValid(): void
	{
		$this->assertOK(git::remote_name_is_valid($valid, 'origin'));
		$this->assertTrue($valid);
		$this->assertOK(git::remote_name_is_valid($valid, '(_*_)'));
		$this->assertFalse($valid);
	}

	public function testSet(): void
	{
		git::repository_init($repo, $dir = $this->mkdir('add'), false);
		git::remote_create($remote, $repo, 'origin', $dir . '/.git');

		$this->assertNull(git::remote_pushurl($remote));
		$this->assertNotNull(git::remote_url($remote));

		$this->assertOK(git::remote_set_pushurl($repo, 'origin', $dir . '/.git'));
		$this->assertOK(git::remote_set_autotag($repo, 'origin', git_remote_autotag_option_t::NONE));
		$this->assertOK(git::remote_set_url($repo, 'origin', $dir . '/.git'));

		git::remote_lookup($remote, $repo, 'origin');
		$this->assertNotNull(git::remote_pushurl($remote));
		$this->assertSame(git_remote_autotag_option_t::NONE, git::remote_autotag($remote));
		$this->assertNotNull(git::remote_url($remote));

		$this->assertOK(git::remote_set_instance_pushurl($remote, $dir . '/foo'));
		$this->assertOK(git::remote_set_instance_url($remote, $dir . '/bar'));

		$this->assertStringEndsWith('/foo', git::remote_pushurl($remote));
		$this->assertStringEndsWith('/bar', git::remote_url($remote));
	}

	public function testCallbacks(): void
	{
		$this->assertOK(git::remote_init_callbacks($callbacks, git_remote_callbacks::VERSION));
		$this->assertInstanceOf(git_remote_callbacks::class, $callbacks);

		$factory = function(string $name): callable {
			return function() use($name): int {
				return 0;
			};
		};

		$callbacks->completion        = $factory('completion');
		$callbacks->certificate_check = $factory('certificate_check');
		$callbacks->credentials       = $factory('credentials');
		$callbacks->sideband_progress = $factory('sideband_progress');
		$callbacks->transfer_progress = $factory('transfer_progress');
		$callbacks->update_tips       = $factory('update_tips');

		// TODO.
	}

	public function testPush(): void
	{
		$rDir = $this->mkdir('push_remote');
		$this->assertOK(git::repository_init($rRepo, $rDir, true));
		$lDir = $this->mkdir('push_local');
		$this->assertOK(git::repository_init($lRepo, $lDir, false));
		$this->assertOK(git::remote_create($remote, $lRepo, 'origin', $rDir));

		git::repository_index($index, $lRepo);
		git::index_write_tree($treeId, $index);
		git::tree_lookup($tree, $lRepo, $treeId);
		git::signature_now($aut, 'John Doe', 'john.doe@example.com');
		git::signature_now($com, 'Jane Doe', 'jane.doe@example.com');
		git::commit_create_v($id, $lRepo, 'HEAD', $aut, $com, null, 'Hello world!', $tree);

		$this->assertOK(git::remote_push($remote, git_strarray::from(['refs/heads/master']), null));

		$this->assertOK(git::repository_head($ref, $rRepo));
		$this->assertSame(git::oid_tostr_s($id), git::oid_tostr_s(git::reference_target($ref)));
	}

	public function testRename(): void
	{
		git::repository_init($repo, $dir = $this->mkdir('rename'), false);
		git::remote_create($remote, $repo, 'origin', $dir . '/.git');
		$this->assertOK(git::remote_rename($problems, $repo, 'origin', 'foo'));
		$this->assertCount(0, $problems);
		$this->assertOK(git::remote_lookup($foo, $repo, 'foo'));
		$this->assertSame(git_error_code::ENOTFOUND, git::remote_lookup($foo, $repo, 'origin'));
	}

}