#include "clar_libgit2.h"
#include "path.h"
#include "refs.h"
#include "worktree.h"
#include "worktree_helpers.h"

#define COMMON_REPO "testrepo"
#define WORKTREE_REPO "testrepo-worktree"

static worktree_fixture fixture =
	WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO);

void test_worktree_refs__initialize(void)
{
	setup_fixture_worktree(&fixture);
}

void test_worktree_refs__cleanup(void)
{
	cleanup_fixture_worktree(&fixture);
}

void test_worktree_refs__list_no_difference_in_worktree(void)
{
	git_strarray refs, wtrefs;
	unsigned i, j;
	int error = 0;

	cl_git_pass(git_reference_list(&refs, fixture.repo));
	cl_git_pass(git_reference_list(&wtrefs, fixture.worktree));

	if (refs.count != wtrefs.count)
	{
		error = GIT_ERROR;
		goto exit;
	}

	for (i = 0; i < refs.count; i++)
	{
		int found = 0;

		for (j = 0; j < wtrefs.count; j++)
		{
			if (!strcmp(refs.strings[i], wtrefs.strings[j]))
			{
				found = 1;
				break;
			}
		}

		if (!found)
		{
			error = GIT_ERROR;
			goto exit;
		}
	}

exit:
	git_strarray_dispose(&refs);
	git_strarray_dispose(&wtrefs);
	cl_git_pass(error);
}

void test_worktree_refs__list_worktree_specific(void)
{
	git_strarray refs, wtrefs;
	git_reference *ref, *new_branch;
	int error = 0;
	git_oid oid;

	cl_git_pass(git_reference_name_to_id(&oid, fixture.repo, "refs/heads/dir"));
	cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, fixture.repo, "refs/bisect/a-bisect-ref"));
	cl_git_pass(git_reference_create(
	        &new_branch, fixture.worktree, "refs/bisect/a-bisect-ref", &oid,
	        0, "test"));

	cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, fixture.repo, "refs/bisect/a-bisect-ref"));
	cl_git_pass(git_reference_lookup(&ref, fixture.worktree, "refs/bisect/a-bisect-ref"));

	cl_git_pass(git_reference_list(&refs, fixture.repo));
	cl_git_pass(git_reference_list(&wtrefs, fixture.worktree));

	cl_assert_equal_sz(wtrefs.count, refs.count + 1);

	git_reference_free(ref);
	git_reference_free(new_branch);
	git_strarray_dispose(&refs);
	git_strarray_dispose(&wtrefs);
	cl_git_pass(error);
}

void test_worktree_refs__list_worktree_specific_hidden_in_main_repo(void)
{
	git_strarray refs, wtrefs;
	git_reference *ref, *new_branch;
	int error = 0;
	git_oid oid;

	cl_git_pass(
	        git_reference_name_to_id(&oid, fixture.repo, "refs/heads/dir"));
	cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(
	        &ref, fixture.worktree, "refs/bisect/a-bisect-ref"));
	cl_git_pass(git_reference_create(
	        &new_branch, fixture.repo, "refs/bisect/a-bisect-ref", &oid,
	        0, "test"));

	cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(
	        &ref, fixture.worktree, "refs/bisect/a-bisect-ref"));
	cl_git_pass(git_reference_lookup(
	        &ref, fixture.repo, "refs/bisect/a-bisect-ref"));

	cl_git_pass(git_reference_list(&refs, fixture.repo));
	cl_git_pass(git_reference_list(&wtrefs, fixture.worktree));

	cl_assert_equal_sz(refs.count, wtrefs.count + 1);

	git_reference_free(ref);
	git_reference_free(new_branch);
	git_strarray_dispose(&refs);
	git_strarray_dispose(&wtrefs);
	cl_git_pass(error);
}

void test_worktree_refs__read_head(void)
{
	git_reference *head;

	cl_git_pass(git_repository_head(&head, fixture.worktree));

	git_reference_free(head);
}

void test_worktree_refs__set_head_fails_when_worktree_wants_linked_repos_HEAD(void)
{
	git_reference *head;

	cl_git_pass(git_repository_head(&head, fixture.repo));
	cl_git_fail(git_repository_set_head(fixture.worktree, git_reference_name(head)));

	git_reference_free(head);
}

void test_worktree_refs__set_head_fails_when_main_repo_wants_worktree_head(void)
{
	git_reference *head;

	cl_git_pass(git_repository_head(&head, fixture.worktree));
	cl_git_fail(git_repository_set_head(fixture.repo, git_reference_name(head)));

	git_reference_free(head);
}

void test_worktree_refs__set_head_works_for_current_HEAD(void)
{
	git_reference *head;

	cl_git_pass(git_repository_head(&head, fixture.repo));
	cl_git_pass(git_repository_set_head(fixture.repo, git_reference_name(head)));

	git_reference_free(head);
}

void test_worktree_refs__set_head_fails_when_already_checked_out(void)
{
	cl_git_fail(git_repository_set_head(fixture.repo, "refs/heads/testrepo-worktree"));
}

void test_worktree_refs__delete_fails_for_checked_out_branch(void)
{
	git_reference *branch;

	cl_git_pass(git_branch_lookup(&branch, fixture.repo,
		    "testrepo-worktree", GIT_BRANCH_LOCAL));
	cl_git_fail(git_branch_delete(branch));

	git_reference_free(branch);
}

void test_worktree_refs__delete_succeeds_after_pruning_worktree(void)
{
	git_worktree_prune_options opts = GIT_WORKTREE_PRUNE_OPTIONS_INIT;
	git_reference *branch;
	git_worktree *worktree;

	opts.flags = GIT_WORKTREE_PRUNE_VALID;

	cl_git_pass(git_worktree_lookup(&worktree, fixture.repo, fixture.worktreename));
	cl_git_pass(git_worktree_prune(worktree, &opts));
	git_worktree_free(worktree);

	cl_git_pass(git_branch_lookup(&branch, fixture.repo,
		    "testrepo-worktree", GIT_BRANCH_LOCAL));
	cl_git_pass(git_branch_delete(branch));
	git_reference_free(branch);
}

void test_worktree_refs__delete_unrelated_branch_on_worktree(void)
{
	git_reference *branch;

	cl_git_pass(git_branch_lookup(&branch, fixture.worktree,
		    "merge-conflict", GIT_BRANCH_LOCAL));
	cl_git_pass(git_branch_delete(branch));

	git_reference_free(branch);
}

void test_worktree_refs__delete_unrelated_branch_on_parent(void)
{
	git_reference *branch;

	cl_git_pass(git_branch_lookup(&branch, fixture.repo,
		    "merge-conflict", GIT_BRANCH_LOCAL));
	cl_git_pass(git_branch_delete(branch));

	git_reference_free(branch);
}

void test_worktree_refs__renaming_reference_updates_worktree_heads(void)
{
	git_reference *head, *branch, *renamed;

	cl_git_pass(git_branch_lookup(&branch, fixture.repo,
		    "testrepo-worktree", GIT_BRANCH_LOCAL));
	cl_git_pass(git_reference_rename(&renamed, branch, "refs/heads/renamed", 0, NULL));

	cl_git_pass(git_reference_lookup(&head, fixture.worktree, GIT_HEAD_FILE));
	cl_assert_equal_i(git_reference_type(head), GIT_REFERENCE_SYMBOLIC);
	cl_assert_equal_s(git_reference_symbolic_target(head), "refs/heads/renamed");

	git_reference_free(head);
	git_reference_free(branch);
	git_reference_free(renamed);
}

void test_worktree_refs__creating_refs_uses_commondir(void)
{
	   git_reference *head, *branch, *lookup;
	   git_commit *commit;
	   git_str refpath = GIT_STR_INIT;

	   cl_git_pass(git_str_joinpath(&refpath,
		       git_repository_commondir(fixture.worktree), "refs/heads/testbranch"));
	   cl_assert(!git_fs_path_exists(refpath.ptr));

	   cl_git_pass(git_repository_head(&head, fixture.worktree));
	   cl_git_pass(git_commit_lookup(&commit, fixture.worktree, git_reference_target(head)));
	   cl_git_pass(git_branch_create(&branch, fixture.worktree, "testbranch", commit, 0));
	   cl_git_pass(git_branch_lookup(&lookup, fixture.worktree, "testbranch", GIT_BRANCH_LOCAL));
	   cl_assert(git_reference_cmp(branch, lookup) == 0);
	   cl_assert(git_fs_path_exists(refpath.ptr));

	   git_reference_free(lookup);
	   git_reference_free(branch);
	   git_reference_free(head);
	   git_commit_free(commit);
	   git_str_dispose(&refpath);
}
