/*
 * Copyright 2011 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.tests;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.PermissionType;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.GitBlitException.ForbiddenException;
import com.gitblit.GitBlitException.UnauthorizedException;
import com.gitblit.Keys;
import com.gitblit.models.FederationModel;
import com.gitblit.models.FederationProposal;
import com.gitblit.models.FederationSet;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.ServerSettings;
import com.gitblit.models.ServerStatus;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.servlet.RpcServlet;
import com.gitblit.utils.RpcUtils;

/**
 * Tests all the rpc client utility methods, the rpc filter and rpc servlet.
 *
 * @author James Moger
 *
 */
public class RpcTests extends GitblitUnitTest {

	String url = GitBlitSuite.url;
	String account = GitBlitSuite.account;
	String password = GitBlitSuite.password;

	private static final AtomicBoolean started = new AtomicBoolean(false);

	@BeforeClass
	public static void startGitblit() throws Exception {
		started.set(GitBlitSuite.startGitblit());
	}

	@AfterClass
	public static void stopGitblit() throws Exception {
		if (started.get()) {
			GitBlitSuite.stopGitblit();
		}
	}

	@Test
	public void testGetProtocolVersion() throws IOException {
		int protocol = RpcUtils.getProtocolVersion(url, null, null);
		assertEquals(RpcServlet.PROTOCOL_VERSION, protocol);
	}

	@Test
	public void testListRepositories() throws IOException {
		Map<String, RepositoryModel> map = RpcUtils.getRepositories(url, null, null);
		assertNotNull("Repository list is null!", map);
		assertTrue("Repository list is empty!", map.size() > 0);
	}

	@Test
	public void testListUsers() throws IOException {
		List<UserModel> list = null;
		try {
			list = RpcUtils.getUsers(url, null, null);
		} catch (UnauthorizedException e) {
		}
		assertNull("Server allows anyone to admin!", list);

		list = RpcUtils.getUsers(url, "admin", "admin".toCharArray());
		assertTrue("User list is empty!", list.size() > 0);
	}

	@Test
	public void testGetUser() throws IOException {
		UserModel user = null;
		try {
			user = RpcUtils.getUser("admin", url, null, null);
		} catch (ForbiddenException e) {
		}
		assertNull("Server allows anyone to get user!", user);

		user = RpcUtils.getUser("admin", url, "admin", "admin".toCharArray());
		assertEquals("User is not the admin!", "admin", user.username);
		assertTrue("User is not an administrator!", user.canAdmin());
	}

	@Test
	public void testListTeams() throws IOException {
		List<TeamModel> list = null;
		try {
			list = RpcUtils.getTeams(url, null, null);
		} catch (UnauthorizedException e) {
		}
		assertNull("Server allows anyone to admin!", list);

		list = RpcUtils.getTeams(url, "admin", "admin".toCharArray());
		assertTrue("Team list is empty!", list.size() > 0);
		assertEquals("admins", list.get(0).name);
	}

	@Test
	public void testUserAdministration() throws IOException {
		UserModel user = new UserModel("garbage");
		user.canAdmin = true;
		user.password = "whocares";

		// create
		assertTrue("Failed to create user!",
				RpcUtils.createUser(user, url, account, password.toCharArray()));

		UserModel retrievedUser = findUser(user.username);
		assertNotNull("Failed to find " + user.username, retrievedUser);
		assertTrue("Retrieved user can not administer Gitblit", retrievedUser.canAdmin);

		// rename and toggle admin permission
		String originalName = user.username;
		user.username = "garbage2";
		user.canAdmin = false;
		assertTrue("Failed to update user!",
				RpcUtils.updateUser(originalName, user, url, account, password.toCharArray()));

		retrievedUser = findUser(user.username);
		assertNotNull("Failed to find " + user.username, retrievedUser);
		assertTrue("Retrieved user did not update", !retrievedUser.canAdmin);

		// delete
		assertTrue("Failed to delete " + user.username,
				RpcUtils.deleteUser(retrievedUser, url, account, password.toCharArray()));

		retrievedUser = findUser(user.username);
		assertNull("Failed to delete " + user.username, retrievedUser);
	}

	private UserModel findUser(String name) throws IOException {
		List<UserModel> users = RpcUtils.getUsers(url, account, password.toCharArray());
		UserModel retrievedUser = null;
		for (UserModel model : users) {
			if (model.username.equalsIgnoreCase(name)) {
				retrievedUser = model;
				break;
			}
		}
		return retrievedUser;
	}

	@Test
	public void testRepositoryAdministration() throws IOException {
		RepositoryModel model = new RepositoryModel();
		model.name = "garbagerepo.git";
		model.description = "created by RpcUtils";
		model.addOwner("garbage");
		model.accessRestriction = AccessRestrictionType.VIEW;
		model.authorizationControl = AuthorizationControl.AUTHENTICATED;

		// create
		RpcUtils.deleteRepository(model, url, account, password.toCharArray());
		assertTrue("Failed to create repository!",
				RpcUtils.createRepository(model, url, account, password.toCharArray()));

		RepositoryModel retrievedRepository = findRepository(model.name);
		assertNotNull("Failed to find " + model.name, retrievedRepository);
		assertEquals(AccessRestrictionType.VIEW, retrievedRepository.accessRestriction);
		assertEquals(AuthorizationControl.AUTHENTICATED, retrievedRepository.authorizationControl);

		// rename and change access restriciton
		String originalName = model.name;
		model.name = "garbagerepo2.git";
		model.accessRestriction = AccessRestrictionType.CLONE;
		model.authorizationControl = AuthorizationControl.NAMED;
		RpcUtils.deleteRepository(model, url, account, password.toCharArray());
		assertTrue("Failed to update repository!", RpcUtils.updateRepository(originalName, model,
				url, account, password.toCharArray()));

		retrievedRepository = findRepository(model.name);
		assertNotNull("Failed to find " + model.name, retrievedRepository);
		assertTrue("Access retriction type is wrong",
				AccessRestrictionType.CLONE.equals(retrievedRepository.accessRestriction));

		// restore VIEW restriction
		retrievedRepository.accessRestriction = AccessRestrictionType.VIEW;
		assertTrue("Failed to update repository!", RpcUtils.updateRepository(retrievedRepository.name, retrievedRepository,
				url, account, password.toCharArray()));
		retrievedRepository = findRepository(retrievedRepository.name);

		// memberships
		UserModel testMember = new UserModel("justadded");
		assertTrue(RpcUtils.createUser(testMember, url, account, password.toCharArray()));

		List<RegistrantAccessPermission> permissions = RpcUtils.getRepositoryMemberPermissions(retrievedRepository, url, account,
				password.toCharArray());
		assertEquals("Unexpected permissions! " + permissions.toString(), 1, permissions.size());
		permissions.add(new RegistrantAccessPermission(testMember.username, AccessPermission.VIEW, PermissionType.EXPLICIT, RegistrantType.USER, null, true));
		assertTrue(
				"Failed to set member permissions!",
				RpcUtils.setRepositoryMemberPermissions(retrievedRepository, permissions, url, account,
						password.toCharArray()));
		permissions = RpcUtils.getRepositoryMemberPermissions(retrievedRepository, url, account,
				password.toCharArray());
		boolean foundMember = false;
		for (RegistrantAccessPermission permission : permissions) {
			if (permission.registrant.equalsIgnoreCase(testMember.username)) {
				foundMember = true;
				assertEquals(AccessPermission.VIEW, permission.permission);
				break;
			}
		}
		assertTrue("Failed to find member!", foundMember);

		// delete
		assertTrue("Failed to delete " + model.name, RpcUtils.deleteRepository(retrievedRepository,
				url, account, password.toCharArray()));

		retrievedRepository = findRepository(model.name);
		assertNull("Failed to delete " + model.name, retrievedRepository);

		for (UserModel u : RpcUtils.getUsers(url, account, password.toCharArray())) {
			if (u.username.equals(testMember.username)) {
				assertTrue(RpcUtils.deleteUser(u, url, account, password.toCharArray()));
				break;
			}
		}
	}

	private RepositoryModel findRepository(String name) throws IOException {
		Map<String, RepositoryModel> repositories = RpcUtils.getRepositories(url, account,
				password.toCharArray());
		RepositoryModel retrievedRepository = null;
		for (RepositoryModel model : repositories.values()) {
			if (model.name.equalsIgnoreCase(name)) {
				retrievedRepository = model;
				break;
			}
		}
		return retrievedRepository;
	}

	@Test
	public void testTeamAdministration() throws IOException {
		List<TeamModel> teams = RpcUtils.getTeams(url, account, password.toCharArray());
		assertEquals(1, teams.size());

		// Create the A-Team
		TeamModel aTeam = new TeamModel("A-Team");
		aTeam.users.add("admin");
		aTeam.addRepositoryPermission("helloworld.git");
		assertTrue(RpcUtils.createTeam(aTeam, url, account, password.toCharArray()));

		aTeam = null;
		teams = RpcUtils.getTeams(url, account, password.toCharArray());
		assertEquals(2, teams.size());
		for (TeamModel team : teams) {
			if (team.name.equals("A-Team")) {
				aTeam = team;
				break;
			}
		}
		assertNotNull(aTeam);
		assertTrue(aTeam.hasUser("admin"));
		assertTrue(aTeam.hasRepositoryPermission("helloworld.git"));

		RepositoryModel helloworld = null;
		Map<String, RepositoryModel> repositories = RpcUtils.getRepositories(url, account,
				password.toCharArray());
		for (RepositoryModel repository : repositories.values()) {
			if (repository.name.equals("helloworld.git")) {
				helloworld = repository;
				break;
			}
		}
		assertNotNull(helloworld);

		// Confirm that we have added the team
		List<String> helloworldTeams = RpcUtils.getRepositoryTeams(helloworld, url, account,
				password.toCharArray());
		assertEquals(1, helloworldTeams.size());
		assertTrue(helloworldTeams.contains(aTeam.name));

		// set no teams
		List<RegistrantAccessPermission> permissions = new ArrayList<RegistrantAccessPermission>();
		for (String team : helloworldTeams) {
			permissions.add(new RegistrantAccessPermission(team, AccessPermission.NONE, PermissionType.EXPLICIT, RegistrantType.TEAM, null, true));
		}
		assertTrue(RpcUtils.setRepositoryTeamPermissions(helloworld, permissions, url, account,
				password.toCharArray()));
		helloworldTeams = RpcUtils.getRepositoryTeams(helloworld, url, account,
				password.toCharArray());
		assertEquals(0, helloworldTeams.size());

		// delete the A-Team
		assertTrue(RpcUtils.deleteTeam(aTeam, url, account, password.toCharArray()));

		teams = RpcUtils.getTeams(url, account, password.toCharArray());
		assertEquals(1, teams.size());
	}

	@Test
	public void testFederationRegistrations() throws Exception {
		List<FederationModel> registrations = RpcUtils.getFederationRegistrations(url, account,
				password.toCharArray());
		assertTrue("No federation registrations were retrieved!", registrations.size() >= 0);
	}

	@Test
	public void testFederationResultRegistrations() throws Exception {
		List<FederationModel> registrations = RpcUtils.getFederationResultRegistrations(url,
				account, password.toCharArray());
		assertTrue("No federation result registrations were retrieved!", registrations.size() >= 0);
	}

	@Test
	public void testFederationProposals() throws Exception {
		List<FederationProposal> proposals = RpcUtils.getFederationProposals(url, account,
				password.toCharArray());
		assertTrue("No federation proposals were retrieved!", proposals.size() >= 0);
	}

	@Test
	public void testFederationSets() throws Exception {
		List<FederationSet> sets = RpcUtils.getFederationSets(url, account, password.toCharArray());
		assertTrue("No federation sets were retrieved!", sets.size() >= 0);
	}

	@Test
	public void testSettings() throws Exception {
		ServerSettings settings = RpcUtils.getSettings(url, account, password.toCharArray());
		assertNotNull("No settings were retrieved!", settings);
	}

	@Test
	public void testServerStatus() throws Exception {
		ServerStatus status = RpcUtils.getStatus(url, account, password.toCharArray());
		assertNotNull("No status was retrieved!", status);
	}

	@Test
	public void testUpdateSettings() throws Exception {
		Map<String, String> updated = new HashMap<String, String>();

		// grab current setting
		ServerSettings settings = RpcUtils.getSettings(url, account, password.toCharArray());
		boolean showSizes = settings.get(Keys.web.showRepositorySizes).getBoolean(true);
		showSizes = !showSizes;

		// update setting
		updated.put(Keys.web.showRepositorySizes, String.valueOf(showSizes));
		boolean success = RpcUtils.updateSettings(updated, url, account, password.toCharArray());
		assertTrue("Failed to update server settings", success);

		// confirm setting change
		settings = RpcUtils.getSettings(url, account, password.toCharArray());
		boolean newValue = settings.get(Keys.web.showRepositorySizes).getBoolean(false);
		assertEquals(newValue, showSizes);

		// restore setting
		newValue = !newValue;
		updated.put(Keys.web.showRepositorySizes, String.valueOf(newValue));
		success = RpcUtils.updateSettings(updated, url, account, password.toCharArray());
		assertTrue("Failed to update server settings", success);
		settings = RpcUtils.getSettings(url, account, password.toCharArray());
		showSizes = settings.get(Keys.web.showRepositorySizes).getBoolean(true);
		assertEquals(newValue, showSizes);
	}

	@Test
	public void testBranches() throws Exception {
		Map<String, Collection<String>> branches = RpcUtils.getBranches(url, account,
				password.toCharArray());
		assertNotNull(branches);
		assertTrue(branches.size() > 0);
	}

	@Test
	public void testFork() throws Exception {
		// test forking by an administrator
		// admins are all-powerful and can fork the unforakable :)
		testFork(account, password, true, true);
		testFork(account, password, false, true);

		// test forking by a permitted normal user
		UserModel forkUser = new UserModel("forkuser");
		forkUser.password = forkUser.username;
		forkUser.canFork = true;
		RpcUtils.deleteUser(forkUser, url, account, password.toCharArray());
		RpcUtils.createUser(forkUser, url, account, password.toCharArray());
		testFork(forkUser.username, forkUser.password, true, true);
		testFork(forkUser.username, forkUser.password, false, false);
		RpcUtils.deleteUser(forkUser, url, account, password.toCharArray());

		// test forking by a non-permitted normal user
		UserModel noForkUser = new UserModel("noforkuser");
		noForkUser.password = noForkUser.username;
		noForkUser.canFork = false;
		RpcUtils.deleteUser(noForkUser, url, account, password.toCharArray());
		RpcUtils.createUser(noForkUser, url, account, password.toCharArray());
		testFork(forkUser.username, forkUser.password, true, false);
		testFork(forkUser.username, forkUser.password, false, false);
		RpcUtils.deleteUser(noForkUser, url, account, password.toCharArray());
	}

	private void testFork(String forkAcct, String forkAcctPassword, boolean allowForks, boolean expectSuccess) throws Exception {
		// test does not exist
		RepositoryModel dne = new RepositoryModel();
		dne.name = "doesNotExist.git";
        assertFalse(String.format("Successfully forked %s!", dne.name),
                RpcUtils.forkRepository(dne, url, forkAcct, forkAcctPassword.toCharArray()));

		// delete any previous fork
		RepositoryModel fork = findRepository(String.format("~%s/helloworld.git", forkAcct));
		if (fork != null) {
			RpcUtils.deleteRepository(fork, url, account, password.toCharArray());
		}

		// update the origin to allow forks or not
		RepositoryModel origin = findRepository("helloworld.git");
		origin.allowForks = allowForks;
		RpcUtils.updateRepository(origin.name, origin, url, account, password.toCharArray());

		// fork the repository
		if (expectSuccess) {
			assertTrue(String.format("Failed to fork %s!", origin.name),
                RpcUtils.forkRepository(origin, url, forkAcct, forkAcctPassword.toCharArray()));
		} else {
			assertFalse(String.format("Successfully forked %s!", origin.name),
	                RpcUtils.forkRepository(origin, url, forkAcct, forkAcctPassword.toCharArray()));
		}

        // attempt another fork
        assertFalse(String.format("Successfully forked %s!", origin.name),
                RpcUtils.forkRepository(origin, url, forkAcct, forkAcctPassword.toCharArray()));

        // delete the fork repository
        RpcUtils.deleteRepository(fork, url, account, password.toCharArray());
	}
}