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

namespace PowderCoating.Infrastructure.Services;

public partial class SeedDataService
{
    /// <summary>
    /// Seeds inventory transaction history for a company, comprising four categories of
    /// transactions: opening balance (Initial), three months of powder restocks (Purchase),
    /// per-job powder consumption for every completed/ready/delivered job (JobUsage),
    /// and one waste plus one manual adjustment record.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Idempotency: returns 0 immediately if any non-deleted inventory transactions already
    /// exist for this company, preventing duplicate ledger entries on re-seed.
    /// </para>
    /// <para>
    /// All transactions are accumulated in a single in-memory list and persisted in one
    /// <c>AddRangeAsync / SaveChangesAsync</c> call for efficiency. The trade-off is that
    /// <c>BalanceAfter</c> values on individual transactions are approximations based on
    /// <c>QuantityOnHand</c> at seed time rather than a running sum — they are good enough
    /// for demo display purposes but should not be trusted as exact ledger balances.
    /// </para>
    /// <para>
    /// JobUsage quantities are stored as <em>negative</em> numbers (e.g. −5.5 lbs) following
    /// the production convention where negative quantity = stock consumed.
    /// </para>
    /// <para>
    /// The color-matching helper <c>PickItem()</c> attempts to link each completed job to an
    /// inventory item whose color name starts with the same word as the job's first item's color
    /// (e.g. "Matte Black" → key "matte"). If no match is found it falls back to round-robin
    /// rotation so every completed job still gets a usage transaction.
    /// </para>
    /// </remarks>
    /// <param name="company">The tenant company to seed inventory transactions for.</param>
    /// <returns>Total number of transaction records inserted, or 0 if already seeded or no inventory items exist.</returns>
    private async Task<int> SeedInventoryTransactionsAsync(Company company)
    {
        var existingCount = await _context.Set<InventoryTransaction>()
            .IgnoreQueryFilters()
            .CountAsync(t => t.CompanyId == company.Id && !t.IsDeleted);

        if (existingCount > 0)
            return 0;

        // ── Load inventory items (powder coats) ───────────────────────────────
        var items = await _context.Set<InventoryItem>()
            .IgnoreQueryFilters()
            .Where(i => i.CompanyId == company.Id && !i.IsDeleted && i.IsActive)
            .ToListAsync();

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

        // Two-query approach: resolve status IDs first to avoid Include() navigation
        // returning null when global query filters interact with IgnoreQueryFilters().
        var completedStatusIds = await _context.Set<JobStatusLookup>()
            .IgnoreQueryFilters()
            .Where(s => s.CompanyId == company.Id
                && new[] { "COMPLETED", "READY_FOR_PICKUP", "DELIVERED" }.Contains(s.StatusCode))
            .Select(s => s.Id)
            .ToListAsync();

        var completedJobs = completedStatusIds.Count == 0
            ? new List<Job>()
            : await _context.Set<Job>()
                .IgnoreQueryFilters()
                .Where(j => j.CompanyId == company.Id && !j.IsDeleted
                            && completedStatusIds.Contains(j.JobStatusId))
                .Include(j => j.JobItems)
                .OrderBy(j => j.Id)
                .ToListAsync();

        var now    = DateTime.UtcNow;
        var seeded = 0;
        var txns   = new List<InventoryTransaction>();

        // ── Initial stock transactions (set opening balances) ─────────────────
        // These reflect inventory on hand from day-1; dated 90 days ago
        foreach (var item in items)
        {
            var openingQty = item.QuantityOnHand > 0 ? item.QuantityOnHand : 25m;
            txns.Add(new InventoryTransaction
            {
                InventoryItemId = item.Id,
                TransactionType = InventoryTransactionType.Initial,
                Quantity        = openingQty,
                UnitCost        = item.UnitCost,
                TotalCost       = Math.Round(openingQty * item.UnitCost, 2),
                TransactionDate = now.AddDays(-90),
                Reference       = "Opening balance",
                Notes           = "Initial stock entry",
                BalanceAfter    = openingQty,
                CompanyId       = company.Id,
                CreatedAt       = now.AddDays(-90)
            });
        }

        // ── Purchase transactions — 12 months of monthly restocks ────────────
        var powderItems = items.Take(8).ToList();
        // Quantities vary slightly month to month to give the inventory chart a natural shape
        var purchaseOffsets = new (int daysAgo, decimal mult)[]
        {
            (365, 0.80m), (335, 0.85m), (305, 0.90m), (275, 0.95m),
            (245, 1.00m), (215, 1.05m), (185, 1.10m), (155, 1.10m),
            (125, 1.00m), ( 95, 1.10m), ( 65, 1.00m), ( 35, 0.95m)
        };
        foreach (var (offset, qtyMult) in purchaseOffsets)
        {
            foreach (var item in powderItems.Take(4))
            {
                var qty = Math.Round(25m * qtyMult, 0);
                txns.Add(new InventoryTransaction
                {
                    InventoryItemId = item.Id,
                    TransactionType = InventoryTransactionType.Purchase,
                    Quantity        = qty,
                    UnitCost        = item.UnitCost,
                    TotalCost       = Math.Round(qty * item.UnitCost, 2),
                    TransactionDate = now.AddDays(-offset),
                    Reference       = $"PO-{now.AddDays(-offset):yyMM}-{item.Id:D3}",
                    Notes           = "Scheduled restock",
                    BalanceAfter    = qty + (item.QuantityOnHand > 0 ? item.QuantityOnHand : 25m),
                    CompanyId       = company.Id,
                    CreatedAt       = now.AddDays(-offset)
                });
            }
        }

        // ── JobUsage transactions — powder consumed for completed jobs ─────────
        // Each completed job consumes powder from one or two inventory items.
        // Spread consumption across the past 3 months.
        var colorMap = new Dictionary<string, InventoryItem?>();
        foreach (var item in items)
        {
            var key = (item.ColorName ?? item.Name).ToLowerInvariant().Split(' ')[0];
            colorMap.TryAdd(key, item);
        }

        // Fallback: rotate through items if no color match.
        // PickItem is a local function rather than a LINQ expression because it needs to
        // mutate the outer itemIdx counter across multiple calls.
        int itemIdx = 0;
        InventoryItem PickItem(string? colorName)
        {
            if (!string.IsNullOrEmpty(colorName))
            {
                var key = colorName.ToLowerInvariant().Split(' ')[0];
                if (colorMap.TryGetValue(key, out var matched) && matched != null)
                    return matched;
            }
            return items[itemIdx++ % items.Count];
        }

        foreach (var (job, idx) in completedJobs.Select((j, i) => (j, i)))
        {
            // Use the job's actual completion date so powder usage history spans the same
            // 12-month window as jobs, giving the Powder Usage report non-trivial data in
            // every month rather than clustering everything in the last 60 days.
            var usageDate = (job.CompletedDate ?? job.ScheduledDate ?? now.AddDays(-30)).Date;

            // Pick a color-matched powder item (or rotate)
            var firstItem = job.JobItems?.FirstOrDefault();
            var inv = PickItem(firstItem?.ColorName);

            // Powder used: 3–15 lbs depending on job size
            var lbsUsed = Math.Round(3m + (idx % 5) * 2.5m, 1);
            if (lbsUsed < 0.5m) lbsUsed = 0.5m;

            txns.Add(new InventoryTransaction
            {
                InventoryItemId = inv.Id,
                TransactionType = InventoryTransactionType.JobUsage,
                Quantity        = -lbsUsed,   // negative = consumed
                UnitCost        = inv.UnitCost,
                TotalCost       = Math.Round(lbsUsed * inv.UnitCost, 2),
                TransactionDate = usageDate,
                Reference       = job.JobNumber,
                Notes           = $"Powder used — {job.JobNumber}",
                BalanceAfter    = Math.Max(0, inv.QuantityOnHand - lbsUsed),
                CompanyId       = company.Id,
                CreatedAt       = usageDate
            });
        }

        // ── Waste/adjustment transactions ─────────────────────────────────────
        if (items.Count >= 2)
        {
            txns.Add(new InventoryTransaction
            {
                InventoryItemId = items[0].Id,
                TransactionType = InventoryTransactionType.Waste,
                Quantity        = -1.5m,
                UnitCost        = items[0].UnitCost,
                TotalCost       = Math.Round(1.5m * items[0].UnitCost, 2),
                TransactionDate = now.AddDays(-45),
                Reference       = "Waste",
                Notes           = "Contaminated powder — disposed",
                BalanceAfter    = Math.Max(0, items[0].QuantityOnHand - 1.5m),
                CompanyId       = company.Id,
                CreatedAt       = now.AddDays(-45)
            });
            txns.Add(new InventoryTransaction
            {
                InventoryItemId = items[1].Id,
                TransactionType = InventoryTransactionType.Adjustment,
                Quantity        = 2.0m,
                UnitCost        = items[1].UnitCost,
                TotalCost       = Math.Round(2.0m * items[1].UnitCost, 2),
                TransactionDate = now.AddDays(-30),
                Reference       = "Adjustment",
                Notes           = "Physical count correction",
                BalanceAfter    = items[1].QuantityOnHand + 2.0m,
                CompanyId       = company.Id,
                CreatedAt       = now.AddDays(-30)
            });
        }

        await _context.Set<InventoryTransaction>().AddRangeAsync(txns);
        await _context.SaveChangesAsync();
        seeded = txns.Count;

        return seeded;
    }
}
