using System.Linq;
using Hims.Shared.UserModels.Menu;
using Button = Hims.Shared.UserModels.Menu.Button;

namespace Hims.Infrastructure.Services
{
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Dapper;
    using Domain.Entities;
    using Domain.Repositories.UnitOfWork;
    using Domain.Services;
    using Relationship = Shared.UserModels.Menu.Relationship;
    using Access = Shared.UserModels.Menu.Access;

    /// <inheritdoc />
    public class MenuService : IMenuService
    {
        /// <summary>
        /// The unit of work.
        /// </summary>
        private readonly IUnitOfWork unitOfWork;

        /// <inheritdoc cref="IBedService" />
        public MenuService(IUnitOfWork unitOfWork) => this.unitOfWork = unitOfWork;

        public async Task<IEnumerable<ViewModel>> FetchAsync()
        {
            var query = $@"SELECT
                                m.""MenuId"",
	                            m.""Url"",
	                            m.""MainPage"",
	                            m.""SubPage"",
	                            m.""Category"",
	                            m.""Priority"",
	                            m.""GeneralClasses"",
	                            m.""IconClasses"",
	                            t.""MenuTypeName"",
	                            m.""MenuTypeId"",
                                COUNT(mb.""MenuButtonId"") OVER(PARTITION BY mb.""MenuButtonId"") AS ""ButtonsCount""
                            FROM
	                            ""Menu"" M
	                            LEFT JOIN ""MenuType"" t on t.""MenuTypeId"" = m.""MenuTypeId""
                                LEFT JOIN ""MenuButton"" mb on mb.""MenuId"" = m.""MenuId"" 
	                            ORDER BY m.""MenuTypeId""";
            var records = await this.unitOfWork.Current.QueryAsync<ViewModel>(query);
            return records;
        }

        public async Task<IEnumerable<string>> FreeFetchAsync()
        {
            var query = $@"SELECT ""Url"" FROM ""Menu""";
            var records = await this.unitOfWork.Current.QueryAsync<string>(query);
            return records;
        }

        public async Task<IEnumerable<Button.ViewModel>> FetchMenuButtonsAsync(Relationship.FilterModel model)
        {
            var query = $@"SELECT M.*, r.""MenuButtonId"" as ""RelationshipMenuId"" FROM ""MenuButton"" M
            LEFT JOIN ""MenuButtonRelationship"" r ON (r.""MenuButtonId"" = M.""MenuButtonId"" AND (""RoleId"" = {model.RoleId} OR ""RoleId"" IS NULL))";
            var records = await this.unitOfWork.Current.QueryAsync<Button.ViewModel>(query);
            return records;
        }

        public async Task<IEnumerable<Relationship.ViewModel>> FetchRelationshipAsync(Relationship.FilterModel model)
        {
            var query = $@"SELECT
	                            m.""MenuId"",
                                m.""MainPage"",
                                m.""SubPage"",
                                m.""IconClasses"" ""Icon"",
                                m.""Url"",
                                r.""RoleId"",
                                r.""MenuId"" as ""RelationshipMenuId"",
                                m.""MenuTypeId"",
                                m.""Priority""
                                FROM
	                        ""Menu""
	                        M LEFT JOIN ""MenuRelationship"" r ON (r.""MenuId"" = M.""MenuId"" AND (""RoleId"" = {model.RoleId} OR ""RoleId"" IS NULL))
	                        WHERE m.""MainPage"" IS NOT NULL ";
            var records = await this.unitOfWork.Current.QueryAsync<Relationship.ViewModel>(query);
            return records;
        }

        public async Task<Access.ViewModel> GetMenuAccessAsync(Relationship.FilterModel model)
        {
            var query = $@"SELECT
	                        a.""MenuAccessId"",
	                        a.""RoleId"",
	                        a.""IsFullAccess"",
	                        a.""InitialRouteMenuId"",
	                        m.""Url"",
	                        m.""MainPage"",
	                        m.""SubPage""

                        FROM
	                        ""MenuAccess"" A 
	                        LEFT JOIN ""Menu"" m on m.""MenuId"" = a.""InitialRouteMenuId""
                        WHERE
	                        A.""RoleId"" = {model.RoleId} LIMIT 1";
            var records = await this.unitOfWork.Current.QuerySingleOrDefaultAsync<Access.ViewModel>(query);
            return records;
        }

        public async Task<int> AddButtonAsync(Button.InsertModel model)
        {

            var isExists = await this.unitOfWork.MenuButton.FindAsync(x => x.Name == model.Name);
            if (isExists != null) return -1;

            var record = new MenuButton
            {
                MenuId = model.MenuId,
                Code = model.UniqId,
                Name = model.Name
            };

            var response = await this.unitOfWork.MenuButton.InsertAsync(record);
            if (response > 0) return 1;

            return -1;
        }

        public async Task<IEnumerable<Relationship.RoleViewModel>> FetchRolesAsync()
        {
            var query = $@"SELECT
	                        ""RoleId"",
	                        ""RoleName""
                        FROM
	                        ""Role"" 
                        WHERE
	                        ""Active"" IS TRUE order by ""RoleName"" ";
            var records = await this.unitOfWork.Current.QueryAsync<Relationship.RoleViewModel>(query);
            return records;
        }

        public async Task<int> AddAsync(List<InsertModel> model)
        {
            var transaction = this.unitOfWork.BeginTransaction();
            var dbRecords = (await this.unitOfWork.Menu.FindAllAsync(transaction)).ToList();
            var allRecords = (dbRecords.Select(x => new CompareModel
            {
                MenuId = x.MenuId,
                Url = x.Url
            })).ToList();

            foreach (var record in model)
            {
                var existingRecord = allRecords.Find(x => x.Url == record.Url);
                if (existingRecord != null)
                {
                    existingRecord.IsPass = true;
                }

                var isExists = await this.unitOfWork.Menu.FindAsync(x => x.Url == record.Url, transaction);
                if (isExists != null) continue;

                var menu = new Menu
                {
                    Url = record.Url,
                    Count = record.Url.Split("/").Length
                };

                var response = await this.unitOfWork.Menu.InsertAsync(menu, transaction);
                if (response > 0) continue;

                transaction.Rollback();
                return -1;
            }

            foreach (var record in allRecords.Where(x => !x.IsPass))
            {
                var deleteResponse = await this.unitOfWork.Menu.DeleteAsync(x => x.MenuId == record.MenuId, transaction);
                if (deleteResponse) continue;

                transaction.Rollback();
                return -1;
            }

            transaction.Commit();
            return 1;
        }

        public async Task<int> AddOnlyAsync(InsertModel model)
        {

            var isExists = await this.unitOfWork.Menu.FindAsync(x => x.Url == model.Url);
            if (isExists != null) return -1;

            var menu = new Menu
            {
                Url = model.Url,
                Count = model.Url.Split("/").Length
            };

            var response = await this.unitOfWork.Menu.InsertAsync(menu);
            if (response > 0) return 1;

            return -1;
        }

        public async Task<int> RemoveOnlyAsync(InsertModel model)
        {

            var isExists = await this.unitOfWork.Menu.FindAsync(x => x.Url == model.Url);
            if (isExists == null) return -1;

            var response = await this.unitOfWork.Menu.DeleteAsync(x => x.MenuId == isExists.MenuId);
            if (response) return 1;

            return -1;
        }

        public async Task<int> UpdateAsync(UpdateModel model)
        {
            var record = await this.unitOfWork.Menu.FindAsync(x => x.MenuId == model.MenuId);
            record.GeneralClasses = model.GeneralClasses;
            record.IconClasses = model.IconClasses;
            record.MainPage = model.MainPage;
            record.SubPage = model.SubPage;
            record.MenuTypeId = model.MenuTypeId;
            record.Priority = model.Priority;
            record.Category = model.Category;

            var response = await this.unitOfWork.Menu.UpdateAsync(record);
            return response;
        }

        public async Task<int> UpdateRelationshipsAsync(Relationship.UpdateModel model)
        {
            var transaction = this.unitOfWork.BeginTransaction();
            var access = await this.unitOfWork.MenuAccess.FindAsync(x => x.RoleId == model.RoleId, transaction);

            if (access == null)
            {
                var accessData = new MenuAccess
                {
                    RoleId = model.RoleId,
                    IsFullAccess = model.IsFullAccess,
                    InitialRouteMenuId = model.InitialMenuId
                };
                var insertResponse = await this.unitOfWork.MenuAccess.InsertAsync(accessData, transaction);
                if (insertResponse <= 0)
                {
                    transaction.Rollback();
                    return -1;
                }
            }
            else
            {
                access.InitialRouteMenuId = model.InitialMenuId;
                access.IsFullAccess = model.IsFullAccess;

                var accessResponse = await this.unitOfWork.MenuAccess.UpdateAsync(access, transaction);
                if (accessResponse <= 0)
                {
                    transaction.Rollback();
                    return -1;
                }
            }

            if (model.IsFullAccess)
            {
                var relationships = await this.unitOfWork.MenuRelationship.FindAllAsync(x => x.RoleId == model.RoleId, transaction);
                var menuRelationships = relationships.ToList();
                if (menuRelationships.Any())
                {
                    foreach (var item in menuRelationships)
                    {
                        var deleteResponse =
                            await this.unitOfWork.MenuRelationship.DeleteAsync(
                                x => x.MenuRelationshipId == item.MenuRelationshipId, transaction);
                        if (deleteResponse) continue;

                        transaction.Rollback();
                        return -1;

                    }
                }

                var menuButtonRelationships = await this.unitOfWork.MenuButtonRelationship.FindAllAsync(x => x.RoleId == model.RoleId, transaction);
                var menuButtonShips = menuButtonRelationships.ToList();
                if (menuButtonShips.Any())
                {
                    foreach (var item in menuButtonShips)
                    {
                        var deleteResponse =
                            await this.unitOfWork.MenuButtonRelationship.DeleteAsync(
                                x => x.MenuButtonRelationshipId == item.MenuButtonRelationshipId, transaction);
                        if (deleteResponse) continue;

                        transaction.Rollback();
                        return -1;

                    }
                }

                transaction.Commit();
                return 1;
            }

            if (model.Removed.Any())
            {
                foreach (var id in model.Removed)
                {
                    var isExits = await this.unitOfWork.MenuRelationship.FindAsync(x =>
                        x.RoleId == model.RoleId &&
                        x.MenuId == id);

                    if (isExits == null) continue;

                    var removedResponse =
                        await this.unitOfWork.MenuRelationship.DeleteAsync(x => x.RoleId == model.RoleId && x.MenuId == id,
                            transaction);

                    if (removedResponse) continue;

                    transaction.Rollback();
                    return -1;
                }
            }

            if (model.Added.Any())
            {
                foreach (var id in model.Added)
                {
                    var isExits = await this.unitOfWork.MenuRelationship.FindAsync(x =>
                        x.RoleId == model.RoleId &&
                        x.MenuId == id);

                    if (isExits != null) continue;

                    var addedResponse =
                        await this.unitOfWork.MenuRelationship.InsertAsync(new MenuRelationship
                        {
                            MenuId = id,
                            RoleId = model.RoleId
                        }, transaction);

                    if (addedResponse > 0) continue;

                    transaction.Rollback();
                    return -1;
                }
            }

            // Menu Buttons

            if (model.MenuButtonsRemoved.Any())
            {
                foreach (var id in model.MenuButtonsRemoved)
                {
                    var isExits = await this.unitOfWork.MenuButtonRelationship.FindAsync(x =>
                        x.RoleId == model.RoleId &&
                        x.MenuButtonId == id);

                    if (isExits == null) continue;

                    var removedResponse =
                        await this.unitOfWork.MenuButtonRelationship.DeleteAsync(x => x.RoleId == model.RoleId && x.MenuButtonId == id,
                            transaction);

                    if (removedResponse) continue;

                    transaction.Rollback();
                    return -1;
                }
            }

            if (model.MenuButtonsAdded.Any())
            {
                foreach (var id in model.MenuButtonsAdded)
                {
                    var isExits = await this.unitOfWork.MenuButtonRelationship.FindAsync(x =>
                        x.RoleId == model.RoleId &&
                        x.MenuButtonId == id);

                    if (isExits != null) continue;

                    var addedResponse =
                        await this.unitOfWork.MenuButtonRelationship.InsertAsync(new MenuButtonRelationship
                        {
                            MenuButtonId = id,
                            RoleId = model.RoleId
                        }, transaction);

                    if (addedResponse > 0) continue;

                    transaction.Rollback();
                    return -1;
                }
            }

            transaction.Commit();
            return 1;
        }

        public async Task<string> FindNameByRoleId(int roleId)
        {
            var query = $@"SELECT ""RoleName"" FROM ""Role"" WHERE ""RoleId"" = {roleId}";
            var response = await this.unitOfWork.Current.QuerySingleAsync<string>(query);
            return response;
        }

    }
}