using System.Security.Claims;

namespace Fast.Shared.Controllers;

[Authorize]
[ApiController]
[Route("api/[controller]")]
public class CommonController(IConfiguration configuration) : ControllerBase
{
    public IConfiguration Configuration { get; } = configuration;

    public class ScreenItem
    {
        public int Id;
        public string Name = "";
        public string Keywords = "";
        public string Path = "";
        public bool IsVisible;
        public bool UserHasPermission;
    }

    public class ModuleMenu
    {
        public int Id;
        public string Name = "";
        public string Path = "";
        public bool IsNew;
        public List<ModuleMenu> Children = new();
    }

    public class ServerSettings
    {
        public string AppName = "";
        public string ModuleName = "";
        public string CompanyHeaderStr = "";
        public bool IsAuthenticationEnabled = true;
    }

    public class CommonData
    {
        public string HomeUrl = "";
        public AppUser UserInfo = new();
        public List<ScreenItem> ScreenItems = new();
        public List<ModuleMenu> ModuleMenus = new();
        public List<ScreenItem> Favorites = new();
    }

    [AllowAnonymous]
    [Route("[action]")]
    public IActionResult GetServerSettings()
    {
        ServerSettings serverSettings = new();
        serverSettings.AppName = FastValues.AppName;
        serverSettings.ModuleName = FastValues.ModuleName;
        serverSettings.CompanyHeaderStr = FastValues.CompanyHeaderStr;
        serverSettings.IsAuthenticationEnabled = Main.IsAuthenticationEnabled;

        // Azure AD Settings
        var db = Main.CreateContext();
        var appSettings = db.AppSettings.AsNoTracking().ToList();
        string? GetValue(string name) => appSettings.FirstOrDefault(x => x.Name == name)?.Value;

        var isTestEntra = GetValue("AzureAd-TenantId") != null;
        var tenantId = GetValue("AzureAd-TenantId") ?? GetValue("Graph-TenantID") ?? string.Empty;
        var clientId = GetValue("AzureAd-ClientId") ?? GetValue("Graph-ClientID") ?? string.Empty;
        var apiApplicationIdUri = isTestEntra ? clientId : $"api://{clientId}";

        return Ok(serverSettings);
    }

    [Route("[action]")]
    public async Task<IActionResult> GetCommonData()
    {
        CommonData commonData = new();
        var tasks = new List<Task>
        {
            Task.Run(async () => { commonData.HomeUrl = await GetHomeUrl(); }),
            Task.Run(async () => { commonData.UserInfo = await GetAppUserAsync(); }),
            Task.Run(async () => { commonData.ModuleMenus = await GetModulesMenusAsync(User); }),
            Task.Run(async () => { commonData.ScreenItems = await GetScreenItemsAsync(User); }),
            Task.Run(async () => { commonData.Favorites = await GetFavoritesAsync(User); })
        };
        await Task.WhenAll(tasks);

        return Ok(commonData);
    }

    [Route("[action]")]
    [AllowAnonymous]
    public async Task<IActionResult> GetThemeName()
    {
        using var db = Main.CreateContext();
        var appUserId = Util.GetAppUserId(User);
        var darkThemeNames = new List<string> { "dark", "t2", "t3" };
        string themeName = (await db.AppUsers.Where(x => x.Id == appUserId).Select(x => x.Theme).FirstOrDefaultAsync()) ?? "light";
        themeName = darkThemeNames.Contains(themeName) ? "dark" : "light";
        return Ok(themeName);
    }

    private async Task<string> GetHomeUrl()
    {
        using var db = Main.CreateContext();
        var dashScreen = (await Main.ScreenCache.GetScreens(User)).FirstOrDefault(x => x.Description == "Dashboard");
        string homeUrl = dashScreen?.Path ?? "";
        return homeUrl;
    }

    private static async Task<List<ScreenItem>> GetScreenItemsAsync(ClaimsPrincipal user)
    {
        var dbScreens = await Main.ScreenCache.GetScreens(user);
        var screenItems = (
            from s in dbScreens
            select new ScreenItem
            {
                Id = s.Id,
                Name = s.Name ?? "",
                Keywords = s.Keywords ?? "",
                Path = s.Path ?? "",
                IsVisible = s.IsVisible,
                UserHasPermission = s.UserHasPermission
            }).ToList();

        return screenItems;
    }

    private static async Task<List<ModuleMenu>> GetModulesMenusAsync(ClaimsPrincipal user)
    {
        using var db = Main.CreateContext();

        var dbScreens = (await Main.ScreenCache.GetScreens(user)).Where(x => x.UserHasPermission).ToList();
        var mainScreenItems = (
            from s in dbScreens
            where (s.IsVisible == false) && (s.Description != null && s.Description.Contains(" Module"))
            orderby s.Name
            select new { s.Id, s.Name, s.Path, s.ParentId }
        ).ToList();

        var subScreenItems = (
            from s in dbScreens
            where s.IsVisible == true && s.ParentId.HasValue
            orderby s.Name
            select new { s.Id, s.Name, s.Path, s.ParentId }
        ).ToList().ToLookup(x => x.ParentId);

        var modulesMenus = new List<ModuleMenu>();

        foreach (var mainScreenItem in mainScreenItems)
        {
            var mainMenuItem = new ModuleMenu
            {
                Id = mainScreenItem.Id,
                Name = mainScreenItem.Name,
                Children = new List<ModuleMenu>()
            };
            foreach (var subScreenItem in subScreenItems[mainScreenItem.Id])
            {
                var subMenuItem = new ModuleMenu
                {
                    Id = subScreenItem.Id,
                    Name = subScreenItem.Name,
                    Path = subScreenItem.Path
                };
                mainMenuItem.Children.Add(subMenuItem);
            }
            modulesMenus.Add(mainMenuItem);
        }

        return modulesMenus;
    }

    private async Task<AppUser> GetAppUserAsync()
    {
        var result = await Util.GetAppUserAsync(User);
        return result;
    }

    private static async Task<List<ScreenItem>> GetFavoritesAsync(ClaimsPrincipal user)
    {
        using var db = Main.CreateContext();
        List<ScreenItem> result = new();
        var appUserId = Util.GetAppUserId(user);
        var faves = await db.Favorites.Where(x => x.UserId == appUserId).AsNoTracking().FirstOrDefaultAsync();

        if (faves != null)
        {
            List<int> screenIds = Util.String.ConvertCommaSeparatedIdsToList(faves.ScreenIds);

            var dbScreens = await Main.ScreenCache.GetScreens(user);
            var screensDic = (
                from s in dbScreens
                where s.IsVisible == true && s.Path != null && screenIds.Contains(s.Id)
                select new ScreenItem
                {
                    Id = s.Id,
                    Name = s.Name ?? "",
                    Keywords = s.Keywords ?? "",
                    Path = s.Path ?? "",
                    IsVisible = s.IsVisible,
                    UserHasPermission = s.UserHasPermission
                }
            ).ToDictionary(x => x.Id);

            //loop and add individually so that favorites are ordered correctly
            foreach (int screenId in screenIds)
            {
                if (screensDic.ContainsKey(screenId))
                {
                    var screen = screensDic[screenId];
                    screen.Name = GetCorrectedScreenItemName(screen);
                    result.Add(screen);
                }
            }
        }

        return result;
    }

    [Route("[action]")]
    public async Task<IActionResult> SaveFavorites(List<ScreenItem> favorites)
    {
        using var db = Main.CreateContext();
        var appUserId = Util.GetAppUserId(User);
        var userFaves = await db.Favorites.Where(x => x.UserId == appUserId).FirstOrDefaultAsync();
        if (userFaves != null)
            db.Favorites.Remove(userFaves);

        if (favorites != null && favorites.Any())
        {
            var newUserFaves = new Favorite() { UserId = appUserId };
            string screenIds = string.Join(",", favorites.Select(x => x.Id).ToList());
            newUserFaves.ScreenIds = screenIds;
            db.Favorites.Add(newUserFaves);
        }
        await db.SaveChangesAsync();

        return Ok();
    }

    [Route("[action]")]
    public async Task<IActionResult> AddFavorite([FromBody] string windowUrl)
    {
        using var db = Main.CreateContext();
        var uri = new Uri(windowUrl);
        var hashWithParams = uri.Fragment;
        string hashOnly = uri.Fragment.Contains('?') ? uri.Fragment[..uri.Fragment.IndexOf("?")] : uri.Fragment;

        var dbScreens = await Main.ScreenCache.GetScreens(User);
        var screenItems = (
            from s in dbScreens
            let fullMatch = s.Path != null && s.Path == windowUrl
            let containsMatch = s.Path != null && s.Path.Contains(windowUrl)
            let hashWithParamsMatch = s.Path != null && s.Path.Contains(hashWithParams)
            let hashOnlyMatch = s.Path != null && s.Path.Contains(hashOnly)
            where s.IsVisible == true && (fullMatch || containsMatch || hashWithParamsMatch || hashOnlyMatch)
            orderby s.Path ascending // similar paths with shorter lengths need to come first (.i.e. {#/Dash} before {#/Dashboard?module=admin})
            orderby fullMatch descending, containsMatch descending, hashWithParamsMatch descending, hashOnlyMatch descending
            select new ScreenItem
            {
                Id = s.Id,
                Name = s.Name ?? "",
                Keywords = s.Keywords ?? "",
                Path = s.Path ?? "",
                IsVisible = s.IsVisible,
                UserHasPermission = s.UserHasPermission
            }
        ).FirstOrDefault();

        if (screenItems != null)
        {
            screenItems.Name = GetCorrectedScreenItemName(screenItems);
            var appUserId = Util.GetAppUserId(User);
            Favorite? userFaves = await db.Favorites.Where(x => x.UserId == appUserId).FirstOrDefaultAsync();
            if (userFaves == null)
            {
                userFaves = new Favorite() { UserId = appUserId };
                db.Favorites.Add(userFaves);
            }

            List<int> screenIds = Util.String.ConvertCommaSeparatedIdsToList(userFaves.ScreenIds);
            if (!screenIds.Contains(screenItems.Id))
            {
                screenIds.Add(screenItems.Id);
                userFaves.ScreenIds = string.Join(",", screenIds);
            }
            else
            {
                //return null if the found item is already saved
                screenItems = null;
            }
            await db.SaveChangesAsync();
        }

        return Ok(screenItems);
    }

    [Route("[action]")]
    public async Task<IActionResult> SaveTheme([FromBody] string themeName)
    {
        using var db = Main.CreateContext();
        var appUserId = Util.GetAppUserId(User);
        var user = await db.AppUsers.Where(x => x.Id == appUserId).FirstOrDefaultAsync();
        if (user != null)
        {
            user.Theme = themeName;
            await db.SaveChangesAsync();
        }

        return Ok();
    }

    private static string GetCorrectedScreenItemName(ScreenItem screenItem)
    {
        var newName = screenItem.Name;
        if (screenItem.Path.Contains("Dash?module"))
            newName = $"Dash {newName}";
        return newName;
    }
}
