using Microsoft.EntityFrameworkCore;
using PowderCoating.Core.Entities;
using PowderCoating.Core.Enums;

namespace PowderCoating.Infrastructure.Services;

public partial class SeedDataService
{
    /// <summary>
    /// Seeds 26 invoices spanning three months of billing history: six paid in month −3,
    /// eight paid or partially paid in month −2, nine in month −1, and three recent plus
    /// one overdue in the current month.  Each invoice is linked to a seeded job and
    /// has one or two line items plus an optional <see cref="Payment"/> record.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Idempotency: returns 0 immediately if any non-deleted invoices already exist for this company.
    /// </para>
    /// <para>
    /// The method resolves four accounting accounts by number/sub-type from the previously
    /// seeded chart of accounts (4000 Powder Coating Revenue, 4100 Sandblasting Revenue,
    /// 1000 Checking, 2200 Sales Tax Payable). If any account is not found its FK is simply
    /// omitted (null) rather than aborting the seed run.
    /// </para>
    /// <para>
    /// The private <c>Inv()</c> local async function handles building, saving, and optionally
    /// recording a payment for a single invoice. It is a local function rather than a separate
    /// method because it captures the outer variables <c>jobs</c>, <c>seq</c>, <c>ji</c>,
    /// and the four account references by closure.
    /// </para>
    /// <para>
    /// Payment dates follow the production convention: for fully-paid invoices the payment is
    /// recorded five days before the due date; for partially-paid invoices the deposit is
    /// recorded four days after the invoice date.
    /// </para>
    /// <para>
    /// The "overdue" demo invoice is created 35 days ago with Net-14 terms, placing it 21 days
    /// past due — enough to appear prominently in the AR Aging report.
    /// </para>
    /// </remarks>
    /// <param name="company">The tenant company to seed invoices for.</param>
    /// <returns>Number of invoices inserted, or 0 if already seeded or no eligible jobs exist.</returns>
    private async Task<int> SeedInvoicesAsync(Company company)
    {
        var existingCount = await _context.Set<Invoice>()
            .IgnoreQueryFilters()
            .CountAsync(i => i.CompanyId == company.Id && !i.IsDeleted);

        if (existingCount > 0)
            return 0;

        // ── Dependencies ──────────────────────────────────────────────────────
        var jobs = await _context.Set<Job>()
            .IgnoreQueryFilters()
            .Include(j => j.JobItems)
            .Where(j => j.CompanyId == company.Id && !j.IsDeleted
                     && j.FinalPrice > 0 && j.CustomerId > 0)
            .OrderBy(j => j.Id)
            .ToListAsync();

        if (jobs.Count == 0) return 0;

        var revenueAcct    = await _context.Set<Account>().IgnoreQueryFilters()
            .FirstOrDefaultAsync(a => a.CompanyId == company.Id && a.AccountNumber == "4000" && !a.IsDeleted);
        var sandblastAcct  = await _context.Set<Account>().IgnoreQueryFilters()
            .FirstOrDefaultAsync(a => a.CompanyId == company.Id && a.AccountNumber == "4100" && !a.IsDeleted);
        var checkingAcct   = await _context.Set<Account>().IgnoreQueryFilters()
            .FirstOrDefaultAsync(a => a.CompanyId == company.Id
                && a.AccountSubType == AccountSubType.Checking && !a.IsDeleted);
        var salesTaxAcct   = await _context.Set<Account>().IgnoreQueryFilters()
            .FirstOrDefaultAsync(a => a.CompanyId == company.Id && a.AccountNumber == "2200" && !a.IsDeleted);

        var preparedBy = await _userManager.Users
            .Where(u => u.CompanyId == company.Id).FirstOrDefaultAsync();

        var now    = DateTime.UtcNow;
        var pfx    = $"INV-{now:yy}{now.Month:D2}-";
        var seq    = 1;
        var seeded = 0;
        var ji     = 0; // rotating job index

        // Rotates through the available jobs in order. Using modulo wrapping means the
        // seeder never throws even if there are fewer jobs than invoices to create.
        Job NextJob() { var j = jobs[ji % jobs.Count]; ji++; return j; }

        // ── Core builder ──────────────────────────────────────────────────────
        //
        // Builds and persists one Invoice + optional Payment in a single call.
        // Each invoice is saved immediately (not batched) so EF generates an ID before
        // the Payment FK is set. Tax is only added as a line item when taxPct > 0.
        async Task Inv(
            InvoiceStatus status,
            int daysAgo,   // invoice creation date
            int dueDays,   // net terms in days
            decimal taxPct,
            string terms,
            string? notes,
            // payment fields — ignored when status is Draft or Sent
            PaymentMethod pMethod = PaymentMethod.BankTransferACH,
            string? pRef = null)
        {
            var job     = NextJob();
            var date    = now.AddDays(-daysAgo);
            var sub     = job.FinalPrice;
            var taxAmt  = Math.Round(sub * taxPct / 100m, 2);
            var total   = sub + taxAmt;
            var isPaid  = status == InvoiceStatus.Paid;
            var isPart  = status == InvoiceStatus.PartiallyPaid;
            var amtPaid = isPaid ? total : isPart ? Math.Round(total * 0.5m, 2) : 0m;

            var desc = job.JobItems?.FirstOrDefault()?.Description ?? "Powder Coating Services";

            var inv = new Invoice
            {
                InvoiceNumber     = $"{pfx}{seq++:D4}",
                JobId             = job.Id,
                CustomerId        = job.CustomerId,
                PreparedById      = preparedBy?.Id,
                Status            = status,
                InvoiceDate       = date,
                DueDate           = date.AddDays(dueDays),
                SentDate          = status != InvoiceStatus.Draft ? date : null,
                PaidDate          = isPaid ? date.AddDays(dueDays - 5) : null,
                SubTotal          = sub,
                TaxPercent        = taxPct,
                TaxAmount         = taxAmt,
                Total             = total,
                AmountPaid        = amtPaid,
                Terms             = terms,
                Notes             = notes,
                CustomerPO        = job.CustomerPO,
                SalesTaxAccountId = taxAmt > 0 ? salesTaxAcct?.Id : null,
                CompanyId         = company.Id,
                CreatedAt         = date
            };

            inv.InvoiceItems.Add(new InvoiceItem
            {
                Description      = desc,
                Quantity         = 1,
                UnitPrice        = sub,
                TotalPrice       = sub,
                RevenueAccountId = revenueAcct?.Id,
                DisplayOrder     = 1,
                CompanyId        = company.Id,
                CreatedAt        = date
            });
            if (taxAmt > 0)
                inv.InvoiceItems.Add(new InvoiceItem
                {
                    Description      = $"Sales Tax ({taxPct:0.##}%)",
                    Quantity         = 1,
                    UnitPrice        = taxAmt,
                    TotalPrice       = taxAmt,
                    RevenueAccountId = salesTaxAcct?.Id,
                    DisplayOrder     = 2,
                    CompanyId        = company.Id,
                    CreatedAt        = date
                });

            await _context.Set<Invoice>().AddAsync(inv);
            await _context.SaveChangesAsync();
            seeded++;

            if (amtPaid > 0)
            {
                // Paid: payment date = dueDate - 5d.  Partial: a few days after invoice
                var pDate = isPaid ? date.AddDays(dueDays - 5) : date.AddDays(4);
                await _context.Set<Payment>().AddAsync(new Payment
                {
                    InvoiceId        = inv.Id,
                    Amount           = amtPaid,
                    PaymentDate      = pDate,
                    PaymentMethod    = pMethod,
                    Reference        = pRef,
                    Notes            = isPaid ? "Paid in full" : "50% deposit",
                    RecordedById     = preparedBy?.Id,
                    DepositAccountId = checkingAcct?.Id,
                    CompanyId        = company.Id,
                    CreatedAt        = pDate
                });
                await _context.SaveChangesAsync();
            }
        }

        // ── Months −12 through −4: 8 paid invoices per month ────────────────
        // 8 × avg ~$650 = ~$5,200/month collected revenue, which exceeds the seeded
        // ~$4,200/month in operating expenses, so the P&L chart shows a consistent profit.
        // Payment methods and tax rates alternate for variety in the ledger.
        var histMethods = new[] { PaymentMethod.BankTransferACH, PaymentMethod.Check, PaymentMethod.CreditDebitCard, PaymentMethod.Cash };
        for (int monthBack = 12; monthBack >= 4; monthBack--)
        {
            for (int inv = 0; inv < 8; inv++)
            {
                var daysAgo = monthBack * 30 + 25 - (inv * 3);
                var taxPct  = (monthBack + inv) % 2 == 0 ? 7.5m : 0m;
                var method  = histMethods[(monthBack * 8 + inv) % histMethods.Length];
                var chkRef  = method == PaymentMethod.Check ? $"CHK-{9000 + monthBack * 8 + inv:D4}" : null;
                await Inv(InvoiceStatus.Paid, daysAgo, 30, taxPct, "Net 30", "Thank you for your business!", method, chkRef);
            }
        }

        // ── Month −3 (6 paid) ─────────────────────────────────────────────────
        await Inv(InvoiceStatus.Paid, 88, 30, 0m,   "Net 30", "Thank you for your business!", PaymentMethod.BankTransferACH);
        await Inv(InvoiceStatus.Paid, 84, 30, 7.5m, "Net 30", "Thank you for your business!", PaymentMethod.Check,             "CHK-1041");
        await Inv(InvoiceStatus.Paid, 80, 14, 0m,   "Net 14", "Thank you!",                   PaymentMethod.Cash);
        await Inv(InvoiceStatus.Paid, 76, 30, 7.5m, "Net 30", "Thank you for your business!", PaymentMethod.BankTransferACH);
        await Inv(InvoiceStatus.Paid, 72, 30, 0m,   "Net 30", "Thank you!",                   PaymentMethod.CreditDebitCard);
        await Inv(InvoiceStatus.Paid, 68, 30, 7.5m, "Net 30", "Thank you for your business!", PaymentMethod.Check,             "CHK-1044");

        // ── Month −2 (6 paid + 2 partial) ────────────────────────────────────
        await Inv(InvoiceStatus.Paid,          62, 30, 0m,   "Net 30", "Thank you!",                             PaymentMethod.BankTransferACH);
        await Inv(InvoiceStatus.Paid,          58, 30, 7.5m, "Net 30", "Thank you for your business!",           PaymentMethod.Check,             "CHK-1048");
        await Inv(InvoiceStatus.Paid,          55, 14, 0m,   "Due on receipt", "Paid on completion.",            PaymentMethod.Cash);
        await Inv(InvoiceStatus.Paid,          51, 30, 7.5m, "Net 30", "Thank you!",                             PaymentMethod.BankTransferACH);
        await Inv(InvoiceStatus.Paid,          47, 30, 0m,   "Net 30", "Thank you for your business!",           PaymentMethod.CreditDebitCard);
        await Inv(InvoiceStatus.Paid,          43, 30, 7.5m, "Net 30", "Thank you!",                             PaymentMethod.Check,             "CHK-1052");
        await Inv(InvoiceStatus.PartiallyPaid, 40, 30, 0m,   "Net 30", "50% deposit received — balance due.",    PaymentMethod.Check,             "CHK-1053");
        await Inv(InvoiceStatus.PartiallyPaid, 37, 30, 7.5m, "Net 30", "Deposit on file — balance due on pickup.", PaymentMethod.BankTransferACH);

        // ── Month −1 (7 paid + 2 partial + 2 sent) ───────────────────────────
        // 7 paid × avg ~$650 + 2 partial × 50% × avg ~$650 ≈ $5,200 collected — above expenses.
        await Inv(InvoiceStatus.Paid,          35, 30, 0m,   "Net 30", "Thank you for your business!",           PaymentMethod.BankTransferACH);
        await Inv(InvoiceStatus.Paid,          32, 30, 7.5m, "Net 30", "Thank you!",                             PaymentMethod.Check,             "CHK-1054");
        await Inv(InvoiceStatus.Paid,          28, 30, 0m,   "Net 30", "Thank you for your business!",           PaymentMethod.BankTransferACH);
        await Inv(InvoiceStatus.Paid,          24, 14, 7.5m, "Net 14", "Thank you!",                             PaymentMethod.Cash);
        await Inv(InvoiceStatus.Paid,          20, 30, 0m,   "Net 30", "Thank you for your business!",           PaymentMethod.BankTransferACH);
        await Inv(InvoiceStatus.Paid,          18, 30, 7.5m, "Net 30", "Thank you!",                             PaymentMethod.CreditDebitCard);
        await Inv(InvoiceStatus.Paid,          16, 30, 0m,   "Net 30", "Thank you for your business!",           PaymentMethod.Check,             "CHK-1058");
        await Inv(InvoiceStatus.PartiallyPaid, 14, 30, 7.5m, "Net 30", "50% deposit received.",                  PaymentMethod.Check,             "CHK-1060");
        await Inv(InvoiceStatus.PartiallyPaid, 11, 30, 0m,   "Net 30", "50% deposit — balance due on completion.", PaymentMethod.BankTransferACH);
        await Inv(InvoiceStatus.Sent,          9,  30, 7.5m, "Net 30", "Payment due within 30 days.");
        await Inv(InvoiceStatus.Sent,          6,  30, 0m,   "Net 30", "Payment due within 30 days.");

        // ── Current month (1 overdue + 2 sent + 1 draft) ─────────────────────
        // Overdue: created 35 days ago on Net 14 terms → 21 days past due (1–30 bucket)
        await Inv(InvoiceStatus.Sent, 35, 14, 7.5m, "Net 14", "PAST DUE — please remit payment immediately.");
        await Inv(InvoiceStatus.Sent, 4,  30, 0m,   "Net 30", "Payment due within 30 days.");
        await Inv(InvoiceStatus.Sent, 2,  30, 7.5m, "Net 30", "Payment due within 30 days.");
        await Inv(InvoiceStatus.Draft, 1, 30, 0m,   "Net 30", null);

        // ── AR Aging demo invoices — populate all four overdue buckets ────────
        // 31–60 day bucket: issued 55 days ago, Net 10 → 45 days past due
        await Inv(InvoiceStatus.Sent, 55, 10, 0m,   "Net 10", "PAST DUE 45 days — second notice sent.");
        // 61–90 day bucket: issued 80 days ago, Net 10 → 70 days past due
        await Inv(InvoiceStatus.Sent, 80, 10, 7.5m, "Net 10", "PAST DUE 70 days — final notice. Collections pending.");
        // 90+ day bucket: issued 120 days ago, Net 14 → 106 days past due
        await Inv(InvoiceStatus.PartiallyPaid, 120, 14, 0m, "Net 14", "PAST DUE 106 days — partial payment received, balance outstanding.");

        return seeded;
    }
}
