using Microsoft.EntityFrameworkCore;
using PowderCoating.Core.Entities;
using PowderCoating.Shared.Constants;

namespace PowderCoating.Infrastructure.Services;

public partial class SeedDataService
{
    /// <summary>
    /// Canonical emails of the 3 demo employees (2 workers + 1 manager). Used as fingerprints
    /// in RemoveSeedDataAsync to avoid needing a special "IsSeeded" flag on ApplicationUser.
    /// </summary>
    internal static readonly string[] SeededWorkerEmails =
    [
        "mike.sanders@pcldemo.com",
        "jake.wilson@pcldemo.com",
        "sarah.brooks@pcldemo.com",
    ];

    /// <summary>
    /// Seeds 3 named employees as ApplicationUser records for the demo company:
    /// Mike Sanders (Coater, Worker), Jake Wilson (Sandblaster, Worker),
    /// and Sarah Brooks (Shop Manager, Manager role with broader permissions).
    /// </summary>
    /// <remarks>
    /// Workers are seeded before jobs and time entries so that AssignedUserId on Job
    /// and UserId on JobTimeEntry and EmployeeClockEntry can reference them.
    /// Uses @pcldemo.com email domain — will never conflict with real accounts.
    /// Idempotency: bails early if any of the 3 emails already exist for this company.
    /// </remarks>
    private async Task<int> SeedShopWorkersAsync(Company company)
    {
        var anyExists = await _userManager.Users
            .AnyAsync(u => SeededWorkerEmails.Contains(u.Email) && u.CompanyId == company.Id);
        if (anyExists) return 0;

        const string pwd = "Worker123!Demo";
        var hireDate = DateTime.UtcNow.AddMonths(-18);
        int created = 0;

        // ── 2 shop workers ────────────────────────────────────────────────────────
        foreach (var (email, fn, ln, emp, pos, rate) in new (string, string, string, string, string, decimal)[]
        {
            ("mike.sanders@pcldemo.com", "Mike", "Sanders", "EMP-001", "Coater",      22.00m),
            ("jake.wilson@pcldemo.com",  "Jake", "Wilson",  "EMP-002", "Sandblaster", 20.00m),
        })
        {
            if (await _userManager.FindByEmailAsync(email) != null) continue;
            var user = new ApplicationUser
            {
                UserName         = email, Email            = email,
                FirstName        = fn,   LastName          = ln,
                EmployeeNumber   = emp,  Position          = pos,
                Department       = "Shop Floor",
                LaborCostPerHour = rate,
                EmailConfirmed   = true, IsActive          = true,
                HireDate         = hireDate,
                CompanyId        = company.Id,
                CompanyRole      = AppConstants.CompanyRoles.Worker,
                CanManageJobs    = true,
                CanViewShopFloor = true,
                CreatedAt        = hireDate
            };
            var r = await _userManager.CreateAsync(user, pwd);
            if (!r.Succeeded)
                throw new InvalidOperationException(
                    $"Failed to create {email}: {string.Join("; ", r.Errors.Select(e => e.Description))}");
            await _userManager.AddToRoleAsync(user, AppConstants.Roles.Employee);
            created++;
        }

        // ── 1 manager ─────────────────────────────────────────────────────────────
        const string managerEmail = "sarah.brooks@pcldemo.com";
        if (await _userManager.FindByEmailAsync(managerEmail) == null)
        {
            var mgr = new ApplicationUser
            {
                UserName         = managerEmail, Email            = managerEmail,
                FirstName        = "Sarah",      LastName          = "Brooks",
                EmployeeNumber   = "EMP-003",    Position          = "Shop Manager",
                Department       = "Management",
                LaborCostPerHour = 32.00m,
                EmailConfirmed   = true,         IsActive          = true,
                HireDate         = hireDate.AddMonths(-6), // hired before the workers
                CompanyId        = company.Id,
                CompanyRole      = AppConstants.CompanyRoles.Manager,
                CanManageJobs    = true,  CanViewShopFloor  = true,
                CanManageCustomers = true, CanCreateQuotes   = true,
                CanApproveQuotes = true,  CanManageCalendar = true,
                CanViewCalendar  = true,  CanManageProducts = true,
                CanViewProducts  = true,  CanManageEquipment = true,
                CanManageMaintenance = true, CanManageInventory = true,
                CanViewReports   = true,
                CreatedAt        = hireDate.AddMonths(-6)
            };
            var r = await _userManager.CreateAsync(mgr, pwd);
            if (!r.Succeeded)
                throw new InvalidOperationException(
                    $"Failed to create {managerEmail}: {string.Join("; ", r.Errors.Select(e => e.Description))}");
            await _userManager.AddToRoleAsync(mgr, AppConstants.Roles.Employee);
            created++;
        }

        return created;
    }

    /// <summary>
    /// Seeds job time entries for completed and in-progress jobs, giving the Worker
    /// Productivity report meaningful data from day one.
    /// </summary>
    /// <remarks>
    /// Each completed or in-progress job receives 2–4 time entries spread across the
    /// 5 demo workers, with realistic hours for each coating stage (sandblasting,
    /// masking, coating, curing, inspection). The total hours roughly correlate with
    /// the job's EstimatedMinutes from its first JobItem.
    ///
    /// Idempotency: bails early if any time entries already exist for this company's jobs.
    /// </remarks>
    private async Task<int> SeedJobTimeEntriesAsync(Company company)
    {
        var existingCount = await _context.Set<JobTimeEntry>()
            .IgnoreQueryFilters()
            .CountAsync(te => te.CompanyId == company.Id && !te.IsDeleted);
        if (existingCount > 0) return 0;

        var workers = await _userManager.Users
            .Where(u => SeededWorkerEmails.Contains(u.Email) && u.CompanyId == company.Id)
            .OrderBy(u => u.Email)
            .ToListAsync();

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

        // Resolve status IDs first — avoids relying on Include(j => j.JobStatus) which can
        // silently return null navigation properties when query filters interact with IgnoreQueryFilters.
        var workedStatusIds = await _context.Set<JobStatusLookup>()
            .IgnoreQueryFilters()
            .Where(s => s.CompanyId == company.Id && new[]
            {
                "IN_PREPARATION", "SANDBLASTING", "MASKING_TAPING", "CLEANING",
                "IN_OVEN", "COATING", "CURING", "QUALITY_CHECK",
                "COMPLETED", "READY_FOR_PICKUP", "DELIVERED"
            }.Contains(s.StatusCode))
            .Select(s => s.Id)
            .ToListAsync();

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

        var workedJobs = await _context.Set<Job>()
            .IgnoreQueryFilters()
            .Where(j => j.CompanyId == company.Id && !j.IsDeleted
                        && workedStatusIds.Contains(j.JobStatusId))
            .ToListAsync();

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

        string[] stages = ["Sandblasting", "Masking & Prep", "Coating", "Curing", "Inspection"];
        decimal[] stageHours = [1.5m, 0.75m, 1.25m, 0.5m, 0.5m];

        var entries = new List<JobTimeEntry>();
        int jobIdx = 0;

        foreach (var job in workedJobs)
        {
            // 2-4 entries per job cycling through stages
            var entryCount = 2 + (jobIdx % 3);
            var workDate = (job.StartedDate ?? job.CreatedAt).AddDays(1);

            for (int e = 0; e < entryCount; e++)
            {
                var worker = workers[(jobIdx + e) % workers.Count];
                var stageIdx = e % stages.Length;

                entries.Add(new JobTimeEntry
                {
                    JobId           = job.Id,
                    UserId          = worker.Id,
                    UserDisplayName = worker.FullName,
                    WorkDate        = workDate.AddDays(e),
                    HoursWorked     = stageHours[stageIdx],
                    Stage           = stages[stageIdx],
                    CompanyId       = company.Id,
                    CreatedAt       = workDate.AddDays(e)
                });
            }

            jobIdx++;
        }

        await _context.Set<JobTimeEntry>().AddRangeAsync(entries);
        await _context.SaveChangesAsync();

        return entries.Count;
    }
}
