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

namespace PowderCoating.Infrastructure.Services;

public partial class SeedDataService
{
    /// <summary>
    /// Seeds 50 powder coating jobs distributed across all 16 statuses with a realistic
    /// weighted distribution (Delivered most common, exactly one Cancelled, one OnHold)
    /// and a shuffled visit order so jobs from different customers are interleaved naturally
    /// rather than appearing as per-customer blocks.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Per-customer job counts and price ranges are defined by <c>CustomerProfile(ci)</c>
    /// where <c>ci</c> is the customer's position in the Id-ascending list seeded by
    /// <c>SeedCustomersAsync</c> (0 = Carolina Fabrication, the largest account).
    /// </para>
    /// <para>
    /// A shuffled <c>visitSchedule</c> (fixed seed 42) drives the outer loop so that
    /// Carolina Fabrication, Apex Motorsports, and individual customers appear
    /// interleaved in creation-date order rather than in consecutive customer blocks.
    /// </para>
    /// <para>
    /// Status pool (fixed seed 99): Delivered &times;10, Completed &times;8,
    /// ReadyForPickup &times;5, then decreasing counts for in-progress stages, with
    /// exactly one Cancelled and one OnHold.
    /// Priority pool (fixed seed 77): Normal 76&thinsp;%, High 12&thinsp;%, Urgent 8&thinsp;%,
    /// Rush 4&thinsp;% — rush is genuinely rare.
    /// </para>
    /// </remarks>
    private async Task<int> SeedJobsAsync(Company company)
    {
        var existingCount = await _context.Set<Job>()
            .IgnoreQueryFilters()
            .CountAsync(j => j.CompanyId == company.Id && !j.IsDeleted);

        if (existingCount > 0)
            return 0;

        var jobStatuses = await _context.Set<JobStatusLookup>()
            .IgnoreQueryFilters()
            .Where(s => s.CompanyId == company.Id)
            .ToDictionaryAsync(s => s.StatusCode, s => s.Id);

        var jobPriorities = await _context.Set<JobPriorityLookup>()
            .IgnoreQueryFilters()
            .Where(p => p.CompanyId == company.Id)
            .ToDictionaryAsync(p => p.PriorityCode, p => p.Id);

        if (jobStatuses.Count == 0 || jobPriorities.Count == 0)
            return 0;

        var customers = await _context.Set<Customer>()
            .IgnoreQueryFilters()
            .Where(c => c.CompanyId == company.Id && !c.IsDeleted)
            .OrderBy(c => c.Id)
            .ToListAsync();

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

        var approvedQuotes = await _context.Set<Quote>()
            .IgnoreQueryFilters()
            .Where(q => q.CompanyId == company.Id && q.QuoteStatus.StatusCode == "APPROVED")
            .OrderBy(q => q.CustomerId)
            .ThenBy(q => q.Id)
            .ToListAsync();

        var shopUsers = await _context.Set<ApplicationUser>()
            .Where(u => u.CompanyId == company.Id && u.IsActive)
            .OrderBy(u => u.Id)
            .ToListAsync();

        var now = DateTime.UtcNow;
        var prefix = $"JOB-{now:yy}{now.Month:D2}-";
        var existing = await _context.Set<Job>()
            .IgnoreQueryFilters()
            .Where(j => j.JobNumber.StartsWith(prefix))
            .Select(j => j.JobNumber)
            .ToListAsync();
        var maxNum = 0;
        foreach (var n in existing)
            if (n.Length >= 13 && int.TryParse(n.Substring(9, 4), out var x) && x > maxNum) maxNum = x;
        var seq = maxNum + 1;

        // ── Per-customer profile: (jobCount, minJobValue, maxJobValue) ─────────
        // Indices match the customer insertion order from SeedCustomersAsync (ascending Id):
        //   0=Carolina Fabrication, 1=Apex Motorsports, 2=Triangle Offroad,
        //   3=Smith Welding, 4=Raleigh Architectural, 5=East Coast Powderworks,
        //   6=Piedmont Metal Works, 7=Cary Industrial, 8=Durham Tech, 9=Wake County Fleet,
        //   10–26 = individual residential customers
        static (int count, decimal minVal, decimal maxVal) CustomerProfile(int ci) => ci switch
        {
            0  => (7,  800m, 2500m),  // Carolina Fabrication — largest account
            1  => (6,  400m, 1500m),  // Apex Motorsports
            2  => (5,  350m, 1200m),  // Triangle Offroad
            3  => (4,  250m,  800m),  // Smith Welding
            4  => (4,  300m,  900m),  // Raleigh Architectural Metals
            5  => (3,  200m,  600m),  // East Coast Powderworks
            6  => (3,  150m,  450m),  // Piedmont Metal Works
            7  => (2,  200m,  500m),  // Cary Industrial Solutions
            8  => (2,  350m,  900m),  // Durham Tech Equipment
            9  => (3,  400m, 1500m),  // Wake County Fleet Services
            10 => (2,   75m,  250m),  // John Davis
            11 => (1,  150m,  350m),  // Sarah Jenkins
            12 => (1,  200m,  400m),  // Mike Thompson
            13 => (2,  100m,  300m),  // Robert Miller
            14 => (0,    0m,    0m),  // Jennifer Clark — prospect only
            15 => (1,  100m,  250m),  // David Wilson
            16 => (0,    0m,    0m),  // Lisa Anderson — prospect only
            17 => (1,  150m,  300m),  // Thomas Harris
            18 => (0,    0m,    0m),  // Karen White — no jobs yet
            19 => (1,  250m,  500m),  // James Taylor
            20 => (0,    0m,    0m),  // Michelle Brown — no jobs yet
            21 => (1,  100m,  250m),  // Chris Lee
            22 => (0,    0m,    0m),  // Amanda Garcia — no jobs yet
            23 => (1,  150m,  350m),  // Kevin Martinez
            24 => (0,    0m,    0m),  // Nancy Rodriguez — no jobs yet
            25 => (0,    0m,    0m),  // Brian Hall — no jobs yet
            _  => (0,    0m,    0m),  // Patricia Young — no jobs yet
        };

        // ── Status pool: realistic shop distribution (total = 50) ──────────────
        // Delivered and Completed dominate; exactly one Cancelled and one OnHold.
        var statusPool = new List<string>();
        foreach (var (code, count) in new (string Code, int Count)[]
        {
            ("DELIVERED",        10),
            ("COMPLETED",         8),
            ("READY_FOR_PICKUP",  5),
            ("IN_PREPARATION",    4),
            ("SANDBLASTING",      4),
            ("COATING",           3),
            ("QUALITY_CHECK",     3),
            ("CURING",            3),
            ("MASKING_TAPING",    2),
            ("IN_OVEN",           2),
            ("CLEANING",          1),
            ("PENDING",           1),
            ("QUOTED",            1),
            ("APPROVED",          1),
            ("ON_HOLD",           1),
            ("CANCELLED",         1),
        })
        {
            for (int k = 0; k < count; k++) statusPool.Add(code);
        }
        // Fisher-Yates shuffle with a fixed seed so resets produce the same distribution
        var statusRng = new Random(99);
        for (int k = statusPool.Count - 1; k > 0; k--)
        {
            var swap = statusRng.Next(k + 1);
            (statusPool[k], statusPool[swap]) = (statusPool[swap], statusPool[k]);
        }

        // ── Priority pool: realistic distribution (total = 50) ─────────────────
        // Rush jobs are genuinely rare; most work is Normal priority.
        var priorityPool = new List<string>();
        foreach (var (code, count) in new (string Code, int Count)[]
        {
            ("NORMAL",  38),
            ("HIGH",     6),
            ("URGENT",   4),
            ("RUSH",     2),
        })
        {
            for (int k = 0; k < count; k++) priorityPool.Add(code);
        }
        var priorityRng = new Random(77);
        for (int k = priorityPool.Count - 1; k > 0; k--)
        {
            var swap = priorityRng.Next(k + 1);
            (priorityPool[k], priorityPool[swap]) = (priorityPool[swap], priorityPool[k]);
        }

        // ── Customer visit schedule: interleave commercial (ci 0–9) and individual (ci 10+) ──
        // A plain Fisher-Yates on the full list clusters commercial entries because they
        // outnumber individual ones 4:1; splitting into two pools and distributing
        // individual jobs evenly throughout ensures the two types never appear in blocks.
        var commercialVisits = new List<int>();
        var individualVisits = new List<int>();
        for (int ci = 0; ci < customers.Count; ci++)
        {
            var (numJobs, _, _) = CustomerProfile(ci);
            for (int j = 0; j < numJobs; j++)
                (ci < 10 ? commercialVisits : individualVisits).Add(ci);
        }
        var rngC = new Random(42);
        for (int k = commercialVisits.Count - 1; k > 0; k--)
        {
            var swap = rngC.Next(k + 1);
            (commercialVisits[k], commercialVisits[swap]) = (commercialVisits[swap], commercialVisits[k]);
        }
        var rngI = new Random(17);
        for (int k = individualVisits.Count - 1; k > 0; k--)
        {
            var swap = rngI.Next(k + 1);
            (individualVisits[k], individualVisits[swap]) = (individualVisits[swap], individualVisits[k]);
        }
        // Distribute individual visits at evenly-spaced positions throughout the commercial list
        var visitSchedule   = new List<int>(commercialVisits.Count + individualVisits.Count);
        double indStride    = individualVisits.Count > 0
            ? (commercialVisits.Count + 1.0) / (individualVisits.Count + 1.0)
            : double.MaxValue;
        int indInsertIdx = 0;
        for (int comIdx = 0; comIdx < commercialVisits.Count; comIdx++)
        {
            while (indInsertIdx < individualVisits.Count && (indInsertIdx + 1) * indStride <= comIdx + 1)
                visitSchedule.Add(individualVisits[indInsertIdx++]);
            visitSchedule.Add(commercialVisits[comIdx]);
        }
        while (indInsertIdx < individualVisits.Count)
            visitSchedule.Add(individualVisits[indInsertIdx++]);

        // Job item descriptions and specs — 15-item pool cycling via (visitIdx*3 + itemIdx) % 15.
        static (string desc, string color, bool sand, bool mask, int mins) ItemSpec(int i, int j) =>
            ((i * 3 + j) % 15) switch
            {
                0  => ("18\" Aluminum Wheels (set of 4)",          "Gloss Black",     false, false, 45),
                1  => ("17\" Steel Wheels (set of 4)",             "Signal White",    false, false, 30),
                2  => ("Jeep Bumper & Rock Sliders",               "Matte Black",     true,  false, 60),
                3  => ("Motorcycle Frame",                         "Matte Black",     true,  false, 90),
                4  => ("Steel Shelving Units (10-shelf set)",      "Textured Gray",   true,  false, 55),
                5  => ("Industrial Machine Guard Panels",          "Safety Yellow",   false, false, 35),
                6  => ("Aluminum Window Frames (set of 8)",        "Satin Bronze",    false, true,  50),
                7  => ("Steel Handrail System — 40 ft",       "Gloss Black",     true,  false, 120),
                8  => ("Wrought Iron Entry Gate",                  "Hammered Black",  true,  false, 180),
                9  => ("Brake Calipers (set of 4)",               "Candy Red",       false, true,  35),
                10 => ("Restaurant Chair Frames (set of 20)",      "Hammered Bronze", false, false, 60),
                11 => ("Bicycle Frame",                            "Candy Red",       true,  true,  60),
                12 => ("Compressor Tank",                          "Safety Orange",   true,  false, 45),
                13 => ("Patio Furniture Set (6 pieces)",           "Textured Beige",  false, false, 50),
                _  => ("Custom Steel Fabrication — Batch",    "Matte Black",     true,  false, 40)
            };

        var jobs             = new List<Job>();
        var quoteIdx         = 0;
        var jobsByCustomer   = new int[customers.Count]; // within-customer job counter per ci
        var jobIdx           = 0;                        // global counter for misc modulos
        var inProgressCount  = 0;                        // caps "Carried Over" card to 2 jobs
        var completedJobCount = 0;                       // drives linear date spread over 12 months

        for (int visitIdx = 0; visitIdx < visitSchedule.Count; visitIdx++, jobIdx++, seq++)
        {
            var ci       = visitSchedule[visitIdx];
            var customer = customers[ci];
            var j        = jobsByCustomer[ci]++;        // within-customer job index
            var (_, minVal, maxVal) = CustomerProfile(ci);

            var statusCode   = statusPool[visitIdx];
            var priorityCode = priorityPool[visitIdx];

            // Try to link the first available approved quote for this customer
            Quote? linkedQuote = null;
            for (int qi = quoteIdx; qi < approvedQuotes.Count; qi++)
            {
                if (approvedQuotes[qi].CustomerId == customer.Id)
                {
                    linkedQuote = approvedQuotes[qi];
                    quoteIdx = qi + 1;
                    break;
                }
                // Every 4th job, forcibly consume the next available approved quote
                if (quoteIdx % 4 == 0 && qi == quoteIdx)
                {
                    linkedQuote = approvedQuotes[qi];
                    quoteIdx++;
                    break;
                }
            }

            // Date logic: completed jobs furthest back, ready-for-pickup recent past,
            // in-progress spread forward, pending/quoted in the future.
            var isCompleted      = statusCode is "COMPLETED" or "DELIVERED" or "CANCELLED";
            var isReadyForPickup = statusCode == "READY_FOR_PICKUP";
            var isInProgress     = statusCode is "IN_PREPARATION" or "SANDBLASTING" or "MASKING_TAPING"
                                               or "CLEANING" or "IN_OVEN" or "COATING" or "CURING" or "QUALITY_CHECK";

            if (isInProgress)    inProgressCount++;
            if (isCompleted)     completedJobCount++;

            // Completed jobs spread linearly ~1–12 months back for chart coverage.
            // Ready-for-pickup: job finished recently, just waiting on customer.
            // In-progress: first 3 are genuinely past-due ("Carried Over"); rest spread into future.
            int daysAgo     = isCompleted      ? 30 + (completedJobCount - 1) * 14
                            : isReadyForPickup  ? 5 + (visitIdx % 8)
                            : isInProgress      ? 10 + (visitIdx % 40)
                            :                     2  + (visitIdx % 15);
            var createdDate = now.AddDays(-daysAgo);

            var scheduledDate = isCompleted      ? createdDate.AddDays(3 + (visitIdx % 5))
                              : isReadyForPickup  ? now.AddDays(visitIdx % 5)
                              : isInProgress      ? now.AddDays(inProgressCount <= 3 ? -(4 - inProgressCount) : inProgressCount - 3)
                              :                     now.AddDays(3 + (visitIdx % 12));
            var rushDays      = priorityCode == "RUSH" ? 2 : priorityCode == "URGENT" ? 3 : 7;
            var dueDate       = scheduledDate.AddDays(rushDays);
            var startedDate   = isCompleted || isReadyForPickup || isInProgress ? (DateTime?)scheduledDate : null;
            var completedDate = isCompleted || isReadyForPickup ? scheduledDate.AddDays(1) : (DateTime?)null;

            // Per-customer value targeting: deterministic variance within the customer's price range
            var range       = maxVal - minVal;
            var targetValue = minVal + range * ((ci * 7 + j * 13) % 100) / 100m;
            var itemCount   = 1 + (visitIdx % 3);
            var items       = new List<JobItem>();

            for (int k = 0; k < itemCount; k++)
            {
                var (desc, color, sand, mask, mins) = ItemSpec(visitIdx, k);
                var qty       = 1 + (k % 3);
                var unitPrice = linkedQuote != null && k == 0
                    ? Math.Round(linkedQuote.Total / itemCount, 2)
                    : Math.Round(targetValue / itemCount / qty, 2);

                items.Add(new JobItem
                {
                    Description          = desc,
                    Quantity             = qty,
                    ColorName            = color,
                    SurfaceAreaSqFt      = 10m + k * 3.5m,
                    UnitPrice            = unitPrice,
                    TotalPrice           = unitPrice * qty,
                    LaborCost            = Math.Round(unitPrice * qty * 0.35m, 2),
                    RequiresSandblasting = sand,
                    RequiresMasking      = mask,
                    EstimatedMinutes     = mins,
                    CompanyId            = company.Id,
                    CreatedAt            = createdDate
                });
            }

            var finalPrice  = items.Sum(it => it.TotalPrice);
            var quotedPrice = linkedQuote?.Total ?? Math.Round(finalPrice * 1.05m, 2);

            jobs.Add(new Job
            {
                JobNumber                = $"{prefix}{seq:D4}",
                CustomerId               = customer.Id,
                QuoteId                  = linkedQuote?.Id,
                AssignedUserId           = shopUsers.Count > 0 ? shopUsers[visitIdx % shopUsers.Count].Id : null,
                Description              = linkedQuote?.Description
                                           ?? $"Powder coating services for {customer.CompanyName ?? $"{customer.ContactFirstName} {customer.ContactLastName}".Trim()}",
                JobStatusId              = jobStatuses[statusCode],
                JobPriorityId            = jobPriorities[priorityCode],
                ScheduledDate            = scheduledDate,
                StartedDate              = startedDate,
                CompletedDate            = completedDate,
                DueDate                  = dueDate,
                QuotedPrice              = quotedPrice,
                FinalPrice               = finalPrice,
                IsRushJob                = priorityCode == "RUSH",
                CustomerPO               = customer.IsCommercial && visitIdx % 3 == 0 ? $"PO-{40000 + visitIdx}" : null,
                SpecialInstructions      = visitIdx % 6  == 0 ? "Customer supplied parts — handle with extra care." :
                                           visitIdx % 11 == 0 ? "Match existing color exactly — bring sample for approval." : null,
                InternalNotes            = visitIdx % 8 == 0 ? "Vintage parts — do not use aggressive blast media." : null,
                RequiresCustomerApproval = visitIdx % 5 == 0,
                IsCustomerApproved       = visitIdx % 5 != 0 || !isInProgress,
                JobItems                 = items,
                CompanyId                = company.Id,
                CreatedAt                = createdDate,
                // Set UpdatedAt to the historical event date so analytics charts group into the
                // correct month. The EF interceptor only stamps UpdatedAt on Modified saves,
                // leaving it null for seeded entities, which the analytics filter treats as excluded.
                UpdatedAt                = completedDate ?? (isInProgress ? scheduledDate : (DateTime?)null) ?? createdDate
            });
        }

        await _context.Set<Job>().AddRangeAsync(jobs);
        await _context.SaveChangesAsync();

        return jobs.Count;
    }
}
