using System.Diagnostics;
using Fast.Shared.Logic.FileService;
using Fast.Shared.Logic.Package;
using Fast.Web.Logic;
using Fast.Web.Logic.NaturalGas;

namespace Fast.Web.Controllers;

[Authorize]
[ApiController]
[Route("api/[controller]")]
public class InvoiceNatGasController : ODataController
{
    private readonly MyDbContext db;
    private readonly AuthorizationHelper authHelper;
    private readonly FileService fileService;
    private const string securityActionApprove = "Invoice Natural Gas Approval";
    private const string securityActionDistribute = "Invoice Natural Gas Distribute";
    private readonly BcExporter bcExporter = new();

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

    [Permission("Invoice Natural Gas", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> GetRequiredData()
    {
        var hasApprovalPermission = await authHelper.IsAuthorizedAsync(User, securityActionApprove, PermissionType.Standard);
        var hasDistributePermission = await authHelper.IsAuthorizedAsync(User, securityActionDistribute, PermissionType.Standard);
        var result = new { hasApprovalPermission, hasDistributePermission };

        return Ok(result);
    }

    [Permission("Invoice Natural Gas", PermissionType.View)]
    [Route("/odata/GetInvoiceGasGridItems")]
    public async Task<IActionResult> GetInvoiceGasGridItems(DateOnly monthStart, DateOnly monthEnd, ODataQueryOptions<InvoiceGasGridItem> queryOptions, bool isExport)
    {
        queryOptions = Util.GetQueryOptionsWithConvertedDates(queryOptions);
        var totalsByEntities = await GetInvoiceHelper.GetNomActualTotalsByEntities(monthStart, monthEnd);

        var queryable = (
            from q in db.VwInvoiceGas
            where monthStart <= q.Month!.Value && q.Month.Value <= monthEnd
            orderby q.Month, q.InternalEntity, q.Counterparty
            select new InvoiceGasGridItem
            {
                Id = q.Id,
                InvoiceNum = q.InvoiceNum,
                Traders = q.Traders,
                Month = q.Month!.Value,
                InternalEntityId = q.InternalEntityId!.Value,
                InternalEntity = q.InternalEntity!,
                CounterpartyId = q.CounterpartyId!.Value,
                Counterparty = q.Counterparty!,
                InvoiceType = q.InvoiceType!,
                DueDate = q.DueDate,
                FileNameOriginal = q.FileNameOriginal,
                FileNameOnDisk = q.FileNameOnDisk,
                IsApproved = q.IsApproved!,
                EmailedByName = q.EmailedByName,
                EmailedTime = q.EmailedTime,
                CounterpartyEmailAddresses = q.CounterpartyEmailAddresses,
                IsChecked = false,
                ContractId = q.ContractId,
                PaymentDateOptionId = q.PaymentDateOptionId,
                PaymentDateTypeId = q.PaymentDateTypeId,
                Subtotal = q.Subtotal,
                TotalQuantity = q.TotalQuantity
            }
        ).AsNoTracking();
        queryable = queryOptions.ApplyTo(queryable) as IQueryable<InvoiceGasGridItem>;
        var items = queryable == null ? null : await queryable.ToListAsync();

        foreach (var item in items ?? [])
        {
            if (totalsByEntities.TryGetValue((item.Month, item.CounterpartyId, item.InternalEntityId), out var nomActualTotals))
                item.SummaryInfo = GenerateSummaryInfo(items ?? [], item, nomActualTotals.TotalNomOrActualVol, nomActualTotals.TotalNomOrActualAmount);
            else
                item.SummaryInfo = "";
        }

        (new DueDateHelper()).SetDueDates(items);

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

        return Ok(items);
    }

    [Permission("Invoice Natural Gas Approval", PermissionType.Standard)]
    [Route("[action]")]
    public async Task<IActionResult> ApproveInvoiceLines(int[] idsToApprove)
    {
        if (idsToApprove == null || idsToApprove.Length == 0)
            throw new Exception("No invoices selected to approve");

        var detailLinesToApprove = await (
            from q in db.InvoiceGasLines
            where idsToApprove.Contains(q.InvoiceId)
                && q.ApprovedBy == null
            select q
        ).ToListAsync();

        if (detailLinesToApprove.Count == 0)
            return Ok();

        var invoicesToValidate = await db.InvoiceGas
            .Include(x => x.InvoiceGasLines)
            .Where(x => idsToApprove.Contains(x.Id))
            .ToListAsync();

        // Collect validation errors
        var errors = new List<string>();
        foreach (var invoice in invoicesToValidate)
        {
            var missingFields = InvoiceHeaderValidator.GetMissingFields(invoice);
            if (missingFields.Count > 0)
                errors.Add($"Invoice {invoice.InvoiceNum}: Missing fields - {string.Join(", ", missingFields)}");
        }

        if (errors.Count > 0)
            throw new Exception(string.Join("\n", errors));

        var appUserId = Util.GetAppUserId(User);
        var approvedTime = DateTime.UtcNow;

        foreach (var line in detailLinesToApprove)
        {
            line.ApprovedBy = appUserId;
            line.ApprovedTime = approvedTime;
        }

        db.SaveChanges();

        return Ok();
    }

    [Permission("Invoice Natural Gas", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> DownloadInvoice(string invoiceNum)
    {
        try
        {
            var fileNameOnDisk = db.InvoiceGas
                .Where(x => x.InvoiceNum == invoiceNum)
                .Select(x => x.FileNameOnDisk)
                .FirstOrDefault();

            if (string.IsNullOrWhiteSpace(fileNameOnDisk))
                throw new Exception($"File not found for invoice {invoiceNum}");

            var fileResponse = await fileService.DownloadFileAsync("Invoices/Gas", fileNameOnDisk);

            return File(fileResponse.Stream, fileResponse.ContentType, fileResponse.FileName);
        }
        catch (Exception ex)
        {
            string messagePrefix = $"Invoice #: {invoiceNum}";
            var bytes = Util.GetExceptionFilesBytes(ex, messagePrefix);
            return File(bytes, "text/plain");
        }
    }

    public class InvoiceSelection
    {
        public int id;
    }

    [Permission("Invoice Natural Gas", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> MergeFiles([FromBody] InvoiceSelection[] invoiceSelections)
    {
        try
        {
            var invoiceIds = (from q in invoiceSelections orderby q.id select q.id).ToArray();
            string newFileName = $"{DateTime.Now:yyyyMMdd_HHmmss}-merged-invoices.pdf";
            string subFolder = "Invoices/Gas";

            var invoiceFileData = await (
                from q in db.InvoiceGas
                where invoiceIds.Contains(q.Id)
                select new
                {
                    q.InvoiceNum,
                    q.FileNameOnDisk,
                    CounterpartyName = q.Counterparty.ShortName
                }
            ).ToListAsync();

            var invoiceDetailsWithPaths = invoiceFileData
                .Where(x => !string.IsNullOrWhiteSpace(x.FileNameOnDisk))
                .Select(x =>
                {
                    return new
                    {
                        x.InvoiceNum,
                        x.CounterpartyName,
                        x.FileNameOnDisk
                    };
                }).ToList();

            var fileDescriptorsAndPaths = invoiceDetailsWithPaths
                .OrderBy(q => q.CounterpartyName)
                .ThenBy(q => q.InvoiceNum.TicketSort())
                .ToList();

            var tempFolder = Path.Combine(Path.GetTempPath(), "InvoiceGasMerge");
            Directory.CreateDirectory(tempFolder);

            var pdfFiles = new List<(string fileName, byte[] bytes)>();
            foreach (var invoice in fileDescriptorsAndPaths)
            {
                var fileResponse = await fileService.DownloadFileAsync(subFolder, invoice.FileNameOnDisk);
                using var memoryStream = new MemoryStream();
                await fileResponse.Stream.CopyToAsync(memoryStream);
                pdfFiles.Add((invoice.FileNameOnDisk, memoryStream.ToArray()));
            }

            var stream = await PdfConverter.MergePdfsFromBytes(pdfFiles) ?? throw new FileNotFoundException("No invoices to merge.");
            return File(stream, "application/pdf", newFileName);
        }
        catch (Exception ex)
        {
            var bytes = Util.GetExceptionFilesBytes(ex);
            return File(bytes, "text/plain");
        }
    }

    [Permission("Invoice Natural Gas", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> ExportToBusinessDynamics(DateTime month)
    {
        var dateOnly = DateOnly.FromDateTime(month);
        var resultStr = await bcExporter.TestBc(dateOnly);
        await LogStatus(resultStr, dateOnly);
        if (resultStr.Contains("invoices failed"))
            throw new Exception(resultStr);
        return Ok(resultStr);
    }

    private async Task LogStatus(string resultStr, DateOnly month)
    {

        await db.Database.CreateExecutionStrategy().Execute(async () =>
        {
            using var dbContextTransaction = await db.Database.BeginTransactionAsync();
            var log = new BusinessCentralHistory
            {
                Status = resultStr.Contains("invoices failed") ? "Error" : "Success",
                RanAt = DateTime.UtcNow,
                Message = resultStr,
                Month = month
            };

            db.BusinessCentralHistories.Add(log);

            await db.SaveChangesAsync();
            await dbContextTransaction.CommitAsync();
        });
    }

    [Permission("Invoice Natural Gas", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> GetLatestStatus(DateTime month)
    {
        var dateOnly = DateOnly.FromDateTime(month);
        var latestLog = await (
            from q in db.BusinessCentralHistories
            where q.Month == dateOnly
            orderby q.RanAt descending
            select q
        ).FirstOrDefaultAsync();

        return Ok(latestLog);
    }

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

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

    private static string TableRow(string label, string value)
    {
        return
            "<tr>" +
            "<td class=\"pr-5\">" + label + "</td>" +
            "<td class=\"pl-5 justify-end flex\">" + value + "</td>" +
            "</tr>";
    }

    [Permission("Invoice Natural Gas", PermissionType.View)]
    [Route("[action]")]
    private static string GenerateSummaryInfo(List<InvoiceGasGridItem> matchingInvoices, InvoiceGasGridItem item, decimal totalNomOrActualVol, decimal totalNomOrActualAmount)
    {
        if (item.InvoiceNum == "" || item.InvoiceNum == null)
            return "";

        var summary = new InvoiceSummary();

        summary.TotalQuantity = totalNomOrActualVol;
        summary.TotalAmount = totalNomOrActualAmount;

        decimal combinedQuantity = 0;
        decimal combinedAmount = 0;


        summary.Lines = matchingInvoices
            .Where(x => x.Month == item.Month && x.InternalEntityId == item.InternalEntityId && x.CounterpartyId == item.CounterpartyId)
            .Select(q => new InvoiceSummaryLine
            { InvoiceNum = q.InvoiceNum, TotalQuantity = q.TotalQuantity, TotalAmount = q.Subtotal })
            .ToList();

        // Helper to format numbers with alignment - positive numbers get a leading space for minus sign alignment
        static string FormatQty(decimal? value) => value switch
        {
            null => " 0",
            < 0 => value.Value.ToString("N0"),
            _ => " " + value.Value.ToString("N0")
        };

        static string FormatAmt(decimal? value) => value switch
        {
            null => " $0.00",
            < 0 => value.Value.ToString("C"),
            _ => " " + value.Value.ToString("C")
        };

        var summaryInfo = "<strong>Quantity:</strong>";
        summaryInfo += "<table class=\"w-full\">";

        foreach (var line in summary.Lines)
        {
            combinedQuantity += line.TotalQuantity ?? 0;
            summaryInfo += TableRow(line.InvoiceNum + ":", FormatQty(line.TotalQuantity));
        }

        item.QuantityDifference = (summary.TotalQuantity - combinedQuantity);

        summaryInfo += TableRow("Total Invoiced:", FormatQty(combinedQuantity));
        summaryInfo += TableRow("Total Nom/Actual:", FormatQty(summary.TotalQuantity));
        summaryInfo += TableRow("Difference:", FormatQty(item.QuantityDifference));
        summaryInfo += "</table><br>";

        summaryInfo += "<strong>Amount:</strong>";
        summaryInfo += "<table>";

        foreach (var line in summary.Lines)
        {
            combinedAmount += line.TotalAmount ?? 0;
            summaryInfo += TableRow(line.InvoiceNum + ":", FormatAmt(line.TotalAmount));
        }

        item.AmountDifference = (summary.TotalAmount - combinedAmount);

        summaryInfo += TableRow("Total Invoiced:", FormatAmt(combinedAmount));
        summaryInfo += TableRow("Total Nom/Actual:", FormatAmt(summary.TotalAmount));
        summaryInfo += TableRow("Difference:", FormatAmt(item.AmountDifference));
        summaryInfo += "</table>";

        return summaryInfo;
    }
}
