/*
 * Syncany, www.syncany.org
 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com> 
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.syncany.tests.integration.cli;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.contrib.java.lang.system.TextFromStandardInputStream.emptyStandardInputStream;

import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.TextFromStandardInputStream;
import org.syncany.cli.CommandLineClient;
import org.syncany.cli.util.InitConsole;
import org.syncany.database.FileVersionComparator.FileChange;
import org.syncany.tests.unit.util.TestCliUtil;
import org.syncany.tests.unit.util.TestFileUtil;
import org.syncany.tests.util.TestAssertUtil;
import org.syncany.tests.util.TestConfigUtil;
import org.syncany.util.StringUtil;

/*
 * Originally the before/after methods in this class were supposed to be @BeforeClass and @AfterClass
 * methods, and the initialized* variables were supposed to be set only once. So basically: 
 * 
 *   1 x init
 *   N x connect tests
 *   
 * Reason behind doing that: Masker key generation takes ages! 
 * 
 * However, the TextFromStandardInputStream is somewhat incompatible with this logic. At least I have not
 * been able to do the above. If you stumble over some weird code, this is probably the reason. 
 */
public class ConnectCommandWithEncryptionTest {
	private File originalWorkingDirectory;

	private File initializedRepoFolder;
	private File initializedRepoFolderCopy;
	private Map<String, String> initializedRepoConnectionSettings;
	private String initializedRepoPassword;
	private String initializedRepoConnectLink;

	@Rule
	public final TextFromStandardInputStream systemInMock = emptyStandardInputStream();

	@Before
	public void before() throws Exception {
		originalWorkingDirectory = new File(System.getProperty("user.dir"));
		initInteractiveWithEncryptionWithParams(); // sets the initialized* variables
	}

	@After
	public void after() {
		TestCliUtil.setCurrentDirectory(originalWorkingDirectory);
		TestFileUtil.deleteDirectory(initializedRepoFolder);
		TestFileUtil.deleteDirectory(initializedRepoFolderCopy);
	}

	@Test
	public void testCliConnectInteractiveWithParamsEnc() throws Exception {
		// Setup
		resetRepo();
		Map<String, String> clientB = TestCliUtil.createLocalTestEnv("B", initializedRepoConnectionSettings);

		// Run
		File localDirB = TestFileUtil.createTempDirectoryInSystemTemp();
		TestCliUtil.setCurrentDirectory(localDirB);

		String[] connectArgs = new String[] {
				"connect"
		};

		System.out.println("repopath = " + clientB.get("repopath"));
		systemInMock.provideText(StringUtil.join(new String[] {
				"local",
				clientB.get("repopath"),
				"somesuperlongpassword"
		}, "\n") + "\n");

		String[] cliOutputA = TestCliUtil.runAndCaptureOutput(new CommandLineClient(connectArgs));
		String cliOutputSingleLineA = StringUtil.join(cliOutputA, " ");

		assertTrue(localDirB.exists());
		assertTrue(new File(localDirB + "/.syncany").exists());
		assertTrue(new File(localDirB + "/.syncany/syncany").exists());
		assertTrue(new File(localDirB + "/.syncany/master").exists());
		assertTrue(new File(localDirB + "/.syncany/config.xml").exists());
		assertTrue(cliOutputSingleLineA.contains("Repository connected"));

		TestAssertUtil.assertFileEquals(new File(localDirB, ".syncany/syncany"), new File(initializedRepoConnectionSettings.get("path"), "syncany"),
				FileChange.CHANGED_ATTRIBUTES, FileChange.CHANGED_LAST_MOD_DATE);
		TestAssertUtil.assertFileEquals(new File(localDirB, ".syncany/master"), new File(initializedRepoConnectionSettings.get("path"), "master"),
				FileChange.CHANGED_ATTRIBUTES, FileChange.CHANGED_LAST_MOD_DATE);

		// Tear down
		TestFileUtil.deleteDirectory(localDirB);
	}

	@Test
	public void testCliConnectInteractiveWithLinkEnc() throws Exception {
		// Setup
		resetRepo();

		// 2. Connect
		File localDirB = TestFileUtil.createTempDirectoryInSystemTemp();
		TestCliUtil.setCurrentDirectory(localDirB);

		String[] connectArgs = new String[] {
				"connect",
				initializedRepoConnectLink
		};

		systemInMock.provideText(StringUtil.join(new String[] {
				// No path or params, not needed because link provided
				initializedRepoPassword
		}, "\n") + "\n");

		String[] cliOutputA = TestCliUtil.runAndCaptureOutput(new CommandLineClient(connectArgs));
		String cliOutputSingleLineA = StringUtil.join(cliOutputA, " ");

		assertTrue(localDirB.exists());
		assertTrue(new File(localDirB + "/.syncany").exists());
		assertTrue(new File(localDirB + "/.syncany/syncany").exists());
		assertTrue(new File(localDirB + "/.syncany/master").exists());
		assertTrue(new File(localDirB + "/.syncany/config.xml").exists());
		assertTrue(cliOutputSingleLineA.contains("Repository connected"));

		TestAssertUtil.assertFileEquals(new File(localDirB, ".syncany/syncany"), new File(initializedRepoConnectionSettings.get("path"), "syncany"),
				FileChange.CHANGED_ATTRIBUTES, FileChange.CHANGED_LAST_MOD_DATE);
		TestAssertUtil.assertFileEquals(new File(localDirB, ".syncany/master"), new File(initializedRepoConnectionSettings.get("path"), "master"),
				FileChange.CHANGED_ATTRIBUTES, FileChange.CHANGED_LAST_MOD_DATE);

		// Tear down
		TestFileUtil.deleteDirectory(localDirB);
	}

	@Test
	@Ignore
	public void testCliConnectInteractiveWithParamsInvalidPassEnc() throws Exception {
		// TODO
	}

	@Test
	@Ignore
	public void testCliConnectInteractiveWithLinkInvalidPassEnc() throws Exception {
		// TODO
	}

	private void initInteractiveWithEncryptionWithParams() throws Exception {
		// Ensuring no console is set
		InitConsole.setInstance(null);

		// Create test repo and temp. client
		initializedRepoPassword = "somesuperlongpassword";
		initializedRepoConnectionSettings = TestConfigUtil.createTestLocalConnectionSettings();

		Map<String, String> clientA = TestCliUtil.createLocalTestEnv("A", initializedRepoConnectionSettings);
		File tempLocalDirA = new File(clientA.get("localdir"));

		initializedRepoFolder = new File(clientA.get("repopath"));

		TestCliUtil.setCurrentDirectory(tempLocalDirA);

		String[] initArgs = new String[] {
				"init"
		};

		systemInMock.provideText(StringUtil.join(new String[] {
				"local",
				initializedRepoFolder.getAbsolutePath(),
				initializedRepoPassword,
				initializedRepoPassword
		}, "\n") + "\n");

		String[] cliOutputA = TestCliUtil.runAndCaptureOutput(new CommandLineClient(initArgs));
		String cliOutputSingleLineA = StringUtil.join(cliOutputA, " ");

		assertTrue(new File(initializedRepoFolder + "/syncany").exists());
		assertTrue(new File(initializedRepoFolder + "/master").exists());
		assertTrue(new File(initializedRepoFolder + "/databases").exists());
		assertTrue(new File(initializedRepoFolder + "/multichunks").exists());

		Pattern linkPattern = Pattern.compile("(syncany://[^\\s]+)");
		Matcher linkMatcher = linkPattern.matcher(cliOutputSingleLineA);

		if (!linkMatcher.find()) {
			fail("Cannot find syncany:// link");
		}

		initializedRepoConnectLink = linkMatcher.group(1);
		initializedRepoFolderCopy = TestFileUtil.createTempDirectoryInSystemTemp();

		// Make copy of the repo
		TestFileUtil.deleteDirectory(initializedRepoFolderCopy);
		FileUtils.copyDirectory(initializedRepoFolder, initializedRepoFolderCopy);

		TestFileUtil.deleteDirectory(tempLocalDirA);
	}

	private void resetRepo() throws IOException {
		TestFileUtil.deleteDirectory(initializedRepoFolder);
		assertFalse(initializedRepoFolder.exists());

		FileUtils.copyDirectory(initializedRepoFolderCopy, initializedRepoFolder);
		assertTrue(initializedRepoFolder.exists());
	}
}
