using Fast.Logic.CrudeOil;
using Fast.Shared;
using Fast.Shared.Logic.FileService;
using Fast.Web.Logic.CrudeOil;
using Fast.Web.Models;

namespace Fast.Web.Controllers;

[Authorize]
[ApiController]
[Route("api/[controller]")]
public class InvoiceCrudeDetailController : ODataController
{
    private readonly MyDbContext db;
    private readonly AuthorizationHelper authHelper;
    private readonly IFileService fileService;
    private const string securityActionApprove = "Invoice Crude Approval";

    public InvoiceCrudeDetailController(MyDbContext db, IWebHostEnvironment env)
    {
        this.db = db;
        authHelper = new AuthorizationHelper(Main.IsAuthenticationEnabled);
        var config = new FileServiceConfig(env.ContentRootPath);
        fileService = new FileService(config);
    }

    [Permission("Invoice Crude", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> GetRequiredData(int counterpartyId, int internalEntityId, DateOnly month)
    {
        var firstOfMonth = Util.Date.FirstDayOfMonth(month);
        var lastOfMonth = Util.Date.LastDayOfMonth(month);
        var hasModifyPermission = await authHelper.IsAuthorizedAsync(User, "Invoice Crude", PermissionType.Modify);
        var hasApprovalPermission = await authHelper.IsAuthorizedAsync(User, securityActionApprove, PermissionType.Standard);
        var pipelines = (await DataHelper.GetPipelinesAsync(false)).Where(x => x.IsCrudePipe == true).ToList();
        var meters = (await DataHelper.GetMetersByProductAsync(Enums.ProductCategory.CrudeOil)).Select(x => x).ToList();

        var dealNums = await (
            from d in db.Deals
            join c in db.Contracts on d.NettingContractNumber equals c.ContractNum into cGrp
            from c in cGrp.DefaultIfEmpty()
            where d.TransactionTypeId == (int)Enums.TransactionType.PhysicalCrudeOil
                && d.StartDate <= lastOfMonth
                && d.EndDate >= firstOfMonth
                && d.CounterpartyId == counterpartyId
                && d.InternalEntityId == internalEntityId
                && d.NettingContractNumber != null
            select new
            {
                d.Id,
                Name = d.TicketNum,
                Trader = d.Trader != null ? d.Trader.Initials : null,
                CounterpartyContractNum = c.TheirContractNum,
                InternalContractNum = c.ContractNum,
            }
        ).AsNoTracking().ToListAsync();

        var states = await DataHelper.GetTerritoriesAsync();
        var countries = db.Countries
            .OrderBy(x => x.Name)
            .Select(x => new IdName(x.Id, x.Name))
            .ToList();


        var result = new { hasModifyPermission, hasApprovalPermission, states, countries, pipelines, dealNums, meters };

        return Ok(result);
    }

    /// <summary>
    /// not currently being used
    /// </summary>
    public class SaveInvoiceResult
    {
        public CrudeInvoice? Invoice;
        public string? Error;
    }

    [Permission("Invoice Crude", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> GetInvoice(int? invoiceId, int counterpartyId, int internalEntityId, DateOnly month)
    {
        month = Util.Date.FirstDayOfMonth(month);

        CrudeInvoice invoice = await GetInvoiceHelper.GetInvoiceAsync(invoiceId, counterpartyId, internalEntityId, month);
        FillReadOnlyFieldsForLines(invoice.Lines);

        return Ok(invoice);
    }

    [Permission("Invoice Crude", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> GetMainInvoice(int counterpartyId, int internalEntityId, DateOnly month)
    {
        month = Util.Date.FirstDayOfMonth(month);

        var mainInvoice = await GetInvoiceHelper.GetNewMainInvoiceAsync(counterpartyId, internalEntityId, month);

        return Ok(mainInvoice);
    }

    public enum InvoiceSaveType
    {
        New = 1,
        Draft = 2,
    }

    [Permission("Invoice Crude", PermissionType.Modify)]
    [Route("[action]")]
    public async Task<IActionResult> SaveInvoice(InvoiceSaveType saveType, CrudeInvoice invoice)
    {
        var saveHelper = new SaveInvoiceHelper(authHelper, User);
        var invoiceNum = await saveHelper.SaveInvoiceAsync(saveType, invoice);

        try
        {
            var invoiceDoc = new InvoiceDoc(fileService, "Invoices", "InvoiceTemplates", "Signatures", db);
            var (fileNameOriginal, fileNameOnDisk) = await invoiceDoc.GenerateAsync(invoiceNum);
            var dbInvoice = await db.InvoiceCrudes.Where(x => x.InvoiceNum == invoiceNum).FirstAsync();
            dbInvoice.FileNameOriginal = fileNameOriginal;
            dbInvoice.FileNameOnDisk = fileNameOnDisk;
            await db.SaveChangesAsync();
        }
        catch (Exception ex)
        {
            var errorDetails = ex.Message;
            var newMessage = $"""
                Error generating document for invoice {invoiceNum}.
                The draft info was saved, but the document was not generated.
                This may indicate a problem with the invoice template.

                {errorDetails}
            """;
            throw new Exception(newMessage);
        }

        return Ok(null);
    }

    private void FillReadOnlyFieldsForLines(List<InvoiceCrudeLineItem> invoiceLines)
    {
        var linesWithDeals = invoiceLines
        .Where(x => x.DealId != null)
        .Select(x => x.DealId)
        .ToList();

        var matchedData = db.Deals
        .Where(x => linesWithDeals.Contains(x.Id))
        .Select(x => new
        {
            x.Id,
            Trader = x.Trader == null ? null : x.Trader.Initials
        })
        .ToList();

        foreach (var line in invoiceLines)
        {
            var lineMatch = matchedData.FirstOrDefault(x => x.Id == line.DealId);
            if (lineMatch != null)
                line.Trader = lineMatch.Trader;
            else
                line.Trader = null;
        }
    }

    [Permission("Invoice Crude", PermissionType.Modify)]
    [Route("[action]")]
    public async Task<IActionResult> GetTaxRate(int meterId, int counterpartyId, DateOnly month)
    {
        var prodMonthEnd = Util.Date.LastDayOfMonth(month);
        var meterState = await db.Meters
            .Where(x => x.Id == meterId)
            .Select(x => x.StateId)
            .FirstOrDefaultAsync();

        var hasValidExemption = await (
            from q in db.SalesTaxExemptions
            where q.CompanyId == counterpartyId
                && q.EffectiveDate <= prodMonthEnd
                && prodMonthEnd <= q.ExpirationDate
                && q.StateId == meterState
            select q.CompanyId
        ).AnyAsync();

        if (hasValidExemption)
            return Ok(0);

        decimal taxRate = 0;
        var countyForMeter = await db.Meters
            .Where(x => x.Id == meterId)
            .Select(x => x.CountyId)
            .FirstOrDefaultAsync();

        if (countyForMeter != null)
        {
            taxRate = await db.SalesTaxRates
                .Where(x => x.CountyId == countyForMeter)
                .Select(x => x.Rate)
                .FirstOrDefaultAsync();
        }

        return Ok(taxRate);
    }

    [Permission("Invoice Crude", PermissionType.Modify)]
    [Route("[action]/{id}")]
    public IActionResult DeleteInvoice(int id)
    {
        InvoiceCrude? dbItem = db.InvoiceCrudes.Where(x => x.Id == id).FirstOrDefault();
        if (dbItem != null)
            db.Entry(dbItem).State = EntityState.Deleted;

        db.SaveChanges();

        return Ok();
    }

    [Permission("Invoice Crude", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> GetNomOrActualVolume(DateOnly month, int internalEntityId, int counterpartyId, [FromBody] int[] dealIds)
    {
        var initialLinesBeforeMods = await InvoiceLineHelper.GetInitialLinesAsync(counterpartyId, internalEntityId, month);
        var nomOrActualVolTotal = initialLinesBeforeMods.Where(x => dealIds.Contains(x.DealId.GetValueOrDefault())).Sum(x => x.Quantity);

        return Ok(nomOrActualVolTotal);
    }

    [Permission("Invoice Crude", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> RegenerateLines(int counterpartyId, int internalEntityId, DateOnly month, List<InvoiceCrudeLineItem> existingLines)
    {
        var regeneratedLines = await InvoiceLineHelper.GetRegeneratedLinesAsync(counterpartyId, internalEntityId, month, existingLines);
        return Ok(regeneratedLines);
    }

    [Permission("Invoice Crude", PermissionType.Modify)]
    [Route("[action]")]
    public async Task<IActionResult> ReviseInvoice(RevisionParams p)
    {
        InvoiceLineHelper.ReverseSelectedLines(p.LineSelection, p.ExistingLines);
        var reversedLines = p.ExistingLines.Where(l => p.LineSelection.Contains(l.LineNum)).ToList();
        var regeneratedLines = await InvoiceLineHelper.GetRegeneratedLinesAsync(p.CounterpartyId, p.InternalEntityId, p.Month, p.ExistingLines);
        var revisedInvoiceLines = reversedLines.Concat(regeneratedLines).ToList();

        for (int i = 0; i < revisedInvoiceLines.Count; i++)
        {
            var line = revisedInvoiceLines[i];
            line.LineNum = i + 1;
            line.IsApproved = false;
        }

        return Ok(revisedInvoiceLines);
    }

    public class RevisionParams
    {
        public int CounterpartyId { get; set; }
        public int InternalEntityId { get; set; }
        public DateOnly Month { get; set; }
        public List<InvoiceCrudeLineItem> ExistingLines { get; set; } = new();
        public List<int> LineSelection { get; set; } = new();
    }

    class InvoiceSummaryLine
    {
        public string InvoiceNum = "";
        public decimal? TotalQuantity;
        public decimal? TotalAmount;
    }

    class InvoiceSummary
    {
        public decimal? TotalQuantity;
        public decimal? TotalAmount;
        public List<InvoiceSummaryLine> Lines = new();
    }

    [Permission("Invoice Crude", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> GetInvoiceVolAndQuantity(DateOnly month, int internalEntityId, int counterpartyId)
    {
        var summary = new InvoiceSummary();

        var initialLinesBeforeMods = await InvoiceLineHelper.GetInitialLinesAsync(counterpartyId, internalEntityId, month);
        summary.TotalQuantity = initialLinesBeforeMods.Sum(x => x.Quantity);
        summary.TotalAmount = Math.Round(initialLinesBeforeMods.Sum(x => x.InvoiceAmount ?? 0), 2, MidpointRounding.AwayFromZero);

        summary.Lines = await db.InvoiceCrudes
        .Where(x => x.Month == month && x.InternalEntityId == internalEntityId && x.CounterpartyId == counterpartyId)
        .Select(q => new InvoiceSummaryLine
        { InvoiceNum = q.InvoiceNum, TotalQuantity = q.TotalQuantity, TotalAmount = q.Subtotal })
        .ToListAsync();

        return Ok(summary);
    }
}
