using static Fast.Models.Enums;

namespace Fast.Web.Controllers;

[Authorize]
[ApiController]
[Route("api/[controller]")]
public class PriceIndexController : ODataController
{
    private readonly AuthorizationHelper authHelper;
    private readonly MyDbContext db;
    public PriceIndexController(MyDbContext context)
    {
        db = context;
        authHelper = new AuthorizationHelper(Main.IsAuthenticationEnabled);
    }

    [Permission("Price Index", PermissionType.View)]
    [Route("/odata/GetPriceIndexItems")]
    public IActionResult GetPriceIndexItems(ODataQueryOptions<PriceIndexItem> queryOptions, bool isExport = false)
    {
        var itemsQueryable = GetPriceIndexItemsInternal();
        var items = (queryOptions.ApplyTo(itemsQueryable) as IEnumerable<PriceIndexItem>)?.ToList();

        if (isExport)
            return File(OpenXmlHelper.GetExportFileStream(items), "application/octet-stream");
        else
            return Ok(items);
    }

    private IQueryable<PriceIndexItem> GetPriceIndexItemsInternal()
    {
        //jlupo - I can get very close to accomplishing this without IndexOverviewInfo using the technique at the link below
        //https://stackoverflow.com/a/60008357/12975583
        //using this technique the query still works and is still efficient but the total "count" is wrong
        //E.G. when you have a record with 3 joined items and you choose to show 10 rows per page, only 8 rows show
        //even if you modified the count, "skip" would still not work correctly

        IQueryable<PriceIndexItem>? itemsQueryable = null;

        itemsQueryable = (
            from pit in db.MarketIndices
            join io in db.VwIndexOverviewInfos on pit.Id equals io.IndexId into j1
            from io in j1.DefaultIfEmpty()
            join pim in db.PublishedToInternalIndices on pit.Id equals pim.PublishedIndexId into j2
            from pim in j2.DefaultIfEmpty()
            join internalAlias in db.MarketIndexAliases on new { IndexId = pim.InternalIndexId, IndexAliasTypeId = 1 } equals new { internalAlias.IndexId, internalAlias.IndexAliasTypeId } into j3
            from internalAlias in j3.DefaultIfEmpty()
            join prod in db.Products on pit.ProductId equals prod.Id into j4
            from prod in j4.DefaultIfEmpty()
            join pict in db.MarketIndexClassificationTypes on pit.ClassificationTypeId equals pict.Id into j5
            from pict in j5.DefaultIfEmpty()
            select new PriceIndexItem
            {
                id = pit.Id,
                name = pit.Name ?? "",
                aliases = io.Aliases ?? "",
                indexType = pit.IndexTypeText,
                commodity = prod != null ? prod.Name ?? "" : "",
                classification = pict.Name ?? "",
                publication = pit.Publication != null ? pit.Publication!.Name : "",
                forwardCurve = internalAlias.IndexAlias ?? pim.InternalIndex.Name ?? ""
            }
        );

        return itemsQueryable;
    }

    [Permission("Price Index", PermissionType.View)]
    [Route("[action]")]
    public IActionResult GetRequiredData()
    {
        string bulletSymbol = "\u2022";
        var hasModifyPermission = authHelper.IsAuthorizedAsync(User, "Price Index", PermissionType.Modify).Result;

        var indexTypes = new List<IdName>() { new IdName(0, "Daily"), new IdName(1, "Monthly"), new IdName(2, "Hybrid") };
        var commodities = (from q in db.Products orderby q.Id select new IdName(q.Id, q.Category.Prefix + " - " + q.Name ?? "")).ToList();
        var classifications = (from q in db.MarketIndexClassificationTypes select new IdName(q.Id, q.Name ?? "")).ToList();
        var publications = (from q in db.MarketIndexPublications select new IdName(q.Id, q.Name)).ToList();
        var priceUnits = DataHelper.GetPriceUnits().Result;
        var indexIdNames = (from q in db.MarketIndices orderby q.Name select new IdName(q.Id, q.Name ?? "")).ToList();
        var aliasTypes = (from q in db.MarketIndexAliasTypes orderby q.Id select new IdName(q.Id, q.Name)).ToList();

        var functions = new List<HybridIndexFunction>
            {
                new() { definition = "Functions", textFiller = "" },
                new() { definition = "IF(condition,true_value,false_value)", textFiller = "IF(,,)" },
                new() { definition = "MAX(value1,value2)", textFiller = "MAX(,)" },
                new() { definition = "MIN(value1,value2)", textFiller = "MIN(,)" },
                new() { definition = "ROUND(value,digits)", textFiller = "ROUND(,)" }
            };

        var suffixes = new List<IndexSuffix>
            {
                new() { definition = "Suffixes", textFiller = "" },
                new() { definition = "CMTD (Calendar Month Trade Days)", textFiller = $"{bulletSymbol}CMTD" },
                new() { definition = "PMTD (Prompt Month Trade Days)", textFiller = $"{bulletSymbol}PMTD" },
                new() { definition = "2MTD (Second Month Trade Days)", textFiller = $"{bulletSymbol}2MTD" },
                new() { definition = "3MTD (Third Month Trade Days)", textFiller = $"{bulletSymbol}3MTD" },
                new() { definition = "AGTD (Argus Trade days)", textFiller = $"{bulletSymbol}AGTD" }
            };

        var variables = new List<HybridVariable>
            {
                new() { definition = "Variables", textFiller = "" },
                new() { definition = "BD1 (Business Days in Prompt Month)", textFiller = "[BD1]" },
                new() { definition = "BD2 (Business Days in Second Month)", textFiller = "[BD2]" },
                new() { definition = "BDT (Business Days Total)", textFiller = "[BDT]" }
            };

        var result = new { hasModifyPermission, indexTypes, commodities, classifications, publications, priceUnits, indexIdNames, aliasTypes, functions, suffixes, variables };

        return Ok(result);
    }

    [Permission("Price Index", PermissionType.View)]
    [Route("[action]/{indexId}")]
    public async Task<IActionResult> GetPriceIndexDetail(int indexId)
    {
        var isHybrid = await db.MarketIndices.Where(x => x.Id == indexId && x.IndexTypeId == (int)Enums.MarketIndexType.Hybrid).AnyAsync();
        HybridIndexHelper? hybridHelper = isHybrid ? new(db) : null;

        var result = (
            from q in db.MarketIndices
            join p in db.PublishedToInternalIndices on q.Id equals p.PublishedIndexId into g
            from p in g.DefaultIfEmpty()
            where q.Id == indexId
            let isMonthly = q.IndexTypeId == (int)Enums.MarketIndexType.Monthly
            select new PriceIndexDetailItem
            {
                id = q.Id,
                name = q.Name ?? "",
                aliases = q.MarketIndexAliases.Select(x => new IndexAlias { id = x.Id, aliasName = x.IndexAlias, aliasTypeId = x.IndexAliasTypeId }).ToList(),
                indexType = isHybrid ? PriceIndexDetailType.Hybrid : isMonthly ? PriceIndexDetailType.Monthly : PriceIndexDetailType.Daily,
                commodityId = q.ProductId,
                classificationTypeId = q.ClassificationTypeId,
                publicationId = q.PublicationId,
                description = q.PriceDetermination ?? "",
                notes = q.Notes ?? "",
                hybridFormula = hybridHelper != null ? hybridHelper.GetFormulaWithNames(q.HybridIndexDefinition ?? "") : "",
                unitPriceId = q.UnitPriceId,
                forwardCurveId = p.InternalIndexId
            }
        ).FirstOrDefault();

        return Ok(result);
    }

    [Permission("Price Index", PermissionType.View)]
    [Route("[action]")]
    public IActionResult TestHybridFormula([FromBody] string formula)
    {
        HybridIndexHelper hybridHelper = new(db);
        HybridFormulaResult result = hybridHelper.TestFormula(formula);

        return Ok(result);
    }

    [Permission("Price Index", PermissionType.Modify)]
    [Route("[action]")]
    public async Task<IActionResult> SavePriceIndex(PriceIndexDetailItem detail, SaveType saveType)
    {
        HybridFormulaResult hybridTestResult = new HybridFormulaResult() { IsValid = true };

        await db.Database.CreateExecutionStrategy().Execute(async () =>
        {
            await using var dbContextTransaction = await db.Database.BeginTransactionAsync();
            MarketIndex? dbItem = null;
            if (saveType != SaveType.New)
            {
                dbItem = await (
                    from q in db.MarketIndices
                        .Include(x => x.PublishedToInternalIndexPublishedIndex)
                        .Include(x => x.MarketIndexAliases)
                    where q.Id == detail.id
                    select q
                ).FirstOrDefaultAsync();
            }

            if (dbItem == null) //if the item does not exist then add it
            {
                dbItem = new();
                dbItem.IsFutureDefault = false;
                db.MarketIndices.Add(dbItem);
            }
            else
            {
                //remove existing items so that they get completely re-inserted
                if (dbItem.PublishedToInternalIndexPublishedIndex != null)
                    db.PublishedToInternalIndices.RemoveRange(dbItem.PublishedToInternalIndexPublishedIndex);
                db.MarketIndexAliases.RemoveRange(dbItem.MarketIndexAliases);
            }

            var d = detail;

            dbItem.Name = d.name;
            if (d.indexType == PriceIndexDetailType.Hybrid)
            {
                HybridIndexHelper hybridHelper = new(db);

                hybridTestResult = hybridHelper.TestFormula(d.hybridFormula);
                if (!hybridTestResult.IsValid)
                    return; //cancel saving and return invalid result

                dbItem.IndexTypeId = (int)Enums.MarketIndexType.Hybrid;
                dbItem.HybridIndexDefinition = hybridHelper.GetFormulaWithIds(d.hybridFormula);
                dbItem.UnitPriceId = null;
                dbItem.ProductId = d.commodityId.GetValueOrDefault();
            }
            else //daily or monthly index
            {
                dbItem.IndexTypeId = d.indexType == PriceIndexDetailType.Daily ? (int)Enums.MarketIndexType.Daily : (int)Enums.MarketIndexType.Monthly;
                dbItem.HybridIndexDefinition = null;
                dbItem.UnitPriceId = d.unitPriceId;
                dbItem.ProductId = d.commodityId.GetValueOrDefault();

                if (d.forwardCurveId.HasValue)
                {
                    var mapping = new PublishedToInternalIndex { PublishedIndexId = d.id, InternalIndexId = d.forwardCurveId.Value };
                    dbItem.PublishedToInternalIndexPublishedIndex = mapping;
                }
            }
            dbItem.ClassificationTypeId = d.classificationTypeId;
            dbItem.PublicationId = d.publicationId;
            dbItem.PriceDetermination = d.description;
            dbItem.Notes = d.notes;

            var newAliases = d.aliases.Select(x => new Database.Models.MarketIndexAlias { IndexAlias = x.aliasName, IndexAliasTypeId = x.aliasTypeId }).ToList();
            dbItem.MarketIndexAliases = newAliases;

            await db.SaveChangesAsync();
            hybridTestResult.HybridIndexId = dbItem.Id;

            await dbContextTransaction.CommitAsync();
        });

        return Ok(hybridTestResult);
    }

    [Permission("Price Index", PermissionType.Modify)]
    [Route("[action]/{id}")]
    public IActionResult DeletePriceIndex(int id)
    {
        var item = db.MarketIndices.Where(x => x.Id == id).First();
        db.Entry(item).State = EntityState.Deleted;

        db.SaveChanges();

        return Ok();
    }
}
