namespace Fast.Controllers;


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

    [Permission("Meter", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> GetRequiredData(int productId)
    {
        // searches category id based on product id to properly filter out points zones and pipelines
        var categoryId = await (from q in db.Products where q.Id == productId select q.CategoryId).FirstOrDefaultAsync();
        var productCategory = (Enums.ProductCategory)categoryId;

        var hasModifyPermission = await authHelper.IsAuthorizedAsync(User, "Meter", PermissionType.Modify);
        var products = await DataHelper.GetProductsAsync(false);
        var productTypes = await (from q in db.Products orderby q.Name select new IdName(q.Id, q.Name ?? "")).ToListAsync();
        var pipelines = (await DataHelper.GetPipelinesAsync(false)).Where(x => x.ProductIds.Contains(productId)).ToList();
        var meterTypes = await (from q in db.MeterTypes orderby q.Name select new IdName(q.Id, q.Name ?? "")).ToListAsync();
        var sourceZones = await (from q in db.Zones orderby q.Name select new { zoneId = q.Id, zoneName = q.Name, PipeId = q.PipelineId }).ToListAsync();
        var plantNames = await (from q in db.Plants orderby q.Name select new IdName(q.Id, q.Name ?? "")).ToListAsync();
        var points = (await DataHelper.GetPointsAsync(false, productCategory)).Where(x => x.ProductIds.Contains(productId)).ToList();

        var result = new { hasModifyPermission, products, productTypes, pipelines, sourceZones, plantNames, points, meterTypes };
        return Ok(result);
    }

    [Permission("Meter", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> GetDetail(int meterId, int productId, DateOnly effectiveDate)
    {
        var dbItem = await (
            from mpd in db.MeterProducts
            where mpd.ProductId == productId && mpd.MeterId == meterId && mpd.EffectiveDate == effectiveDate
            select new
            {
                Id = mpd.Id,
                MeterId = mpd.MeterId,
                ProductId = mpd.ProductId,
                MeterTypeId = mpd.MeterTypeId,
                Number = string.IsNullOrEmpty(mpd.Number) ? null : mpd.Number,
                HubCode = string.IsNullOrEmpty(mpd.HubCode) ? null : mpd.HubCode,
                IsUpstream = mpd.IsUpstream,
                SourcePipeId = mpd.SourceZone != null ? mpd.SourceZone.Pipeline.Id : 0,
                SourceZoneId = mpd.SourceZone != null ? mpd.SourceZone.Id : 0,
                SourcePointIds = mpd.MeterProductSourcePoints.Where(x => x.MeterProductId == mpd.Id).Select(x => x.PointId).ToList(),
                DeliveryPointIds = mpd.MeterProductDeliveryPoints.Where(x => x.MeterProductId == mpd.Id).Select(x => x.PointId).ToList(),
                PlantIds = string.IsNullOrEmpty(mpd.PlantIds) ? null : mpd.PlantIds,
                Description = string.IsNullOrEmpty(mpd.Description) ? null : mpd.Description,
                EffectiveDate = mpd.EffectiveDate

            }
        ).AsNoTracking().FirstOrDefaultAsync();

        if (dbItem == null)
        {
            // create blank detail in case of new product
            var nullDetail = new MeterProductDetail
            {
                MeterId = meterId,
                ProductId = productId,
                IsUpstream = false,
                HubCode = null,
                MeterTypeId = null,
                Number = null,
                SourcePipeId = null,
                SourceZoneId = null,
                SourcePointIds = new List<int>(),
                DeliveryPointIds = new List<int>(),
                PlantIds = new List<int>(),
                Description = null,
                EffectiveDate = DateOnly.FromDateTime(DateTime.Now)
            };
            return Ok(nullDetail);
        }

        var detail = new MeterProductDetail
        {
            Id = dbItem.Id,
            MeterId = dbItem.MeterId,
            ProductId = dbItem.ProductId,
            MeterTypeId = dbItem.MeterTypeId,
            Number = dbItem.Number,
            IsUpstream = dbItem.IsUpstream,
            HubCode = dbItem.HubCode,
            SourcePipeId = dbItem.SourcePipeId,
            SourceZoneId = dbItem.SourceZoneId,
            SourcePointIds = dbItem.SourcePointIds,
            DeliveryPointIds = dbItem.DeliveryPointIds,
            PlantIds = dbItem.PlantIds?.Split(",").Select(x => int.Parse(x)).ToList(),
            Description = dbItem.Description,
            EffectiveDate = dbItem.EffectiveDate
        };

        return Ok(detail);
    }

    public enum SaveType
    {
        New = 1,
        Normal = 2
    }

    [Permission("Meter", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> SaveDetail(MeterProductDetail detail, SaveType saveType)
    {
        int resultId = 0;
        await db.Database.CreateExecutionStrategy().Execute(async () =>
        {
            using var dbContextTransaction = await db.Database.BeginTransactionAsync();
            MeterProduct? dbItem = null;
            if (saveType != SaveType.New)
            {
                dbItem = await (
                    from q in db.MeterProducts
                    where q.Id == detail.Id
                    select q
                ).FirstOrDefaultAsync();
            }
            if (dbItem == null) //if the item does not exist then add it
            {
                dbItem = new MeterProduct();
                db.MeterProducts.Add(dbItem);
            }

            var sourcePointsToRemove = await db.MeterProductSourcePoints.Where(x => x.MeterProductId == dbItem.Id).ToListAsync();
            db.MeterProductSourcePoints.RemoveRange(sourcePointsToRemove);

            var deliveryPointsToRemove = await db.MeterProductDeliveryPoints.Where(x => x.MeterProductId == dbItem.Id).ToListAsync();
            db.MeterProductDeliveryPoints.RemoveRange(deliveryPointsToRemove);

            var d = detail;

            dbItem.MeterId = d.MeterId;
            dbItem.MeterTypeId = d.MeterTypeId;
            dbItem.ProductId = d.ProductId;
            dbItem.Number = d.Number;
            dbItem.HubCode = d.HubCode;
            dbItem.IsUpstream = d.IsUpstream;
            dbItem.SourceZoneId = d.SourceZoneId;
            if (d.SourcePointIds != null && d.SourcePointIds.Count > 0)
            {
                foreach (var sourcePointId in d.SourcePointIds)
                {
                    var newSourcePointItem = new MeterProductSourcePoint();
                    newSourcePointItem.PointId = sourcePointId;
                    dbItem.MeterProductSourcePoints.Add(newSourcePointItem);
                }
            }
            if (d.DeliveryPointIds != null && d.DeliveryPointIds.Count > 0)
            {
                foreach (var deliveryPointId in d.DeliveryPointIds)
                {
                    var newDeliveryPointItem = new MeterProductDeliveryPoint();
                    newDeliveryPointItem.PointId = deliveryPointId;
                    dbItem.MeterProductDeliveryPoints.Add(newDeliveryPointItem);
                }
            }

            dbItem.PlantIds = d.PlantIds != null && d.PlantIds.Count > 0 ? string.Join(",", d.PlantIds!) : null;
            dbItem.Description = d.Description;
            dbItem.EffectiveDate = d.EffectiveDate ?? DateOnly.FromDateTime(DateTime.Now);

            await db.SaveChangesAsync();
            resultId = dbItem.Id;

            await dbContextTransaction.CommitAsync();
        });
        return Ok(resultId);
    }

    [Permission("Meter", PermissionType.Modify)]
    [Route("[action]")]
    public async Task<IActionResult> DeleteDetail(int id)
    {
        var dbItem = await db.MeterProducts.Where(x => x.Id == id).FirstOrDefaultAsync();
        if (dbItem != null)
        {
            db.MeterProducts.Remove(dbItem);
            await db.SaveChangesAsync();
        }
        return Ok();
    }
}
