using System;
using System.Collections.Generic;
using System.Data.Common;
using System.IO;
using System.Linq;
using LeafWeb.Core.Utility;
using Renci.SshNet;

namespace LeafWeb.Core.Remote
{
	public class PiscalSshClient : IPiscalClient
	{
		private const string BaseDirectory = "./LeafWeb";
		private const string RemoteScriptPath = BaseDirectory + "/piscal_manager.sh";
		private readonly PasswordConnectionInfo _connectionInfo;

		private const string StatusSuccess = "success";
		private const string StatusRunning = "running";
		private const string StatusError = "error";

		public PiscalSshClient(string connectionString)
		{
			var conn = new DbConnectionStringBuilder {ConnectionString = connectionString};

			var host = conn["host"] as string;
			var username = conn["username"] as string;
			var password = conn["password"] as string;
			_connectionInfo = new PasswordConnectionInfo(host, username, password);
		}

		private SshClient GetSshClient()
		{
			return new SshClient(_connectionInfo);
		}

		private ScpClient GetScpClient()
		{
			return new ScpClient(_connectionInfo);
		}

		public void RunLeafInputFile(PiscalLeafInputFile file)
		{
			var inputPath = $"{BaseDirectory}/{file.DirectoryName}/{file.Filename}";

			// copy file
			using (var scp = GetScpClient())
			using (var stream = new MemoryStream(file.Contents))
			{
				Console.WriteLine(inputPath);
				scp.Connect();
				scp.Upload(stream, inputPath);
				scp.Disconnect();
			}

			// begin processing
			using (var ssh = GetSshClient())
			{
				ssh.Connect();
				var commandText = $"{RemoteScriptPath} -d {file.DirectoryName} -f {file.Filename}";
				var command = ssh.CreateCommand(commandText);
				command.Execute();
				ssh.Disconnect();

				if (command.ExitStatus != 0)
					throw new PiscalClientException(command.Error);

				Console.Write(command.Result);
			}
		}

		public PiscalStatus GetLeafInputFileStatus(PiscalLeafInputFile file)
		{
			var statusRaw = GetLeafInputStatusRaw(file);

			switch (statusRaw[0])
			{
				case StatusRunning:
					return PiscalStatus.Running;
				case StatusSuccess:
					return PiscalStatus.Success;
				default:
					return PiscalStatus.Error;
			}
		}

		private string[] GetLeafInputStatusRaw(PiscalLeafInputFile file)
		{
			using (var ssh = GetSshClient())
			{
				ssh.Connect();
				var commandText = $"{RemoteScriptPath} -d {file.DirectoryName} -s";
				var command = ssh.CreateCommand(commandText);
				command.Execute();
				ssh.Disconnect();

				if (command.ExitStatus != 0)
					throw new PiscalClientException(command.Error);

				return command.Result
					.SplitNewLine()
					.Where(s => s.Length > 0)
					.Select(s => s.Trim()).ToArray();
			}
		}

		/// <summary>
		/// Gets the leaf output from piscal, only run on if result status is success
		/// </summary>
		public IEnumerable<PiscalLeafOutputFile> RetrieveLeafOutput(PiscalLeafInputFile file)
		{
			// get output files
			var status = GetLeafInputStatusRaw(file);
			if (status[0] != StatusSuccess)
				throw new PiscalClientException("output not available, status is " + status[0]);

			var filePaths = status.Skip(1);

			using (var scp = GetScpClient())
			{
				scp.Connect();
				foreach (var filePath in filePaths)
				{
					using (var stream = new MemoryStream())
					{
						scp.Download(filePath, stream);
						yield return 
							new PiscalLeafOutputFile
							{
								Contents = stream.ToArray(),
								Filename = filePath.FilenameFromPath(),
								DirectoryName = file.DirectoryName
							};
					}
				}

				scp.Disconnect();
			}
		}

		public void CleanupLeafProcess(PiscalLeafInputFile file)
		{
			var status = GetLeafInputStatusRaw(file);
			if (status[0] == StatusRunning)
				throw new PiscalClientException("Trying to cleanup a running process");

			using (var ssh = GetSshClient())
			{
				ssh.Connect();
				var commandText = $"{RemoteScriptPath} -d {file.DirectoryName} -c";
				var command = ssh.CreateCommand(commandText);
				command.Execute();
				ssh.Disconnect();

				if (command.ExitStatus != 0)
					throw new PiscalClientException(command.Error);
			}
		}
	}
}