using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.IdentityModel.Tokens;

namespace Fast.Controllers;


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

    [Permission("Meter", PermissionType.View)]
    [Route("/odata/GetMeterItems")]
    public IActionResult GetItems(ODataQueryOptions<MeterListItem> queryOptions, bool isExport, DateTime? asOfDate)
    {
        queryOptions = Util.GetQueryOptionsWithConvertedDates(queryOptions);
        var itemsQueryable = GetItemsInternal(asOfDate);
        var items = (queryOptions.ApplyTo(itemsQueryable) as IEnumerable<MeterListItem>)?.ToList();

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

    [Permission("Meter", PermissionType.View)]
    [Route("[action]")]
    public IQueryable<MeterListItem> GetItemsInternal(DateTime? asOfDate)
    {
        var asOfDateOnly = DateOnly.FromDateTime(asOfDate ?? DateTime.Today);
        var itemsQueryable = (
            from q in db.Meters
            where asOfDate == null || q.InactiveDate == null || q.InactiveDate >= asOfDateOnly
            join mo in db.VwMeterOverviewInfos on q.Id equals mo.Id into j1
            from mo in j1.DefaultIfEmpty()
            select new MeterListItem
            {
                MeterId = q.Id,
                Name = q.Name,
                InactiveDate = q.InactiveDate,
                ProducerIds = mo.ProducerNames,
                Lease = q.Lease == null ? null : q.Lease.Name,
                State = q.State == null ? null : q.State.Name,
                Location = q.Location == null ? null : q.Location.Name,
                Latitude = q.Latitude,
                Longitude = q.Longitude,
                Numbers = mo.Numbers,
                Pipelines = mo.PipelineNames,
                SourcePointNames = mo.PointNames,
                DeliveryPointNames = mo.DeliveryPointNames,
                HubCodes = mo.HubCodes,
                MeterTypes = mo.MeterTypeNames,
            }
        ).AsNoTracking();

        return itemsQueryable;
    }

    [Permission("Meter", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> GetRequiredData()
    {
        var hasModifyPermission = await authHelper.IsAuthorizedAsync(User, "Meter", PermissionType.Modify);
        var stateNames = await DataHelper.GetTerritoriesAsync();
        var counties = await (from q in db.Counties orderby q.Name select new IdName(q.Id, q.Name ?? "")).ToListAsync();
        var producers = await DataHelper.GetProducersAsync(false);
        var meterTypes = await (from q in db.MeterTypes orderby q.Name select new IdName(q.Id, q.Name ?? "")).ToListAsync();
        var leases = await DataHelper.GetLeasesAsync();
        var locations = await (from q in db.Locations orderby q.Name select new IdName(q.Id, q.Name ?? "")).ToListAsync();
        var sourcePipes = await DataHelper.GetPipelinesAsync(false);
        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, Enums.ProductCategory.All);
        var pointsCrude = points.Where(x => x.IsCrudePoint).ToList();
        var pointsGas = points.Where(x => x.IsGasPoint).ToList();
        var products = await DataHelper.GetProductsAsync(true);

        var result = new { hasModifyPermission, stateNames, counties, meterTypes, leases, locations, sourcePipes, sourceZones, plantNames, pointsCrude, pointsGas, producers, products };
        return Ok(result);
    }


    [Permission("Meter", PermissionType.Modify)]
    [Route("[action]")]
    public async Task<IActionResult> SaveDetail(MeterDetail detail, Enums.SaveType saveType)
    {
        int resultId = 0;
        var oldMeterProducts = new List<MeterProduct>();
        await db.Database.CreateExecutionStrategy().Execute(async () =>
        {
            using var dbContextTransaction = await db.Database.BeginTransactionAsync();
            Meter? dbItem = null;
            if (saveType != Enums.SaveType.New)
            {
                dbItem = await (
                    from q in db.Meters
                    where q.Id == detail.MeterId
                    select q
                ).FirstOrDefaultAsync();
            }
            if (dbItem == null) //if the item does not exist then add it
            {
                dbItem = new Meter();
                db.Meters.Add(dbItem);
                oldMeterProducts = new List<MeterProduct>();
            }
            else
            {
                // Remove existing items so that they get completely re-inserted
                oldMeterProducts = await db.MeterProducts.Include(mp => mp.MeterProductSourcePoints).Include(mp => mp.MeterProductDeliveryPoints).Where(x => x.MeterId == detail.MeterId).ToListAsync();
                db.MeterProducts.RemoveRange(oldMeterProducts);
            }
            var d = detail;
            dbItem.Name = d.CommonDetail.Name;
            dbItem.StateId = d.CommonDetail.StateId ?? null;
            dbItem.LeaseId = d.CommonDetail.LeaseId ?? null;
            dbItem.LocationId = d.CommonDetail.LocationId ?? null;
            dbItem.Latitude = d.CommonDetail.Latitude ?? null;
            dbItem.Longitude = d.CommonDetail.Longitude ?? null;
            dbItem.InactiveDate = d.CommonDetail.InactiveDate ?? null;
            dbItem.ProducerIds = d.CommonDetail.ProducerIds != null && d.CommonDetail.ProducerIds.Length > 0 ? string.Join(",", d.CommonDetail.ProducerIds!)
                    : null;
            dbItem.CountyId = d.CommonDetail.CountyId ?? null;
            var meterProductsToBeAdded = new List<MeterProduct>();
            foreach (var meterProduct in oldMeterProducts)
            {
                var mpa = new MeterProduct();
                mpa.MeterId = d.MeterId;
                mpa.MeterTypeId = meterProduct.MeterTypeId;
                mpa.ProductId = meterProduct.ProductId;
                mpa.Number = meterProduct.Number;
                mpa.HubCode = meterProduct.HubCode;
                mpa.IsUpstream = meterProduct.IsUpstream;
                mpa.SourceZoneId = meterProduct.SourceZoneId;
                mpa.PlantIds = meterProduct.PlantIds;
                mpa.Description = meterProduct.Description;
                mpa.EffectiveDate = meterProduct.EffectiveDate;
                foreach (var sourcePoint in meterProduct.MeterProductSourcePoints)
                {
                    var newMeterSourcePointItem = new MeterProductSourcePoint
                    {
                        PointId = sourcePoint.PointId
                    };
                    mpa.MeterProductSourcePoints.Add(newMeterSourcePointItem);
                }
                foreach (var deliveryPoint in meterProduct.MeterProductDeliveryPoints)
                {
                    var newMeterDeliveryPointItem = new MeterProductDeliveryPoint
                    {
                        PointId = deliveryPoint.PointId
                    };
                    mpa.MeterProductDeliveryPoints.Add(newMeterDeliveryPointItem);
                }
                meterProductsToBeAdded.Add(mpa);
            }
            dbItem.MeterProducts = meterProductsToBeAdded;
            await db.SaveChangesAsync();
            resultId = dbItem.Id;
            await dbContextTransaction.CommitAsync();
        });

        return Ok(resultId);
    }

    [Permission("Meter", PermissionType.Modify)]
    [Route("[action]/{id}")]
    public async Task<IActionResult> DeleteDetail(int id)
    {
        //delete any Gas SOS related items that don't have nominations
        await db.GasSupplies.Where(x => x.MeterId == id && x.GasMarketSupplies.Count == 0).ExecuteDeleteAsync();
        await db.GasMarkets.Where(x => x.MeterId == id && x.GasMarketSupplies.Count == 0).ExecuteDeleteAsync();

        //delete any Crude SOS related items that don't have nominations
        await db.CrudeSupplies.Where(x => x.MeterId == id && x.CrudeMarketSupplies.Count == 0).ExecuteDeleteAsync();
        await db.CrudeMarkets.Where(x => x.MeterId == id && x.CrudeMarketSupplies.Count == 0).ExecuteDeleteAsync();

        var firstGasSupplyNomDate = await db.GasSupplies.Where(x => x.MeterId == id).OrderBy(x => x.Date).Select(x => x.Date).FirstOrDefaultAsync();
        var firstGasMarketNomDate = await db.GasMarkets.Where(x => x.MeterId == id).OrderBy(x => x.Date).Select(x => x.Date).FirstOrDefaultAsync();
        var firstCrudeSupplyNomDate = await db.CrudeSupplies.Where(x => x.MeterId == id).OrderBy(x => x.Date).Select(x => x.Date).FirstOrDefaultAsync();
        var firstCrudeMarketNomDate = await db.CrudeMarkets.Where(x => x.MeterId == id).OrderBy(x => x.Date).Select(x => x.Date).FirstOrDefaultAsync();
        var firstNomDate = (new DateOnly[] { firstGasSupplyNomDate, firstGasMarketNomDate, firstCrudeSupplyNomDate, firstCrudeMarketNomDate }).Max();

        if (firstNomDate != DateOnly.MinValue)
        {
            var msg = $"This meter has nominations starting on {firstNomDate}. Please clear any nominations for the meter first.";
            throw new Exception(msg);
        }

        Meter dbItem = await db.Meters.Where(x => x.Id == id).FirstAsync();
        db.Meters.Remove(dbItem);
        db.SaveChanges();
        return Ok();
    }

    [Permission("Meter", PermissionType.View)]
    [Route("[action]/{meterId}")]
    public async Task<IActionResult> GetMeterProducts(int meterId)
    {
        var products = await (
        from mps in db.MeterProducts
            .Include(x => x.Product)
            .Include(x => x.MeterType)
            .Include(x => x.MeterProductSourcePoints)
        where mps.MeterId == meterId
        select new MeterProductListItem
        {
            Id = mps.Id,
            MeterId = mps.MeterId,
            ProductId = mps.ProductId,
            MeterType = mps.MeterType != null ? mps.MeterType.Name : "",
            Product = mps.Product.Name ?? "",
            Number = mps.Number ?? "",
            HubCode = mps.HubCode ?? "",
            IsUpstream = mps.IsUpstream ? "Yes" : "No",
            SourcePointIds = string.Join(",", mps.MeterProductSourcePoints.Select(sp => sp.PointId.ToString())),
            DeliveryPointIds = string.Join(",", mps.MeterProductDeliveryPoints.Select(sp => sp.PointId.ToString())),
            PlantIds = mps.PlantIds ?? "",
            SourceZone = mps.SourceZone == null ? null : mps.SourceZone.Name,
            SourcePipe = mps.SourceZone == null ? null : mps.SourceZone.Pipeline.Name,
            Description = mps.Description ?? "",
            EffectiveDate = mps.EffectiveDate.ToString("yyyy-MM-dd")

        }).ToListAsync();

        return Ok(products);
    }

    private object GetOdataOptions<T>(string v)
    {
        throw new NotImplementedException();
    }
}
