using System.Text.Json;
using static Fast.Logic.Util.String;
using SosItem = Fast.Models.Crude.SosItem;
using SosTransferItem = Fast.Models.Crude.SosTransferItem;

namespace Fast.Web.Controllers;

[Authorize]
[ApiController]
[Route("api/[controller]")]
public class SosCrudeSettingController : ODataController
{
    private readonly MyDbContext db;

    public class PipeAndDefaultSettings
    {
        public PipeAndDefaultSettings(SosSettingItem pipeSettings, SosSettingItem defaultSettings)
        {
            PipeSettings = pipeSettings;
            DefaultSettings = defaultSettings;
        }

        public SosSettingItem PipeSettings { get; set; } = new();
        public SosSettingItem DefaultSettings { get; set; } = new();
    }

    public SosCrudeSettingController(MyDbContext context)
    {
        db = context;
    }

    public async Task<PipeAndDefaultSettings> GetSettingsTyped(System.Security.Claims.ClaimsPrincipal user, int pipeId, bool forDealScreen = false)
    {
        var userId = Util.GetAppUserId(user);
        var pipeSettings = await GetSettingsInternal(userId, pipeId, forDealScreen);
        var defaultSettings = await GetSettingsInternal(userId, null, forDealScreen);
        var result = new PipeAndDefaultSettings(pipeSettings, defaultSettings);
        return result;
    }

    [Permission("SOS Crude Nomination", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> GetSettings(int pipeId)
    {
        var result = await GetSettingsTyped(User, pipeId);
        return Ok(result);
    }

    public async static Task<SosSettingItem> GetSettingsInternal(int userId, int? pipeId, bool forDealScreen = false)
    {
        var db = Main.CreateContext();
        var dbPipeSetting = await GetPipeSettings(db, userId, pipeId, false);
        var settingItem = new SosSettingItem();
        settingItem.PipeId = pipeId;
        settingItem.FontSize = dbPipeSetting.FontSize;
        settingItem.MainFrozenColumnCount = dbPipeSetting.MainFrozenColumnCount;
        settingItem.TransferFrozenColumnCount = dbPipeSetting.TransferFrozenColumnCount;
        settingItem.Sorts = JsonSerializer.Deserialize<SosSortDescriptor[]>(dbPipeSetting.Sorts ?? "[]")?.ToList() ?? new List<SosSortDescriptor>();

        var mainDisplayInfosByPropName = GetDisplayInfos<SosItem>(true).ToDictionary(x => x.PropName);
        var transferDisplayInfosByPropName = GetDisplayInfos<SosTransferItem>(true).ToDictionary(x => x.PropName);

        var mainDisplayInfosOrdered = new List<DisplayInfo>();
        foreach (var column in dbPipeSetting.MainColumns.Split(","))
        {
            var columnSplit = column.Split(":");
            if (mainDisplayInfosByPropName.TryGetValue(columnSplit[0], out var displayInfo))
            {
                displayInfo.Width = int.Parse(columnSplit[1]);
                displayInfo.IsVisible = columnSplit[2] == "1";
                mainDisplayInfosOrdered.Add(displayInfo);
            }
        }

        var transferDisplayInfosOrdered = new List<DisplayInfo>();
        foreach (var column in dbPipeSetting.TransferColumns.Split(","))
        {
            var columnSplit = column.Split(":");
            if (transferDisplayInfosByPropName.TryGetValue(columnSplit[0], out var displayInfo))
            {
                displayInfo.Width = int.Parse(columnSplit[1]);
                displayInfo.IsVisible = columnSplit[2] == "1";
                transferDisplayInfosOrdered.Add(displayInfo);
            }
        }

        settingItem.MainDisplayInfos = mainDisplayInfosOrdered;
        settingItem.TransferDisplayInfos = transferDisplayInfosOrdered;

        if (forDealScreen)
        {
            settingItem.TransferDisplayInfos = FilterTransferDisplayInfosForDeal(settingItem.TransferDisplayInfos);
        }

        return settingItem;
    }

    /// <summary>
    /// removes columns that are not needed when displaying from the deal screen
    /// </summary>
    private static List<DisplayInfo> FilterTransferDisplayInfosForDeal(List<DisplayInfo> transferDisplayInfos)
    {
        var excludedDisplayNames = new HashSet<string>
            {
                "PTR Contract",
                "PTR Delivery Meter",
                "PTR %",
                "PTR Amount",
                "PTR Fuel %",
                "PTR Fuel Amount",
                "Total Receipt Volume",
                "Nom Receipt Volume",
                "Nom Fuel %",
                "Nom Fuel Amount",
                "Deal Volume",
            };

        var value = transferDisplayInfos
            .Where(x => !excludedDisplayNames.Contains(x.DisplayName))
            .ToList();

        return value;
    }

    [Permission("SOS Crude Nomination", PermissionType.View)]
    [Route("[action]")]
    public IActionResult GetAllColumnDisplayInfos()
    {
        List<DisplayInfo> allMainColumnDisplayInfos = GetDisplayInfos<SosItem>(true).ToList();
        List<DisplayInfo> allTransferColumnDisplayInfos = GetDisplayInfos<SosTransferItem>(true).ToList();
        return Ok(new { allMainColumnDisplayInfos, allTransferColumnDisplayInfos });
    }

    [Permission("SOS Crude Nomination", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> SaveSettings(PipeAndDefaultSettings settings, int savePipeId)
    {
        var userId = Util.GetAppUserId(User);

        var pipeSetting = await GetPipeSettings(db, userId, savePipeId, true);
        pipeSetting.UserId = userId;
        pipeSetting.PipeId = savePipeId;
        pipeSetting.FontSize = settings.PipeSettings.FontSize;
        pipeSetting.MainFrozenColumnCount = settings.PipeSettings.MainFrozenColumnCount;
        pipeSetting.TransferFrozenColumnCount = settings.PipeSettings.TransferFrozenColumnCount;
        pipeSetting.MainColumns = string.Join(",", settings.PipeSettings.MainDisplayInfos.Select(x => $"{x.PropName}:{x.Width}:{(x.IsVisible ? "1" : "0")}"));
        pipeSetting.TransferColumns = string.Join(",", settings.PipeSettings.TransferDisplayInfos.Select(x => $"{x.PropName}:{x.Width}:{(x.IsVisible ? "1" : "0")}"));
        await db.SaveChangesAsync();

        var defaultSetting = await GetPipeSettings(db, userId, null, true);
        defaultSetting.UserId = userId;
        defaultSetting.PipeId = null;
        defaultSetting.FontSize = settings.DefaultSettings.FontSize;
        defaultSetting.MainFrozenColumnCount = settings.DefaultSettings.MainFrozenColumnCount;
        defaultSetting.TransferFrozenColumnCount = settings.DefaultSettings.TransferFrozenColumnCount;
        defaultSetting.MainColumns = string.Join(",", settings.DefaultSettings.MainDisplayInfos.Select(x => $"{x.PropName}:{x.Width}:{(x.IsVisible ? "1" : "0")}"));
        defaultSetting.TransferColumns = string.Join(",", settings.DefaultSettings.TransferDisplayInfos.Select(x => $"{x.PropName}:{x.Width}:{(x.IsVisible ? "1" : "0")}"));
        await db.SaveChangesAsync();

        //return the saved settings which will be pipe-specific settings if they exist
        var result = await GetSettingsInternal(userId, savePipeId);

        return Ok(result);
    }

    [Permission("SOS Crude Nomination", PermissionType.View)]
    [Route("[action]")]
    public async Task<ActionResult> SaveSelectedPipe(int pipeId)
    {
        var userId = Util.GetAppUserId(User);
        var setting = await db.SosCrudeSettings.Where(x => x.UserId == userId).FirstOrDefaultAsync();
        if (setting == null)
        {
            setting = new SosCrudeSetting();
            db.SosCrudeSettings.Add(setting);
        }

        setting.UserId = userId;
        setting.AutoSelectPipelineId = pipeId;
        await db.SaveChangesAsync();

        return Ok();
    }

    [Permission("SOS Crude Nomination", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> SaveSort(List<SosSortDescriptor> sorts, int pipeId)
    {
        var userId = Util.GetAppUserId(User);
        var pipeSetting = await GetPipeSettings(db, userId, pipeId, true);

        string jsonString = JsonSerializer.Serialize(sorts);
        pipeSetting.Sorts = jsonString;
        await db.SaveChangesAsync();
        return Ok();
    }

    private static SosCrudePipeSetting GetDefaultPipeSetting(int? pipeId, int userId)
    {
        var setting = new SosCrudePipeSetting();
        setting.PipeId = pipeId;
        setting.FontSize = 14;
        setting.MainFrozenColumnCount = 1;
        setting.TransferFrozenColumnCount = 1;
        setting.UserId = userId;

        var mainDisplayInfosByPropName = GetDisplayInfos<SosItem>(true).ToDictionary(x => x.PropName);
        var mainCols = new List<string>();
        foreach (var displayInfo in mainDisplayInfosByPropName.Values)
            mainCols.Add($"{displayInfo.PropName}:{displayInfo.Width}:1");
        setting.MainColumns = string.Join(",", mainCols);

        var transferDisplayInfosByPropName = GetDisplayInfos<SosTransferItem>(true).ToDictionary(x => x.PropName);
        var transferCols = new List<string>();
        foreach (var displayInfo in transferDisplayInfosByPropName.Values)
            transferCols.Add($"{displayInfo.PropName}:{displayInfo.Width}:1");
        setting.TransferColumns = string.Join(",", transferCols);

        return setting;
    }

    private async static Task<SosCrudePipeSetting> GetPipeSettings(MyDbContext db, int userId, int? pipeId, bool createIfNotFound)
    {
        var pipeSetting = await (
            from q in db.SosCrudePipeSettings
            where q.UserId == userId && (q.PipeId == pipeId || q.PipeId == null)
            orderby q.PipeId ?? 0 descending
            select q
        ).FirstOrDefaultAsync() ?? GetDefaultPipeSetting(pipeId, userId);

        //if the only saved setting we found was for "all pipelines" but we're trying to get settings for a specific pipe then we need to create a new pipe-specifc setting with same values copied into it
        if (pipeSetting.PipeId == null && pipeId != null)
        {
            var newPipeSetting = new SosCrudePipeSetting();
            newPipeSetting.PipeId = pipeId;
            newPipeSetting.UserId = userId;
            newPipeSetting.FontSize = pipeSetting.FontSize;
            newPipeSetting.MainFrozenColumnCount = pipeSetting.MainFrozenColumnCount;
            newPipeSetting.MainColumns = pipeSetting.MainColumns;
            newPipeSetting.TransferFrozenColumnCount = pipeSetting.TransferFrozenColumnCount;
            newPipeSetting.TransferColumns = pipeSetting.TransferColumns;
            newPipeSetting.Sorts = pipeSetting.Sorts;
            pipeSetting = newPipeSetting;
        }

        if (createIfNotFound && pipeSetting.Id == 0)
        {
            db.SosCrudePipeSettings.Add(pipeSetting);
            await db.SaveChangesAsync();
        }

        return pipeSetting;
    }
}
