namespace Fast.Web.Controllers;

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

    public PathingController(MyDbContext db)
    {
        this.db = db;
        authHelper = new AuthorizationHelper(Main.IsAuthenticationEnabled);
    }

    [Permission("SOS Pathing", PermissionType.View)]
    [Route("/odata/GetPathingItems")]
    public async Task<IActionResult> GetItems(ODataQueryOptions<PathListItem> queryOptions, bool isExport)
    {
        var itemsQueryable = GetItemsInternal();
        itemsQueryable = queryOptions.ApplyTo(itemsQueryable) as IQueryable<PathListItem>;
        var items = itemsQueryable == null ? null : await itemsQueryable.ToListAsync();

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

    private IQueryable<PathListItem> GetItemsInternal()
    {
        IQueryable<PathListItem>? itemsQueryable = null;

        itemsQueryable = (
            from q in db.GasPaths
            join oi in db.VwGasPathingOverviewInfos on q.Id equals oi.PathId into j1
            from oi in j1.DefaultIfEmpty()
            select new PathListItem
            {
                PathId = q.Id,
                PathName = q.Name,
                ContractNums = oi.ContractNums ?? "",
                ContractOwners = oi.ContractOwners ?? "",
                SourceCounterparty = q.SourceCounterparty == null ? "" : q.SourceCounterparty.Name,
                SourceMeter = q.SourceMeter == null ? "" : q.SourceMeter.Name
            }
        );

        return itemsQueryable;
    }

    [Permission("SOS Pathing", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> GetRequiredData()
    {
        var hasModifyPermission = await authHelper.IsAuthorizedAsync(User, "SOS Pathing", PermissionType.Modify);
        var contracts = await GetContractsAsync();
        var counterparties = (await DataHelper.GetCounterpartiesAsync(false, Enums.ProductCategory.NaturalGasAndLng)).Select(x => new IdName(x.EntityId, x.FullName)).ToList();
        var pipelines = (await DataHelper.GetPipelinesAsync(false)).Select(x => new IdName(x.PipeId, x.PipeName)).ToList();
        var meters = await DataHelper.GetMetersByProductAsync(Enums.ProductCategory.NaturalGasAndLng);

        var result = new { hasModifyPermission, contracts, counterparties, pipelines, meters };
        return Ok(result);
    }

    public async Task<List<PathContract>> GetContractsAsync()
    {
        var cbd = DateOnly.FromDateTime(DateTime.Now.Date);

        var activeContracts = await (
            from c in db.PipelineContracts
            where (!c.EndDate.HasValue || c.EndDate > cbd)
            && c.ProductId == (int)Enums.Product.NaturalGas
            orderby c.ContractId
            select new PathContract
            {
                ContractId = c.Id,
                ContractNum = c.ContractId,
                ContractOwner = c.ContractOwner == null ? "" : c.ContractOwner.Name,
                ContractDesc = c.ContractOwner == null ? c.ContractId : c.ContractId + " - " + c.ContractOwner.Name,
                ContractCounterpartyId = c.CounterpartyId,
                ContractPipeId = c.PipelineId
            }).ToListAsync();

        var inactiveContracts = await (
            from c in db.PipelineContracts
            where (c.EndDate <= cbd)
            && c.ProductId == (int)Enums.Product.NaturalGas
            orderby c.ContractId
            select new PathContract
            {
                ContractId = c.Id,
                ContractNum = c.ContractId,
                ContractOwner = c.ContractOwner == null ? "" : c.ContractOwner.Name,
                ContractDesc = c.ContractOwner == null ? "{Inactive} " + c.ContractId : "{Inactive} " + c.ContractId + " - " + c.ContractOwner.Name,
                ContractCounterpartyId = c.CounterpartyId,
                ContractPipeId = c.PipelineId
            }).ToListAsync();

        var result = activeContracts.Concat(inactiveContracts).ToList();
        return result;
    }

    [Permission("SOS Pathing", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> GetDetail(int id)
    {
        PathDetail? result = null;

        var productId = (int)Enums.Product.NaturalGas;
        var asOfDate = DateOnly.FromDateTime(DateTime.Now.Date);

        var path = await (
            from p in db.GasPaths
            where p.Id == id
            orderby p.Name
            let sourceMeterProduct = p.SourceMeter.MeterProducts
                .Where(x => x.ProductId == productId && x.EffectiveDate <= asOfDate).OrderByDescending(x => x.EffectiveDate).FirstOrDefault()
            let sourceZone = sourceMeterProduct == null ? null : sourceMeterProduct.SourceZone
            let sourcePipe = sourceZone == null ? null : sourceZone.Pipeline
            select new
            {
                pathId = p.Id,
                pathName = p.Name,
                sourceCounterpartyId = p.SourceCounterpartyId,
                sourceMeterId = p.SourceMeterId,
                sourceMeterName = p.SourceMeter == null ? "" : p.SourceMeter.Name,
                sourcePipeId = sourcePipe == null ? 0 : (int?)sourcePipe.Id,
                sourcePipeName = sourcePipe == null ? "" : sourcePipe.Name,
                sourceContractId = p.SourcePipeContractId,
                sourceContractNum = p.SourcePipeContract == null ? "" : p.SourcePipeContract.ContractId,
                sourceContractOwner = p.SourcePipeContract == null || p.SourcePipeContract.ContractOwner == null ? "" : p.SourcePipeContract.ContractOwner!.Name,
                pathRoutes = (
                    from pr in p.GasPathRoutes
                    let mapMeter1 = pr.MeterMap != null ? pr.MeterMap.Meter1 : null
                    let mapMeter2 = pr.MeterMap != null ? pr.MeterMap.Meter2 : null
                    let toContractOwner = pr.ToPipeContract == null || pr.ToPipeContract.ContractOwner == null ? "" : pr.ToPipeContract.ContractOwner.Name
                    let toContractNum = pr.ToPipeContract == null ? "" : pr.ToPipeContract.ContractId
                    let isMeter1First = pr.IsMeter1First ?? false
                    let mapMeterProduct1 = mapMeter1.MeterProducts
                        .Where(x => x.ProductId == productId && x.EffectiveDate <= asOfDate).OrderByDescending(x => x.EffectiveDate).FirstOrDefault()
                    let mapMeterProduct2 = mapMeter2.MeterProducts
                        .Where(x => x.ProductId == productId && x.EffectiveDate <= asOfDate).OrderByDescending(x => x.EffectiveDate).FirstOrDefault()
                    let mapZone1 = mapMeterProduct1 != null ? mapMeterProduct1.SourceZone : null
                    let mapZone2 = mapMeterProduct2 != null ? mapMeterProduct2.SourceZone : null
                    let mapPipe1 = mapZone1 != null ? mapZone1.Pipeline : null
                    let mapPipe2 = mapZone2 != null ? mapZone2.Pipeline : null
                    orderby pr.OrderId
                    select new
                    {
                        fromPipeId = isMeter1First ? mapPipe1.Id : mapPipe2.Id,
                        fromPipeName = isMeter1First ? mapPipe1.Name : mapPipe2.Name,
                        fromMeterId = isMeter1First ? mapMeter1.Id : mapMeter2.Id,
                        fromMeterName = isMeter1First ? mapMeter1.Name : mapMeter2.Name,
                        toPipeId = isMeter1First ? mapPipe2.Id : mapPipe1.Id,
                        toPipeName = isMeter1First ? mapPipe2.Name : mapPipe1.Name,
                        toMeterId = isMeter1First ? mapMeter2.Id : mapMeter1.Id,
                        toMeterName = isMeter1First ? mapMeter2.Name : mapMeter1.Name,
                        toContractId = pr.ToPipeContractId ?? 0,
                        toContractNum,
                        toContractOwner,
                        meterMapId = pr.MeterMapId,
                        isMeter1First
                    }
                )
            }
        ).AsSingleQuery().FirstOrDefaultAsync();

        var routes = new List<PathRouteItem>();

        int? prevContractId;
        string prevContractNum;
        string prevContractOwner;

        if (path != null)
        {
            if (path.pathRoutes.Any())
                routes.Add(new PathRouteItem { PipeId = path.sourcePipeId, PipeName = path.sourcePipeName, MeterName = path.sourceMeterName, ContractId = path.sourceContractId, ContractNum = path.sourceContractNum, ContractOwner = path.sourceContractOwner, MeterMapId = 0 });

            prevContractId = path.sourceContractId;
            prevContractNum = path.sourceContractNum;
            prevContractOwner = path.sourceContractOwner;

            foreach (var route in path.pathRoutes)
            {
                routes.Add(new PathRouteItem { PipeId = route.fromPipeId, PipeName = route.fromPipeName, MeterName = route.fromMeterName, ContractId = prevContractId, ContractNum = prevContractNum, ContractOwner = prevContractOwner, MeterMapId = route.meterMapId.GetValueOrDefault(), IsMeter1First = route.isMeter1First });
                routes.Add(new PathRouteItem { PipeId = route.toPipeId, PipeName = route.toPipeName, MeterName = route.toMeterName, ContractId = route.toContractId, ContractNum = route.toContractNum, ContractOwner = route.toContractOwner, MeterMapId = route.meterMapId.GetValueOrDefault(), IsMeter1First = route.isMeter1First });

                prevContractId = route.toContractId;
                prevContractNum = route.toContractNum;
                prevContractOwner = route.toContractOwner;
            }

            result = new PathDetail
            {
                PathId = path.pathId,
                PathName = path.pathName,
                SourceCounterpartyId = path.sourceCounterpartyId,
                SourceMeterId = path.sourceMeterId,
                SourcePipeId = path.sourcePipeId,
                SourceContractId = path.sourceContractId,
                PathRoutes = routes
                //sourceFromPipeId = path.sourcePipeId
            };
        }

        return Ok(result);
    }

    [Permission("SOS Pathing", PermissionType.View)]
    [Route("[action]/{fromPipeId}/{toPipeId}")]
    public async Task<IActionResult> GetTransferMeterMaps(int fromPipeId, int toPipeId)
    {
        DateOnly currentDate = DateOnly.FromDateTime(DateTime.Now.Date);

        var productId = (int)Enums.Product.NaturalGas;
        var asOfDate = DateOnly.FromDateTime(DateTime.Now.Date);

        var result = await (
            from q in db.TransferMeters
            let meterProduct1 = q.Meter1.MeterProducts
                .Where(x => x.ProductId == productId && x.EffectiveDate <= asOfDate).OrderByDescending(x => x.EffectiveDate).FirstOrDefault()
            let meterProduct2 = q.Meter2.MeterProducts
                .Where(x => x.ProductId == productId && x.EffectiveDate <= asOfDate).OrderByDescending(x => x.EffectiveDate).FirstOrDefault()
            let zone1 = meterProduct1 != null ? meterProduct1.SourceZone : null
            let zone2 = meterProduct2 != null ? meterProduct2.SourceZone : null
            let pipe1 = zone1 != null ? zone1.Pipeline : null
            let pipe2 = zone2 != null ? zone2.Pipeline : null
            let pipeId1 = pipe1 != null ? pipe1.Id : 0
            let pipeId2 = pipe2 != null ? pipe2.Id : 0
            where
                q.ProductId == productId
                    && ((pipeId1 == fromPipeId && pipeId2 == toPipeId)
                        || (pipeId1 == toPipeId && pipeId2 == fromPipeId))
            select new TransferMeterMapItem
            {
                TransferMeterMapId = q.Id,
                Pipe1Id = pipeId1,
                Pipe2Id = pipeId2,
                Meter1Id = q.Meter1Id,
                Meter2Id = q.Meter2Id,
                Meter1Name = q.Meter1.InactiveDate != null && q.Meter1.InactiveDate!.Value <= currentDate ? "{Inactive} " + q.Meter1.Name : q.Meter1.Name,
                Meter2Name = q.Meter2.InactiveDate != null && q.Meter2.InactiveDate!.Value <= currentDate ? "{Inactive} " + q.Meter2.Name : q.Meter2.Name,
            }
        ).ToListAsync();

        foreach (var item in result)
        {
            if (fromPipeId == toPipeId)
            {
                if (string.Compare(item.Meter1Name, item.Meter2Name) < 0)
                {
                    item.TransferMeterMapName = item.Meter1Name + " / " + item.Meter2Name;
                    item.IsMeter1First = true;
                }
                else
                {
                    item.TransferMeterMapName = item.Meter2Name + " / " + item.Meter1Name;
                    item.IsMeter1First = false;
                }
            }
            else if (item.Pipe1Id == fromPipeId)
            {
                item.TransferMeterMapName = item.Meter1Name + " / " + item.Meter2Name;
                item.IsMeter1First = true;
            }
            else
            {
                item.TransferMeterMapName = item.Meter2Name + " / " + item.Meter1Name;
                item.IsMeter1First = false;
            }
        }

        result = result.OrderBy(x => x.TransferMeterMapName.Contains("Inactive") ? "zzz" : x.TransferMeterMapName).ToList();

        return Ok(result);
    }

    [Permission("SOS Pathing", PermissionType.Modify)]
    [Route("[action]")]
    public async Task<IActionResult> SaveDetail(PathDetail pathDetail)
    {
        Database.Models.GasPath mapping;

        if (pathDetail.PathId > 0)
        {
            mapping = await db.GasPaths.Where(x => x.Id == pathDetail.PathId).Include(x => x.GasPathRoutes).FirstAsync();
            mapping.GasPathRoutes.Clear();
        }
        else
        {
            mapping = new Database.Models.GasPath();
            db.GasPaths.Add(mapping);
        }

        mapping.Name = pathDetail.PathName;
        mapping.SourceCounterpartyId = pathDetail.SourceCounterpartyId;
        mapping.SourceMeterId = pathDetail.SourceMeterId;
        mapping.SourcePipeContractId = pathDetail.SourceContractId;

        int orderId = 1;
        //skip first two routes since the first route contains source data and the second does not have contract data
        for (int i = 2; i < pathDetail.PathRoutes.Count; i += 2)
        {
            var route = pathDetail.PathRoutes[i];
            mapping.GasPathRoutes.Add(new GasPathRoute { MeterMapId = route.MeterMapId, ToPipeContractId = route.ContractId, IsMeter1First = route.IsMeter1First, OrderId = orderId });
            orderId += 1;
        }

        await db.SaveChangesAsync();

        return Ok(mapping.Id);
    }

    [Permission("SOS Pathing", PermissionType.Modify)]
    [Route("[action]/{id}")]
    public async Task<IActionResult> DeleteDetail(int id)
    {
        Database.Models.GasPath dbItem = await db.GasPaths.Where(x => x.Id == id).FirstAsync();
        db.Entry(dbItem).State = EntityState.Deleted;

        await db.SaveChangesAsync();

        return Ok();
    }
}
