using Microsoft.CodeAnalysis;
using Seenginx.Models;
using Seenginx.Services.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Text;
using System.Threading.Tasks;

namespace Seenginx.Services
{
	public class NginxService : INginxService
	{
		private readonly SeenginxSettings Settings;
		private readonly CommandService CommandService;

		public NginxService(SeenginxSettings configPaths, CommandService commandService)
		{
			Settings = configPaths;
			CommandService = commandService;
		}

		public async Task<IEnumerable<ConfigFile>> GetFilesAsync()
		{
			await Task.Run(() => { });
			var rootConfigs = Directory.GetFiles(Settings.nginx.rootPath, "*.conf");
			var rootDraftConfigs = Directory.GetFiles(Settings.nginx.rootPath, "*.conf.draft");

			var confdConfigs = Directory.GetFiles(Path.Combine(Settings.nginx.rootPath, "conf.d"), "*.conf");
			var confDraftConfigs = Directory.GetFiles(Path.Combine(Settings.nginx.rootPath, "conf.d"), "*.conf.draft");

			var sitesAvailableConfigs = Directory.GetFiles(Path.Combine(Settings.nginx.rootPath, "sites-available"), "*.conf");
			var sitesAvailableDraftConfigs = Directory.GetFiles(Path.Combine(Settings.nginx.rootPath, "sites-available"), "*.conf.draft");

			var rootConfigFiles = rootConfigs.Select(fp =>
			{
				var name = Path.GetFileNameWithoutExtension(fp);
				var configFile = new ConfigFile();
				configFile.CanBeDeleted = false;
				configFile.Folder = string.Empty;
				configFile.LastUpdated = File.GetLastWriteTime(fp);
				configFile.Name = name;
				configFile.FullPath = Path.Combine(Settings.nginx.rootPath, $"{configFile.Name}.conf");
				configFile.Body = File.ReadAllText(fp);
				if (rootDraftConfigs.Any(cfp => cfp.Contains(name)))
					configFile.DraftBody = File.ReadAllText(rootDraftConfigs.First(cfp => cfp.Contains(name)));

				return configFile;
			});
			var confdConfigFiles = confdConfigs.Select(fp =>
			{
				var fileName = Path.GetFileNameWithoutExtension(fp);
				var configFile = new ConfigFile();
				configFile.CanBeDeleted = true;
				configFile.Folder = "conf.d";
				configFile.LastUpdated = File.GetLastWriteTime(fp);
				configFile.Name = fileName;
				configFile.FullPath = Path.Combine(Settings.nginx.rootPath, configFile.Folder, $"{configFile.Name}.conf");
				configFile.Body = File.ReadAllText(fp);
				if (confDraftConfigs.Any(cfp => cfp.Contains(fileName)))
					configFile.DraftBody = File.ReadAllText(confDraftConfigs.First(cfp => cfp.Contains(fileName)));

				return configFile;
			});
			var sitesAvailableConfigFiles = sitesAvailableConfigs.Select(fp =>
			{
				var fileName = Path.GetFileNameWithoutExtension(fp);
				var configFile = new ConfigFile();
				configFile.CanBeDeleted = true;
				configFile.Folder = "sites-available";
				configFile.LastUpdated = File.GetLastWriteTime(fp);
				configFile.Name = fileName;
				configFile.FullPath = Path.Combine(Settings.nginx.rootPath, configFile.Folder, $"{configFile.Name}.conf");
				configFile.Body = File.ReadAllText(fp);
				if (sitesAvailableDraftConfigs.Any(cfp => cfp.Contains(fileName)))
					configFile.DraftBody = File.ReadAllText(sitesAvailableDraftConfigs.First(cfp => cfp.Contains(fileName)));

				return configFile;
			});

			var finalList = new List<ConfigFile>();
			finalList.AddRange(rootConfigFiles);
			finalList.AddRange(confdConfigFiles);
			finalList.AddRange(sitesAvailableConfigFiles);

			finalList = finalList.OrderBy(cf => cf.Name).ToList();

			return finalList;
		}

		public async Task<IEnumerable<Template>> GetTemplates()
		{
			var templates = new List<Template>();
			try
			{
				var nginxTemplateDirectory = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "templates", "nginx");
				var nginxTemplateFiles = Directory.GetFiles(nginxTemplateDirectory, "*.template");

				foreach (var templateFilePath in nginxTemplateFiles)
				{
					var template = new Template();
					var templateFileLines = await File.ReadAllLinesAsync(templateFilePath);
					template.Name = templateFileLines.FirstOrDefault();
					template.Code = string.Join(Environment.NewLine, templateFileLines.Skip(2));
					templates.Add(template);
				}

				return templates;
			}
			catch (Exception ex)
			{
				throw ex;
			}
		}


		public async Task<Result> ValidateForAddFileAsync(NewFileForm newFileForm)
		{
			var validationResult = new Result();
			try
			{
				var filePath = Path.Combine(Settings.nginx.rootPath, "conf.d", $"{newFileForm.Name}.conf");

				if (File.Exists(filePath))
					return validationResult.Invalidate($"There's already a file with the '{newFileForm.Name}.conf' name.");

				return validationResult;
			}
			catch (Exception ex)
			{
				return validationResult.Invalidate($"Exception at {nameof(ValidateForAddFileAsync)}()", ex);
			}
		}

		public async Task<Result<ConfigFile>> AddFileAsync(NewFileForm newFileForm)
		{
			var addResult = new Result<ConfigFile>();
			try
			{
				var newFile = new ConfigFile();
				newFile.Name = newFileForm.Name;
				newFile.Folder = "conf.d";
				newFile.FullPath = Path.Combine(Settings.nginx.rootPath, newFile.Folder, $"{newFileForm.Name}.conf");
				newFile.Body = newFileForm.SelectedTemplate == 0.ToString() ? string.Empty : (await GetTemplates()).SingleOrDefault(t => t.Name == newFileForm.SelectedTemplate)?.Code;
				newFile.LastUpdated = DateTime.UtcNow;

				await File.WriteAllTextAsync(newFile.FullPath, newFile.Body, Encoding.UTF8);

				addResult.SetData(newFile);

				return addResult;
			}
			catch (Exception ex)
			{
				return addResult.Invalidate(ex.Message, ex);
			}
		}


		public async Task<Result> ValidateForDeleteFileAsync(ConfigFile configFile)
		{
			var validationResult = new Result();
			try
			{
				if (File.Exists(configFile.FullPath))
					return validationResult.Invalidate($"File '{configFile.FullPath}' not found.");

				return validationResult;
			}
			catch (Exception ex)
			{
				return validationResult.Invalidate(ex.Message, ex);
			}
		}

		public async Task<Result> DeleteFileAsync(ConfigFile configFile)
		{
			var result = new Result();
			try
			{
				if (!File.Exists(configFile.FullPath))
					return result.Invalidate($"File '{configFile.FullPath}' not found.");

				File.Delete(configFile.FullPath);
				if (configFile.DraftBody != null)
					File.Delete($"{configFile.FullPath}.draft");

				return result;
			}
			catch (Exception ex)
			{
				return result.Invalidate(ex.Message, ex);
			}
		}


		public async Task<Result> ValidateForSaveFileAsync(ConfigFile configFile)
		{
			var validationResult = new Result();
			try
			{
				if (File.Exists(configFile.FullPath))
					return validationResult.Invalidate($"File '{configFile.FullPath}' not found.");

				return validationResult;
			}
			catch (Exception ex)
			{
				return validationResult.Invalidate(ex.Message, ex);
			}
		}

		public async Task<Result<ConfigFile>> SaveFileAsync(ConfigFile configFile)
		{
			var saveResult = new Result<ConfigFile>();
			try
			{
				await File.WriteAllTextAsync(Path.Combine(Settings.nginx.rootPath, configFile.Folder, $"{configFile.Name}.conf"), configFile.Body, Encoding.UTF8);

				return saveResult;
			}
			catch (Exception ex)
			{
				return saveResult.Invalidate(ex.Message, ex);
			}
		}


		public async Task<Result> ValidateForSaveDraftFileAsync(ConfigFile configFile)
		{
			var validationResult = new Result();
			try
			{
				var draftPathName = $"{configFile.FullPath}.draft";

				if (!File.Exists(draftPathName))
					return validationResult.Invalidate($"File '{draftPathName}' not found.");

				return validationResult;
			}
			catch (Exception ex)
			{
				return validationResult.Invalidate(ex.Message, ex);
			}
		}

		public async Task<Result<ConfigFile>> SaveDraftFileAsync(ConfigFile configFile)
		{
			var saveDraftResult = new Result<ConfigFile>();
			try
			{
				await File.WriteAllTextAsync(Path.Combine(Settings.nginx.rootPath, configFile.Folder, $"{configFile.Name}.conf.draft"), configFile.DraftBody, Encoding.UTF8);

				return saveDraftResult;
			}
			catch (Exception ex)
			{
				return saveDraftResult.Invalidate(ex.Message, ex);
			}
		}


		public async Task<Result> ValidateForRenameFileAsync(List<ConfigFile> configFiles, ConfigFile selectedConfigFile, string newName)
		{
			var renameResult = new Result();
			try
			{


				if (configFiles.Count(cf => cf.Name == newName) > 0)
					return renameResult.Invalidate($"File '{selectedConfigFile.FullPath}' already exists.");




				if (!File.Exists(selectedConfigFile.FullPath))
					return renameResult.Invalidate($"Original file '{selectedConfigFile.FullPath}' not found.");

				var newPathName = Path.Combine(Settings.nginx.rootPath, selectedConfigFile.Folder, $"{newName}.conf");
				if (File.Exists(newPathName))
					return renameResult.Invalidate($"The file '{newPathName}' already exists.");

				return renameResult;
			}
			catch (Exception ex)
			{
				return renameResult.Invalidate(ex.Message, ex);
			}
		}

		public async Task<Result<ConfigFile>> RenameFileAsync(ConfigFile configFile, string newName)
		{
			var renameResult = new Result<ConfigFile>();
			try
			{
				var originalPathName = Path.Combine(Settings.nginx.rootPath, configFile.Folder, $"{configFile.Name}.conf");
				var newPathName = Path.Combine(Settings.nginx.rootPath, configFile.Folder, newName);

				File.Move(originalPathName, newPathName, overwrite: false);
				if (configFile.DraftBody != null)
					File.Move($"{originalPathName}.draft", $"{newPathName}.draft", overwrite: false);

				configFile.FullPath = newPathName;
				renameResult.SetData(configFile);

				return renameResult;
			}
			catch (Exception ex)
			{
				return renameResult.Invalidate(ex.Message, ex);
			}
		}



		public async Task<Result<string>> TestFileAsync(ConfigFile configFile)
		{
			var command1 = string.Empty;
			var command2 = string.Empty;
			var runResult = new Result<string>();
			var originalPathName = Path.Combine(Settings.nginx.rootPath, configFile.Folder, $"{configFile.Name}.conf");
			switch (Environment.OSVersion.Platform)
			{
				case PlatformID.Win32S:
				case PlatformID.Win32Windows:
				case PlatformID.Win32NT:
				case PlatformID.WinCE:
					//command1 = @$"cd ""C:\Program Files\nginx""";
					command2 = @$"nginx.exe -c ""{originalPathName}"" -t";
					break;
				case PlatformID.Unix:
					command1 = "";
					break;
				case PlatformID.Xbox:
				case PlatformID.MacOSX:
					return runResult.Invalidate($"Invalid platform '{Environment.OSVersion.Platform}'");
			}

			runResult = await CommandService.RunCommandAsync(/*command1,*/ command2);

			return runResult;
		}

	}
}
