using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Newtonsoft.Json;
using GreenTree.Nachtragsmanagement.Core.Authentication;
using GreenTree.Nachtragsmanagement.Services.User;
using GreenTree.Nachtragsmanagement.Web.Models.Admin.User;
using GreenTree.Nachtragsmanagement.Core.Domain.User;
using GreenTree.Nachtragsmanagement.Core;
using GreenTree.Nachtragsmanagement.Core.Plugins;
using GreenTree.Nachtragsmanagement.Web.Framework.Authorization;
using GreenTree.Nachtragsmanagement.Services.Logging;
using GreenTree.Nachtragsmanagement.Web.Models.Admin.Plugins;
using GreenTree.Nachtragsmanagement.Web.Models.Admin.AppInfo;
using System.Reflection;
using System.Net;
using System.IO;
using GreenTree.Nachtragsmanagement.Services.Configuration;

namespace GreenTree.Nachtragsmanagement.Web.Controllers
{
    public class AdminController : Controller
    {
        private readonly IUserService _userService;
        private readonly IUserHelper _userHelper;
        private readonly IPluginFinder _pluginFinder;
        private readonly ILogger _logger;
        private readonly IWebHelper _webHelper;
        private readonly IConfigurationService _configurationService;

        public AdminController(
            IUserService userService,
            IUserHelper userHelper,
            IPluginFinder pluginFinder,
            ILogger logger,
            IWebHelper webHelper,
            IConfigurationService configurationService)
        {
            _userService = userService;
            _userHelper = userHelper;
            _pluginFinder = pluginFinder;
            _logger = logger;
            _webHelper = webHelper;
            _configurationService = configurationService;

            ViewData["AllRoles"] = _userService.GetAllRoles();
            ViewData["AllFunctions"] = _userService.GetAllFunctions();
        }

        #region Users

        /// <summary>
        /// Basic user view function
        /// </summary>
        [FunctionAuthorize(true, "Administration-Users")]
        public ActionResult ViewUsers()
        {
            var users = _userService.GetAllUsers();
            var userModels = users
                .Select(u => UserDataModel.FromUser(u, false))
                .ToList();

            return View("~/Views/Admin/Users/View.cshtml", userModels);
        }

        /// <summary>
        /// Get JSON data of specific user
        /// </summary>
        /// <param name="id">User id.</param>
        public ActionResult GetUser(int id = -1)
        {
            var user = _userService.GetUserById(id);
            if (user == null)
                return new JsonResult
                {
                    Data = "notFound",
                    JsonRequestBehavior = JsonRequestBehavior.AllowGet
                };

            var userModel = UserDataModel.FromUser(user, false);

            return new JsonResult
            {
                Data = JsonConvert.SerializeObject(userModel),
                JsonRequestBehavior = JsonRequestBehavior.AllowGet
            };
        }

        /// <summary>
        /// Callback result for user grid
        /// </summary>
        /// <param name="scrollHeight">The height of the grid scrollable component.</param>
        public ActionResult PartialUsers(int scrollHeight = -1)
        {
            var users = _userService.GetAllUsers();
            var userModels = users
                .Select(u => UserDataModel.FromUser(u, false))
                .ToList();

            ViewData["ScrollHeight"] = scrollHeight;

            return PartialView("~/Views/Admin/Users/_UserGridPartial.cshtml", userModels);
        }

        /// <summary>
        /// Partial edit for editing of existing or for new user
        /// </summary>
        /// <param name="id">Id for existing user, otherweise -1.</param>
        public ActionResult EditUser(int id = -1)
        {
            var user = _userService.GetUserById(id);
            var userModel = UserDataModel.FromUser(user, true);

            return PartialView("~/Views/Admin/Users/_UserEditPartial.cshtml", userModel);
        }

        /// <summary>
        /// Partial edit result if ModelState is valid, otherwise simple JSON result for success
        /// </summary>
        /// <param name="userModel">User model to be saved.</param>
        [HttpPost, ValidateInput(false)]
        public ActionResult EditUser(UserDataModel userModel)
        {
            try
            {
                if (!ModelState.IsValid)
                {
                    foreach (var role in userModel.RoleValues)
                        userModel.RoleDescriptions.Add(
                            ((IList<Role>)ViewData["AllRoles"])
                                .First(r => r.Id == role).Description);

                    return PartialView("~/Views/Admin/Users/_UserEditPartial.cshtml", userModel);
                }

                var selectedRoles = _userService.GetRolesByIds(userModel.RoleValues.ToArray());

                if (userModel.Id == -1)
                {
                    var user = userModel.ToUser();

                    user.SetRoles(selectedRoles);
                    user.Password = StaticHelper.GetMD5Hash(userModel.Password);

                    _userService.InsertUser(user);

                    _logger.Entity(user, Core.Domain.Logging.LogEntityActivity.Insert, _userHelper.FromCookiesOrSession());
                }
                else
                {
                    var user = _userService.GetUserById(userModel.Id);

                    user.CustomNumber = userModel.CustomNumber;
                    user.Forename = userModel.Forename;
                    user.Lastname = userModel.Lastname;
                    user.MailAddress = userModel.MailAddress;

                    if (!String.IsNullOrEmpty(userModel.Password))
                        user.Password = StaticHelper.GetMD5Hash(userModel.Password);

                    user.SetRoles(selectedRoles);

                    _userService.UpdateUser(user);

                    _logger.Entity(user, Core.Domain.Logging.LogEntityActivity.Update, _userHelper.FromCookiesOrSession());
                }

                return new JsonResult
                {
                    Data = "success"
                };
            }
            catch (Exception ex)
            {
                _logger.Error("Fehler bei Speicherung eines Benutzers.", ex, _userHelper.FromCookiesOrSession());

                return PartialView("~/Views/Shared/_PopupError.cshtml", ex);
            }
        }

        /// <summary>
        /// Simple JSON result for deleting a specific user
        /// </summary>
        /// <param name="id">User id.</param>
        [HttpPost]
        public ActionResult DeleteUser(int id)
        {
            try
            {
                var user = _userService.GetUserById(id);

                if (user != null)
                    _userService.DeleteUser(user);

                _logger.Entity(user, Core.Domain.Logging.LogEntityActivity.Delete, _userHelper.FromCookiesOrSession());

                return new JsonResult
                {
                    Data = "success"
                };
            }
            catch (Exception ex)
            {
                _logger.Error("Fehler bei Löschung eines Benutzers.", ex, _userHelper.FromCookiesOrSession());

                return PartialView("~/Views/Shared/_PopupError.cshtml", ex);
            }
        }

        #endregion

        #region Roles

        /// <summary>
        /// Basic role view function
        /// </summary>
        [FunctionAuthorize(true, "Administration-Roles")]
        public ActionResult ViewRoles()
        {
            var roles = _userService.GetAllRoles();
            var roleModels = roles
                .Select(r => RoleDataModel.FromRole(r, false))
                .ToList();

            return View("~/Views/Admin/Roles/View.cshtml", roleModels);
        }

        /// <summary>
        /// Get JSON data of specific role
        /// </summary>
        /// <param name="id">Role id.</param>
        public ActionResult GetRole(int id = -1)
        {
            var role = _userService.GetRoleById(id);
            if (role == null)
                return new JsonResult
                {
                    Data = "notFound",
                    JsonRequestBehavior = JsonRequestBehavior.AllowGet
                };

            var roleModel = RoleDataModel.FromRole(role, false);

            return new JsonResult
            {
                Data = JsonConvert.SerializeObject(roleModel),
                JsonRequestBehavior = JsonRequestBehavior.AllowGet
            };
        }

        /// <summary>
        /// Callback result for role grid
        /// </summary>
        /// <param name="scrollHeight">The height of the grid scrollable component.</param>
        public ActionResult PartialRoles(int scrollHeight = -1)
        {
            var roles = _userService.GetAllRoles();
            var roleModels = roles
                .Select(r => RoleDataModel.FromRole(r, false))
                .ToList();

            ViewData["ScrollHeight"] = scrollHeight;

            return PartialView("~/Views/Admin/Roles/_RoleGridPartial.cshtml", roleModels);
        }

        /// <summary>
        /// Partial edit for editing of existing or for new role
        /// </summary>
        /// <param name="id">Id for existing role, otherweise -1.</param>
        public ActionResult EditRole(int id = -1)
        {
            var role = _userService.GetRoleById(id);
            var roleModel = RoleDataModel.FromRole(role, true);

            return PartialView("~/Views/Admin/Roles/_RoleEditPartial.cshtml", roleModel);
        }

        /// <summary>
        /// Partial edit result if ModelState is valid, otherwise simple JSON result for success
        /// </summary>
        /// <param name="roleModel">Role model to be saved.</param>
        [HttpPost, ValidateInput(false)]
        public ActionResult EditRole(RoleDataModel roleModel)
        {
            try
            {
                if (!ModelState.IsValid)
                {
                    foreach (var role in roleModel.FunctionValues)
                        roleModel.FunctionDescriptions.Add(
                            ((IList<Function>)ViewData["AllFunctions"])
                                .First(r => r.Id == role).Description);

                    return PartialView("~/Views/Admin/Roles/_RoleEditPartial.cshtml", roleModel);
                }

                var selectedFunctions = _userService.GetFunctionsByIds(roleModel.FunctionValues.ToArray());

                if (roleModel.Id == -1)
                {
                    var role = roleModel.ToRole();

                    role.SetFunctions(selectedFunctions);

                    _userService.InsertRole(role);

                    _logger.Entity(role, Core.Domain.Logging.LogEntityActivity.Insert, _userHelper.FromCookiesOrSession());
                }
                else
                {
                    var role = _userService.GetRoleById(roleModel.Id);

                    role.Description = roleModel.Description;
                    role.Level = roleModel.Level;

                    role.SetFunctions(selectedFunctions);

                    _userService.UpdateRole(role);

                    _logger.Entity(role, Core.Domain.Logging.LogEntityActivity.Update, _userHelper.FromCookiesOrSession());
                }

                return new JsonResult
                {
                    Data = "success"
                };
            }
            catch (Exception ex)
            {
                _logger.Error("Fehler bei Speicherung einer Rolle.", ex, _userHelper.FromCookiesOrSession());

                return PartialView("~/Views/Shared/_PopupError.cshtml", ex);
            }
        }

        /// <summary>
        /// Simple JSON result for deleting a specific role
        /// </summary>
        /// <param name="id">Role id.</param>
        /// <param name="replaceId">Id of role which user get in place of deleting role.</param>
        [HttpPost]
        public ActionResult DeleteRole(int id, int replaceId)
        {
            try
            {
                var role = _userService.GetRoleById(id);
                var replaceRole = _userService.GetRoleById(replaceId);

                var roleUsers = _userService.GetUsersByRole(id);

                foreach (var user in roleUsers)
                {
                    if (replaceId == -1)
                        user.Roles.Remove(role);
                    else
                        user.Roles.Add(replaceRole);

                    _userService.UpdateUser(user);
                }

                if (role != null)
                    _userService.DeleteRole(role);

                _logger.Entity(role, Core.Domain.Logging.LogEntityActivity.Delete, _userHelper.FromCookiesOrSession());

                return new JsonResult
                {
                    Data = "success"
                };
            }
            catch (Exception ex)
            {
                _logger.Error("Fehler bei Löschung einer Rolle.", ex, _userHelper.FromCookiesOrSession());

                return PartialView("~/Views/Shared/_PopupError.cshtml", ex);
            }
        }

        #endregion

        #region Plugins

        /// <summary>
        /// Basic plugin view function
        /// </summary>
        public ActionResult ViewPlugins()
        {
            var plugins = _pluginFinder.GetPlugins<IPlugin>(LoadPluginsMode.All);
            var pluginModels = plugins
                .Select(p => PluginDataModel.FromPluginDesciptor(p.PluginDescriptor, _webHelper))
                .ToList();

            return View("~/Views/Admin/Plugins/View.cshtml", pluginModels);
        }

        /// <summary>
        /// Callback result for plugin grid
        /// </summary>
        /// <param name="scrollHeight">The height of the grid scrollable component.</param>
        public ActionResult PartialPlugins(int scrollHeight = -1)
        {
            var plugins = _pluginFinder.GetPlugins<IPlugin>(LoadPluginsMode.All);
            var pluginModels = plugins
                .Select(p => PluginDataModel.FromPluginDesciptor(p.PluginDescriptor, _webHelper))
                .ToList();

            ViewData["ScrollHeight"] = scrollHeight;

            return View("~/Views/Admin/Plugins/_PluginsGridPartial.cshtml", pluginModels);
        }

        /// <summary>
        /// Installs a plugin
        /// </summary>
        /// <param name="pluginName">SystemName of plugin.</param>
        [HttpPost]
        public ActionResult InstallPlugin(string pluginName)
        {
            try
            {
                var pluginDescriptor = _pluginFinder.GetPluginDescriptorBySystemName(pluginName, LoadPluginsMode.All);
                if (pluginDescriptor == null)
                    return RedirectToAction("Plugins");

                if (pluginDescriptor.Installed)
                    return RedirectToAction("Plugins");

                pluginDescriptor.Instance().Install();

                _logger.Information(String.Format("Plugin \"{0}\" erfolgreich installiert.", pluginName));

                _webHelper.RestartAppDomain();

                return new JsonResult
                {
                    Data = "success"
                };
            }
            catch (Exception ex)
            {
                _logger.Error(
                    String.Format("Fehler bei der Installation des Plugin \"{0}\".", pluginName), ex);

                return PartialView("~/Views/Shared/_PopupError.cshtml", ex);
            }
        }

        /// <summary>
        /// Uninstalls a plugin
        /// </summary>
        /// <param name="pluginName">SystemName of plugin.</param>
        [HttpPost]
        public ActionResult UninstallPlugin(string pluginName)
        {
            try
            {
                var pluginDescriptor = _pluginFinder.GetPluginDescriptorBySystemName(pluginName, LoadPluginsMode.All);
                if (pluginDescriptor == null)
                    return RedirectToAction("Plugins");

                if (!pluginDescriptor.Installed)
                    return RedirectToAction("Plugins");

                pluginDescriptor.Instance().Uninstall();

                _logger.Information(String.Format("Plugin \"{0}\" erfolgreich deinstalliert.", pluginName));

                _webHelper.RestartAppDomain();

                return new JsonResult
                {
                    Data = "success"
                };
            }
            catch (Exception ex)
            {
                _logger.Error(
                    String.Format("Fehler bei der Installation des Plugin \"{0}\".", pluginName), ex);

                return PartialView("~/Views/Shared/_PopupError.cshtml", ex);
            }
        }

        #endregion

        #region AppInfo

        /// <summary>
        /// Basic appInfo view function
        /// </summary>
        public ActionResult ViewAppInfo()
        {
            var assemblies = AppDomain.CurrentDomain.GetAssemblies()
                .Select(a => new AssemblyDataModel
                {
                    Name = a.GetName().Name,
                    Version = a.GetName().Version.ToString(),
                    Manufacturer =
                        a.GetCustomAttributes<AssemblyCompanyAttribute>().Any()
                            ? a.GetCustomAttributes<AssemblyCompanyAttribute>()
                                .FirstOrDefault().Company
                            : "Unbekannt"
                })
                .OrderBy(a => a.Manufacturer)
                .ThenBy(a => a.Name)
                .ToArray();

            var model = new AppInfoDataModel
            {
                Assemblies = assemblies,
                BaseDirectory = AppDomain.CurrentDomain.BaseDirectory
            };

            return View("~/Views/Admin/AppInfo/View.cshtml", model);
        }

        /// <summary>
        /// Update check
        /// </summary>
        [HttpPost]
        public ActionResult CheckUpdate()
        {
            var model = new AppUpdateDataModel
            {
                Package = "GreenTree.Nachtragsmanagement",
                CurrentVersion = AppendixVersion.CurrentVersion
            };

            var wc = new WebClient();

            wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
            var webData = String.Format("Package={0}&CurrentVersion={1}", model.Package, model.CurrentVersion);
            var webResult = wc.UploadString(_configurationService.GetCurrentConfiguration().CheckUpdateUrl, webData);

            var jsonResult = JsonConvert.DeserializeObject<JsonResult>(webResult);
            var result = JsonConvert.DeserializeObject<AppUpdateDataModel>(jsonResult.Data.ToString());

            return new JsonResult
            {
                Data = result
            };
        }

        #endregion
    }
}