<?php

declare(strict_types=1);
namespace Tests\git2;
use git2\git;
use git2\git_config;
use git2\git_error_code;
use git2\git_index;
use git2\git_object_t;
use git2\git_odb;
use git2\git_refdb;
use git2\git_reference;
use git2\git_repository;
use git2\git_repository_init_flag_t;
use git2\git_repository_init_options;
use git2\git_repository_item_t;
use git2\git_repository_state_t;

final class RepositoryTest extends GitTestCase
{

	public function testInit(): void
	{
		$dir = $this->mkdir('init');
		$this->assertOK(git::repository_init($repo, $dir, false));
		$this->assertSame(realpath($dir . '/.git'), realpath(git::repository_path($repo)));
		$this->assertSame(git_repository_state_t::NONE, git::repository_state($repo));
		$this->assertTrue(git::repository_is_empty($repo));
		$this->assertInstanceOf(git_repository::class, $repo);
		$this->assertTrue(is_dir($dir . '/.git'));
		$this->assertFalse(git::repository_is_bare($repo));
		$this->assertFalse(git::repository_is_shallow($repo));
		$this->assertFalse(git::repository_is_worktree($repo));

		$this->assertTrue(git::repository_head_unborn($repo));

		git::repository_index($index, $repo);
		git::index_write_tree($treeId, $index);
		git::tree_lookup($tree, $repo, $treeId);
		git::signature_new($sig, 'John Doe', 'john.doe@example.com', 1640263000, 60);
		git::commit_create_v($id, $repo, 'HEAD', $sig, $sig, null, 'Hello world!', $tree);
		git::commit_lookup($initial, $repo, $id);
		git::commit_create_v($id2, $repo, 'HEAD', $sig, $sig, null, 'Hello world!', $tree, $initial);

		$this->assertFalse(git::repository_head_unborn($repo));

		$this->assertOK(git::worktree_add($wt, $repo, 'foo', $this->mkdir('init_wt') . '/wt', null));
		$this->assertOK(git::repository_head_for_worktree($wth, $repo, 'foo'));
		$this->assertInstanceOf(git_reference::class, $wth);

		$this->assertOK(git::repository_open_from_worktree($repoWt, $wt));
		$this->assertInstanceOf(git_repository::class, $repoWt);

		file_put_contents($dir . '/.git/MERGE_MSG', 'foo');

		$this->assertOK(git::repository_message($message, $repo));
		$this->assertSame('foo', (string) $message);
		$this->assertOK(git::repository_message_remove($repo));
		$this->assertFileDoesNotExist($dir . '/.git/MERGE_MSG');

		$this->assertOK(git::repository_odb($odb, $repo));
		$this->assertInstanceOf(git_odb::class, $odb);

		$this->assertOK(git::repository_refdb($refdb, $repo));
		$this->assertInstanceOf(git_refdb::class, $refdb);
	}

	public function testInitExt(): void
	{
		git::repository_init_options_init($options, git_repository_init_options::VERSION);
		$options->description = 'hello world';
		$options->flags = git_repository_init_flag_t::MKDIR | git_repository_init_flag_t::BARE;

		$dir = $this->mkdir('init_ext');
		$this->assertOK(git::repository_init_ext($repo, $dir . '/foo', $options));
		$this->assertTrue(is_dir($dir . '/foo'));
		$this->assertSame('hello world', file_get_contents($dir . '/foo/description'));
	}

	public function testInitBare(): void
	{
		$dir = $this->mkdir('init_bare');
		$this->assertOK(git::repository_init($repo, $dir, true));
		$this->assertInstanceOf(git_repository::class, $repo);
		$this->assertTrue(is_file($dir . '/description'));
		$this->assertTrue(git::repository_is_bare($repo));
	}

	public function testInitError(): void
	{
		$this->assertSame(git_error_code::ERROR, git::repository_init($repo, '', false));

		$dir = $this->mkdir('init_err');
		touch($dir . '/file');
		$this->assertSame(git_error_code::EEXISTS, git::repository_init($repo, $dir . '/file', false));
	}

	public function testOpen(): void
	{
		$dir = $this->mkdir('open');
		git::repository_init($repo, $dir, false);
		$this->assertOK(git::repository_open($open, $dir));
		$this->assertInstanceOf(git_repository::class, $open);
	}

	public function testOpenBare(): void
	{
		$dir = $this->mkdir('open_bare');
		git::repository_init($repo, $dir, true);
		$this->assertOK(git::repository_open_bare($open, $dir));
		$this->assertInstanceOf(git_repository::class, $open);
	}

	public function testOpenError(): void
	{
		$dir = $this->mkdir('open_err');
		$this->assertSame(git_error_code::ENOTFOUND, git::repository_open($open, $dir));
	}

	public function testWorkDir(): void
	{
		$dir = $this->mkdir('workdir');
		$this->assertOK(git::repository_init($repo, $dir, false));
		$this->assertSame(realpath($dir), realpath(git::repository_workdir($repo)));
	}

	public function testWorkDirBare(): void
	{
		$dir = $this->mkdir('workdir_bare');
		$this->assertOK(git::repository_init($repo, $dir, true));
		$this->assertNull(git::repository_workdir($repo));
	}

	public function testHead(): void
	{
		$dir = $this->mkdir('head');
		$this->assertOK(git::repository_init($repo, $dir, false));
		$this->assertSame(git_error_code::EUNBORNBRANCH, git::repository_head($head, $repo));
		$this->assertOK(git::repository_index($index, $repo));
		$this->assertOK(git::index_write_tree($treeId, $index));
		$this->assertOK(git::tree_lookup($tree, $repo, $treeId));
		$this->assertOK(git::signature_now($sig, 'John Doe', 'john.doe@example.com'));

		$result = git::commit_create_v(
			$id,
			$repo,
			'HEAD',
			$sig, $sig,
			null,
			'Hello world!',
			$tree,
		);

		$this->assertOK($result);
		$this->assertOK(git::repository_head($head, $repo));
		$this->assertInstanceOf(git_reference::class, $head);
		$this->assertFalse(git::repository_is_empty($repo));
	}

	public function testIndex(): void
	{
		$dir = $this->mkdir('index');
		$this->assertOK(git::repository_init($repo, $dir, false));
		$this->assertOK(git::repository_index($index, $repo));
		$this->assertInstanceOf(git_index::class, $index);
	}

	public function testCommondir(): void
	{
		$dir = $this->mkdir('commondir');
		$this->assertOK(git::repository_init($repo, $dir, false));
		$this->assertSame(realpath($dir . '/.git'), realpath(git::repository_commondir($repo)));
	}

	public function testConfig(): void
	{
		$dir = $this->mkdir('config');
		$this->assertOK(git::repository_init($repo, $dir, false));
		$this->assertOK(git::repository_config($config, $repo));
		$this->assertInstanceOf(git_config::class, $config);
	}

	public function testConfigSnapshot(): void
	{
		$dir = $this->mkdir('config_snapshot');
		$this->assertOK(git::repository_init($repo, $dir, false));
		$this->assertOK(git::repository_config_snapshot($config, $repo));
		$this->assertInstanceOf(git_config::class, $config);
	}

	public function testDetachHead(): void
	{
		$dir = $this->mkdir('detach_head');
		$this->assertOK(git::repository_init($repo, $dir, false));
		$this->assertSame(git_error_code::EUNBORNBRANCH, git::repository_detach_head($repo));
		$this->assertOK(git::repository_index($index, $repo));
		$this->assertOK(git::index_write_tree($treeId, $index));
		$this->assertOK(git::tree_lookup($tree, $repo, $treeId));
		$this->assertOK(git::signature_now($sig, 'John Doe', 'john.doe@example.com'));

		$result = git::commit_create_v(
			$id,
			$repo,
			'HEAD',
			$sig, $sig,
			null,
			'Hello world!',
			$tree,
		);

		$this->assertOK($result);
		$this->assertSame(false, git::repository_head_detached($repo));
		$this->assertOK(git::repository_detach_head($repo));
		$this->assertSame(true, git::repository_head_detached($repo));
	}

	public function testDiscover(): void
	{
		$dir = $this->mkdir('discover');
		$this->assertOK(git::repository_init($repo, $dir, false));
		mkdir($dir . '/test');
		$this->assertOK(git::repository_discover($buf, $dir . '/test', false));
		$this->assertSame(realpath($dir . '/.git'), realpath((string) $buf));
	}

	public function testNamespace(): void
	{
		$dir = $this->mkdir('namespace');
		$this->assertOK(git::repository_init($repo, $dir, false));
		$this->assertNull(git::repository_get_namespace($repo));
		$this->assertOK(git::repository_set_namespace($repo, 'TEST'));
		$this->assertSame('TEST', git::repository_get_namespace($repo));
		$this->assertOK(git::repository_set_namespace($repo, null));
		$this->assertNull(git::repository_get_namespace($repo));
	}

	public function testHashfile(): void
	{
		$dir = $this->mkdir('hashfile');
		git::repository_init($repo, $dir, false);
		file_put_contents($dir . '/foo', 'foo');
		$this->assertOK(git::repository_hashfile($hash, $repo, $dir . '/foo', git_object_t::BLOB, null));
		$this->assertSame('19102815663d23f8b75a47e7a01965dcdc96468c', git::oid_tostr_s($hash));
	}

	public function testIdent(): void
	{
		$dir = $this->mkdir('ident');
		git::repository_init($repo, $dir, false);
		$this->assertOK(git::repository_ident($name, $email, $repo));
		$this->assertNull($name);
		$this->assertNull($email);
		$this->assertOK(git::repository_set_ident($repo, 'John Doe', 'john.doe@example.com'));
		$this->assertOK(git::repository_ident($name, $email, $repo));
		$this->assertSame('John Doe', $name);
		$this->assertSame('john.doe@example.com', $email);
	}

	public function testItemPath(): void
	{
		$dir = $this->mkdir('item_path');
		git::repository_init($repo, $dir, false);
		$this->assertOK(git::repository_item_path($out, $repo, git_repository_item_t::INDEX));
		$this->assertSame(realpath($dir . '/.git/index'), realpath((string) $out));
	}

	public function testOpenExt(): void
	{
		$dir = $this->mkdir('open_ext');
		git::repository_init($_, $dir, false);
		$this->assertOK(git::repository_open_ext($repo, $dir, 0, null));
		$this->assertInstanceOf(git_repository::class, $repo);
	}

}