using System.Collections.Generic;
using System.Linq;
using System.Net;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Oqtane.Enums;
using Oqtane.Infrastructure;
using Oqtane.Models;
using Oqtane.Repository;
using Oqtane.Security;
using Oqtane.Shared;

namespace Oqtane.Controllers
{
    [Route(ControllerRoutes.ApiRoute)]
    public class PageController : Controller
    {
        private readonly IPageRepository _pages;
        private readonly IModuleRepository _modules;
        private readonly IPageModuleRepository _pageModules;
        private readonly IPermissionRepository _permissionRepository;
        private readonly ISettingRepository _settings;
        private readonly IUserPermissions _userPermissions;
        private readonly IUrlMappingRepository _urlMappings;
        private readonly ISyncManager _syncManager;
        private readonly ILogManager _logger;
        private readonly Alias _alias;

        public PageController(IPageRepository pages, IModuleRepository modules, IPageModuleRepository pageModules, IPermissionRepository permissionRepository, ISettingRepository settings, IUserPermissions userPermissions, IUrlMappingRepository urlMappings, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger)
        {
            _pages = pages;
            _modules = modules;
            _pageModules = pageModules;
            _permissionRepository = permissionRepository;
            _settings = settings;
            _userPermissions = userPermissions;
            _urlMappings = urlMappings;
            _syncManager = syncManager;
            _logger = logger;
            _alias = tenantManager.GetAlias();
        }

        // GET: api/<controller>?siteid=x
        [HttpGet]
        public IEnumerable<Page> Get(string siteid)
        {
            List<Page> pages = new List<Page>();

            int SiteId;
            if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
            {
                List<Setting> settings = _settings.GetSettings(EntityNames.Page).ToList();

                foreach (Page page in _pages.GetPages(SiteId))
                {
                    if (_userPermissions.IsAuthorized(User, PermissionNames.View, page.PermissionList))
                    {
                        page.Settings = settings.Where(item => item.EntityId == page.PageId)
                            .Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, page.PermissionList))
                            .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
                        pages.Add(page);
                    }
                }
            }
            else
            {
                _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Page Get Attempt {SiteId}", siteid);
                HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                pages = null;
            }

            return pages;
        }

        // GET api/<controller>/5
        [HttpGet("{id}")]
        public Page Get(int id)
        {
            var page = _pages.GetPage(id);
            if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, page.PermissionList))
            {
                page.Settings = _settings.GetSettings(EntityNames.Page, page.PageId)
                    .Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, page.PermissionList))
                    .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
                return page;
            }
            else
            {
                if (page != null)
                {
                    _logger.Log(LogLevel.Warning, this, LogFunction.Security, "Unauthorized Page Get Attempt {PageId}", id);
                    HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                }
                else
                {
                    HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
                }
                return null;
            }
        }

        // GET api/<controller>/path/x?path=y
        [HttpGet("path/{siteid}")]
        public Page Get(string path, int siteid)
        {
            Page page = _pages.GetPage(WebUtility.UrlDecode(path), siteid);
            if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, page.PermissionList))
            {
                page.Settings = _settings.GetSettings(EntityNames.Page, page.PageId)
                    .Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, page.PermissionList))
                    .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
                return page;
            }
            else
            {
                if (page != null)
                {
                    _logger.Log(LogLevel.Warning, this, LogFunction.Security, "Unauthorized Page Get Attempt {SiteId} {Path}", siteid, path);
                    HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                }
                else
                {
                    HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
                }
                return null;
            }
        }

        // POST api/<controller>
        [HttpPost]
        [Authorize(Roles = RoleNames.Registered)]
        public Page Post([FromBody] Page page)
        {
            if (ModelState.IsValid && page.SiteId == _alias.SiteId)
            {
                List<Permission> permissions;
                if (page.ParentId != null)
                {
                    permissions = _pages.GetPage(page.ParentId.Value).PermissionList;
                }
                else
                {
                    permissions = new List<Permission> {
                        new Permission(PermissionNames.Edit, RoleNames.Admin, true)
                    };
                }

                if (_userPermissions.IsAuthorized(User, PermissionNames.Edit, permissions))
                {
                    page = _pages.AddPage(page);
                    _syncManager.AddSyncEvent(_alias, EntityNames.Page, page.PageId, SyncEventActions.Create);
                    _syncManager.AddSyncEvent(_alias, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);
                    _logger.Log(LogLevel.Information, this, LogFunction.Create, "Page Added {Page}", page);

                    if (!page.Path.StartsWith("admin/"))
                    {
                        var modules = _modules.GetModules(page.SiteId).Where(item => item.AllPages).ToList();
                        foreach (Module module in modules)
                        {
                            var pageModule = _pageModules.GetPageModules(page.SiteId).FirstOrDefault(item => item.ModuleId == module.ModuleId);
                            _pageModules.AddPageModule(new PageModule { PageId = page.PageId, ModuleId = pageModule.ModuleId, Title = pageModule.Title, Pane = pageModule.Pane, Order = pageModule.Order, ContainerType = pageModule.ContainerType });
                        }
                    }
                }
                else
                {
                    _logger.Log(LogLevel.Warning, this, LogFunction.Create, "User Not Authorized To Add Page {Page}", page);
                    HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                    page = null;
                }
            }
            else
            {
                _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Page Post Attempt {Page}", page);
                HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                page = null;
            }
            return page;
        }

        // POST api/<controller>/5?userid=x
        [HttpPost("{id}")]
        [Authorize(Roles = RoleNames.Registered)]
        public Page Post(int id, string userid)
        {
            Page page = null;
            Page parent = _pages.GetPage(id);
            User user = _userPermissions.GetUser(User);
            if (parent != null && parent.SiteId == _alias.SiteId && parent.IsPersonalizable && user.UserId == int.Parse(userid))
            {
                var path = Utilities.GetFriendlyUrl(user.Username);
                page = _pages.GetPage(parent.Path + "/" + path, parent.SiteId);
                if (page == null)
                {
                    page = new Page();
                    page.SiteId = parent.SiteId;
                    page.ParentId = parent.PageId;
                    page.Name = user.Username;
                    page.Path = parent.Path + "/" + path;
                    page.Title = ((!string.IsNullOrEmpty(user.DisplayName)) ? user.DisplayName : user.Username) + " - " + parent.Name;
                    page.Order = 0;
                    page.IsNavigation = false;
                    page.Url = "";
                    page.ThemeType = parent.ThemeType;
                    page.DefaultContainerType = parent.DefaultContainerType;
                    page.Icon = parent.Icon;
                    page.PermissionList = new List<Permission>()
                    {
                        new Permission(PermissionNames.View, int.Parse(userid), true),
                        new Permission(PermissionNames.View, RoleNames.Everyone, true),
                        new Permission(PermissionNames.Edit, int.Parse(userid), true)
                    };
                    page.IsPersonalizable = false;
                    page.UserId = int.Parse(userid);
                    page = _pages.AddPage(page);

                    // copy parent page settings
                    var settings = _settings.GetSettings(EntityNames.Page, parent.PageId);
                    foreach (var setting in settings)
                    {
                        _settings.AddSetting(new Setting { EntityName = EntityNames.Page, EntityId = page.PageId, SettingName = setting.SettingName, SettingValue = setting.SettingValue, IsPrivate = setting.IsPrivate });
                    }

                    // copy modules
                    List<PageModule> pagemodules = _pageModules.GetPageModules(page.SiteId).ToList();
                    foreach (PageModule pm in pagemodules.Where(item => item.PageId == parent.PageId && !item.IsDeleted))
                    {
                        Module module = new Module();
                        module.SiteId = page.SiteId;
                        module.PageId = page.PageId;
                        module.ModuleDefinitionName = pm.Module.ModuleDefinitionName;
                        module.AllPages = false;
                        module.PermissionList = new List<Permission>()
                        {
                            new Permission(PermissionNames.View, int.Parse(userid), true),
                            new Permission(PermissionNames.View, RoleNames.Everyone, true),
                            new Permission(PermissionNames.Edit, int.Parse(userid), true)
                        };
                        module = _modules.AddModule(module);

                        string content = _modules.ExportModule(pm.ModuleId);
                        if (content != "")
                        {
                            _modules.ImportModule(module.ModuleId, content);
                        }

                        PageModule pagemodule = new PageModule();
                        pagemodule.PageId = page.PageId;
                        pagemodule.ModuleId = module.ModuleId;
                        pagemodule.Title = pm.Title;
                        pagemodule.Pane = pm.Pane;
                        pagemodule.Order = pm.Order;
                        pagemodule.ContainerType = pm.ContainerType;
                        pagemodule.EffectiveDate = pm.EffectiveDate;
                        pagemodule.ExpiryDate = pm.ExpiryDate;
                        pagemodule.Header = pm.Header;
                        pagemodule.Footer = pm.Footer;

                        _pageModules.AddPageModule(pagemodule);
                    }

                    _syncManager.AddSyncEvent(_alias, EntityNames.Page, page.PageId, SyncEventActions.Create);
                    _syncManager.AddSyncEvent(_alias, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);

                    // set user personalized page path
                    var settingName = $"PersonalizedPagePath:{page.SiteId}:{parent.PageId}";
                    var pathSetting = _settings.GetSetting(EntityNames.User, page.UserId.Value, settingName);
                    if(pathSetting == null)
                    {
                        pathSetting = new Setting { EntityName = EntityNames.User, EntityId = page.UserId.Value, SettingName = settingName, SettingValue = path, IsPrivate = false };
                        _settings.AddSetting(pathSetting);
                    }
                    else
                    {
                        pathSetting.SettingValue = path;
                        _settings.UpdateSetting(pathSetting);
                    }

                    _syncManager.AddSyncEvent(_alias, EntityNames.User, user.UserId, SyncEventActions.Update);
                }
            }
            else
            {
                _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Page Post Attempt {PageId} By User {UserId}", id, userid);
                HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                page = null;
            }
            return page;
        }

        // PUT api/<controller>/5
        [HttpPut("{id}")]
        [Authorize(Roles = RoleNames.Registered)]
        public Page Put(int id, [FromBody] Page page)
        {
            // get current page
            var currentPage = _pages.GetPage(page.PageId, false);

            if (ModelState.IsValid && page.SiteId == _alias.SiteId && page.PageId == id && currentPage != null && _userPermissions.IsAuthorized(User, page.SiteId, EntityNames.Page, page.PageId, PermissionNames.Edit))
            {
                // get current page permissions
                var currentPermissions = _permissionRepository.GetPermissions(page.SiteId, EntityNames.Page, page.PageId).ToList();

                // preserve new path and deleted status
                var newPath = page.Path;
                var deleted = page.IsDeleted;
                page.Path = currentPage.Path;
                page.IsDeleted = currentPage.IsDeleted;

                // update page
                UpdatePage(page, page.PageId, page.Path, newPath, deleted);

                // get differences between current and new page permissions
                var added = GetPermissionsDifferences(page.PermissionList, currentPermissions);
                var removed = GetPermissionsDifferences(currentPermissions, page.PermissionList);

                // synchronize module permissions
                if (page.UpdateModulePermissions && (added.Count > 0 || removed.Count > 0))
                {
                    var pageModules = _pageModules.GetPageModules(page.SiteId);
                    foreach (PageModule pageModule in pageModules.Where(item => item.PageId == page.PageId).ToList())
                    {
                        // ignore "shared" modules
                        if (!pageModules.Any(item => item.ModuleId == pageModule.ModuleId && item.PageId != pageModule.PageId))
                        {
                            var modulePermissions = _permissionRepository.GetPermissions(pageModule.Module.SiteId, EntityNames.Module, pageModule.Module.ModuleId).ToList();
                            // permissions added
                            foreach (Permission permission in added)
                            {
                                if (!modulePermissions.Any(item => item.PermissionName == permission.PermissionName
                                  && item.RoleId == permission.RoleId && item.UserId == permission.UserId && item.IsAuthorized == permission.IsAuthorized))
                                {
                                    _permissionRepository.AddPermission(new Permission
                                    {
                                        SiteId = page.SiteId,
                                        EntityName = EntityNames.Module,
                                        EntityId = pageModule.ModuleId,
                                        PermissionName = permission.PermissionName,
                                        RoleId = permission.RoleId,
                                        UserId = permission.UserId,
                                        IsAuthorized = permission.IsAuthorized
                                    });
                                }
                            }

                            // permissions removed
                            foreach (Permission permission in removed)
                            {
                                var modulePermission = modulePermissions.FirstOrDefault(item => item.PermissionName == permission.PermissionName
                                  && item.RoleId == permission.RoleId && item.UserId == permission.UserId && item.IsAuthorized == permission.IsAuthorized);
                                if (modulePermission != null)
                                {
                                    _permissionRepository.DeletePermission(modulePermission.PermissionId);
                                }
                            }
                        }
                    }
                }

                // update child paths
                if (page.ParentId != currentPage.ParentId)
                {
                    foreach (Page _page in _pages.GetPages(page.SiteId).Where(item => item.Path.StartsWith(currentPage.Path)).ToList())
                    {
                        _page.Path = _page.Path.Replace(currentPage.Path, page.Path);
                        _pages.UpdatePage(_page);
                    }
                }

                _syncManager.AddSyncEvent(_alias, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);

                // personalized page
                if (page.UserId != null && currentPage.Path != page.Path)
                {
                    // set user personalized page path
                    var settingName = $"PersonalizedPagePath:{page.SiteId}:{page.ParentId}";
                    var path = page.Path.Substring(page.Path.LastIndexOf("/") + 1);
                    var settings = _settings.GetSettings(EntityNames.User, page.UserId.Value).ToList();
                    var setting = settings.FirstOrDefault(item => item.SettingName == settingName);
                    if (setting == null)
                    {
                        setting = new Setting { EntityName = EntityNames.User, EntityId = page.UserId.Value, SettingName = settingName, SettingValue = path, IsPrivate = false };
                        _settings.AddSetting(setting);
                    }
                    else
                    {
                        setting.SettingValue = path;
                        _settings.UpdateSetting(setting);
                    }
                    _syncManager.AddSyncEvent(_alias, EntityNames.User, page.UserId.Value, SyncEventActions.Update);
                }

                _logger.Log(LogLevel.Information, this, LogFunction.Update, "Page Updated {Page}", page);
            }
            else
            {
                _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Page Put Attempt {Page}", page);
                HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                page = null;
            }
            return page;
        }

        private void UpdatePage(Page page, int pageId, string oldPath, string newPath, bool deleted)
        {
            var update = (page.PageId == pageId);
            if (oldPath != newPath)
            {
                var urlMapping = _urlMappings.GetUrlMapping(page.SiteId, page.Path);
                if (urlMapping != null)
                {
                    urlMapping.MappedUrl = newPath + page.Path.Substring(oldPath.Length);
                    _urlMappings.UpdateUrlMapping(urlMapping);
                }

                page.Path = newPath + page.Path.Substring(oldPath.Length);
                update = true;
            }
            if (deleted != page.IsDeleted)
            {
                page.IsDeleted = deleted;
                update = true;
            }
            if (update)
            {
                _pages.UpdatePage(page);
                _syncManager.AddSyncEvent(_alias, EntityNames.Page, page.PageId, SyncEventActions.Update);
            }

            // update any children
            foreach (var _page in _pages.GetPages(page.SiteId).Where(item => item.ParentId == page.PageId))
            {
                UpdatePage(_page, pageId, oldPath, newPath, deleted);
            }
        }

        private List<Permission> GetPermissionsDifferences(List<Permission> permissions1, List<Permission> permissions2)
        {
            var differences = new List<Permission>();
            foreach (Permission p in permissions1)
            {
                if (!permissions2.Any(item => item.PermissionName == p.PermissionName && item.RoleId == p.RoleId && item.UserId == p.UserId && item.IsAuthorized == p.IsAuthorized))
                {
                    differences.Add(p);
                }
            }
            return differences;
        }

        // PUT api/<controller>/?siteid=x&pageid=y&parentid=z
        [HttpPut]
        [Authorize(Roles = RoleNames.Registered)]
        public void Put(int siteid, int pageid, int? parentid)
        {
            if (siteid == _alias.SiteId && _pages.GetPage(pageid, false) != null && _userPermissions.IsAuthorized(User, siteid, EntityNames.Page, pageid, PermissionNames.Edit))
            {
                int order = 1;
                List<Page> pages = _pages.GetPages(siteid).ToList();
                foreach (Page page in pages.Where(item => item.ParentId == parentid).OrderBy(item => item.Order))
                {
                    if (page.Order != order)
                    {
                        page.Order = order;
                        _pages.UpdatePage(page);
                        _syncManager.AddSyncEvent(_alias, EntityNames.Page, page.PageId, SyncEventActions.Update);
                    }
                    order += 2;
                }

                _syncManager.AddSyncEvent(_alias, EntityNames.Site, siteid, SyncEventActions.Refresh);
                _logger.Log(LogLevel.Information, this, LogFunction.Update, "Page Order Updated {SiteId} {PageId} {ParentId}", siteid, pageid, parentid);
            }
            else
            {
                _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Page Put Attempt {SiteId} {PageId} {ParentId}", siteid, pageid, parentid);
                HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
            }
        }

        // DELETE api/<controller>/5
        [HttpDelete("{id}")]
        [Authorize(Roles = RoleNames.Registered)]
        public void Delete(int id)
        {
            Page page = _pages.GetPage(id);
            if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, page.SiteId, EntityNames.Page, page.PageId, PermissionNames.Edit))
            {
                _pages.DeletePage(page.PageId);
                _syncManager.AddSyncEvent(_alias, EntityNames.Page, page.PageId, SyncEventActions.Delete);
                _syncManager.AddSyncEvent(_alias, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);
                _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Page Deleted {PageId}", page.PageId);
            }
            else
            {
                _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Page Delete Attempt {PageId}", id);
                HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
            }
        }

        // POST api/<controller>/5/6
        [HttpPost("{fromPageId}/{toPageId}/{usePagePermissions}")]
        [Authorize(Roles = RoleNames.Registered)]
        public void Post(int fromPageId, int toPageId, bool usePagePermissions)
        {
            var fromPage = _pages.GetPage(fromPageId);
            if (fromPage != null && fromPage.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, fromPage.PermissionList))
            {
                var toPage = _pages.GetPage(toPageId);
                if (toPage != null && toPage.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, toPage.PermissionList))
                {
                    // copy page settings
                    var settings = _settings.GetSettings(EntityNames.Page, fromPage.PageId).ToList();
                    foreach (var setting in settings)
                    {
                        _settings.AddSetting(new Setting
                        {
                            EntityName = setting.EntityName,
                            EntityId = toPage.PageId,
                            SettingName = setting.SettingName,
                            SettingValue = setting.SettingValue,
                            IsPrivate = setting.IsPrivate
                        });
                    }

                    // copy modules
                    List<PageModule> pageModules = _pageModules.GetPageModules(fromPage.SiteId).ToList();
                    foreach (PageModule pm in pageModules.Where(item => item.PageId == fromPage.PageId && !item.Module.AllPages && !item.IsDeleted))
                    {
                        Module module;

                        // determine if module is a shared instance (ie. exists on other pages)
                        if (!pageModules.Any(item => item.ModuleId == pm.ModuleId && item.PageId != fromPage.PageId))
                        {
                            // create new module
                            module = new Module();
                            module.SiteId = fromPage.SiteId;
                            module.PageId = toPageId;
                            module.ModuleDefinitionName = pm.Module.ModuleDefinitionName;
                            module.AllPages = false;
                            if (usePagePermissions)
                            {
                                module.PermissionList = toPage.PermissionList;
                            }
                            else
                            {
                                module.PermissionList = pm.Module.PermissionList;
                            }
                            module.PermissionList = module.PermissionList.Select(item => new Permission
                            {
                                SiteId = item.SiteId,
                                EntityName = EntityNames.Module,
                                EntityId = -1,
                                PermissionName = item.PermissionName,
                                RoleName = item.RoleName,
                                UserId = item.UserId,
                                IsAuthorized = item.IsAuthorized,
                            }).ToList();

                            module = _modules.AddModule(module);

                            // copy module content (includes settings)
                            string content = _modules.ExportModule(pm.ModuleId);
                            if (content != "")
                            {
                                _modules.ImportModule(module.ModuleId, content);
                            }
                        }
                        else
                        {
                            // use existing module
                            module = pm.Module;
                        }

                        PageModule pageModule = new PageModule();
                        pageModule.PageId = toPageId;
                        pageModule.ModuleId = module.ModuleId;
                        pageModule.Title = pm.Title;
                        pageModule.Pane = pm.Pane;
                        pageModule.Order = pm.Order;
                        pageModule.ContainerType = pm.ContainerType;
                        pageModule.EffectiveDate = pm.EffectiveDate;
                        pageModule.ExpiryDate = pm.ExpiryDate;
                        pageModule.Header = pm.Header;
                        pageModule.Footer = pm.Footer;

                        _pageModules.AddPageModule(pageModule);
                    }

                    _syncManager.AddSyncEvent(_alias, EntityNames.Site, fromPage.SiteId, SyncEventActions.Refresh);
                }
                else
                {
                    HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                }
            }
            else
            {
                HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
            }
        }
    }
}
