using PowderCoating.Application.DTOs.Quote;
using PowderCoating.Application.Interfaces;
using PowderCoating.Application.Services;
using PowderCoating.Core.Entities;

namespace PowderCoating.UnitTests;

public class JobItemAssemblyServiceTests
{
    private static readonly DateTime CreatedAtUtc = new(2026, 5, 9, 14, 30, 0, DateTimeKind.Utc);

    private readonly IJobItemAssemblyService _service = new JobItemAssemblyService();

    [Fact]
    public void CreateJobItem_FromWizardDto_PreservesSalesFieldsAndCalculatedChildren()
    {
        var source = new CreateQuoteItemDto
        {
            Description = "Powder coated tumbler",
            Quantity = 2m,
            SurfaceAreaSqFt = 12m,
            EstimatedMinutes = 18,
            CatalogItemId = 44,
            IsSalesItem = true,
            Sku = "TMB-RED-20",
            ManualUnitPrice = 29.99m,
            PowderCostOverride = 7.25m,
            RequiresSandblasting = true,
            RequiresMasking = true,
            Notes = "Merch item",
            IncludePrepCost = false,
            Complexity = "Moderate",
            AiTags = "merch,tumbler",
            AiPredictionId = 91,
            Coats =
            [
                new CreateQuoteItemCoatDto
                {
                    CoatName = "Base",
                    Sequence = 1,
                    ColorName = "Signal Red",
                    ColorCode = "RAL3001",
                    Finish = "Gloss",
                    CoverageSqFtPerLb = 30m,
                    TransferEfficiency = 50m
                }
            ],
            PrepServices =
            [
                new CreateQuoteItemPrepServiceDto
                {
                    PrepServiceId = 7,
                    EstimatedMinutes = 12,
                    BlastSetupId = 88
                }
            ]
        };

        var pricing = new QuoteItemPricingResult
        {
            UnitPrice = 29.99m,
            TotalPrice = 59.98m,
            LaborCost = 23.992m    // explicitly from pricing engine, not a 0.4× multiplier
        };

        var jobItem = _service.CreateJobItem(source, jobId: 10, companyId: 3, pricing: pricing, createdAtUtc: CreatedAtUtc);
        var coats = _service.CreateJobItemCoats(source, jobItemId: 25, companyId: 3, CreatedAtUtc);
        var prepServices = _service.CreateJobItemPrepServices(source, jobItemId: 25, companyId: 3, CreatedAtUtc);

        Assert.Equal(10, jobItem.JobId);
        Assert.Equal("Powder coated tumbler", jobItem.Description);
        Assert.True(jobItem.IsSalesItem);
        Assert.Equal("TMB-RED-20", jobItem.Sku);
        Assert.False(jobItem.IncludePrepCost);
        Assert.Equal(91, jobItem.AiPredictionId);
        Assert.Equal("merch,tumbler", jobItem.AiTags);
        Assert.Equal(59.98m, jobItem.TotalPrice);
        Assert.Equal(23.992m, jobItem.LaborCost);
        Assert.Equal(CreatedAtUtc, jobItem.CreatedAt);

        var coat = Assert.Single(coats);
        Assert.Equal(25, coat.JobItemId);
        Assert.Equal(1.6m, coat.PowderToOrder);
        Assert.Equal("Signal Red", coat.ColorName);
        Assert.Equal(CreatedAtUtc, coat.CreatedAt);

        var prepService = Assert.Single(prepServices);
        Assert.Equal(88, prepService.BlastSetupId);
        Assert.Equal(12, prepService.EstimatedMinutes);
        Assert.Equal(CreatedAtUtc, prepService.CreatedAt);
    }

    [Fact]
    public void CreateJobItem_FromQuoteItem_PreservesQuoteShapeAndPrepCostFlag()
    {
        var quoteItem = new QuoteItem
        {
            Description = "Bracket set",
            Quantity = 3m,
            SurfaceAreaSqFt = 10m,
            CatalogItemId = 14,
            IsGenericItem = false,
            IsLaborItem = false,
            IsSalesItem = true,
            Sku = "BRK-SET",
            ManualUnitPrice = 18m,
            PowderCostOverride = 6m,
            UnitPrice = 42m,
            TotalPrice = 126m,
            RequiresSandblasting = true,
            RequiresMasking = false,
            EstimatedMinutes = 25,
            Notes = "Use existing hang points",
            Complexity = "Complex",
            IncludePrepCost = true,
            AiTags = "bracket,steel",
            AiPredictionId = 55,
            Coats =
            [
                new QuoteItemCoat
                {
                    CoatName = "Top Coat",
                    Sequence = 1,
                    InventoryItemId = 12,
                    ColorName = "Stale Name",
                    ColorCode = "STALE",
                    Finish = "Stale",
                    CoverageSqFtPerLb = 20m,
                    TransferEfficiency = 80m,
                    PowderCostPerLb = 5m,
                    Notes = "Resolved from inventory",
                    InventoryItem = new InventoryItem
                    {
                        Id = 12,
                        Name = "Gloss Black",
                        ColorCode = "RAL9005",
                        Finish = "Gloss"
                    }
                }
            ],
            PrepServices =
            [
                new QuoteItemPrepService
                {
                    PrepServiceId = 4,
                    EstimatedMinutes = 9,
                    BlastSetupId = 41
                }
            ]
        };

        var jobItem = _service.CreateJobItem(quoteItem, jobId: 99, companyId: 6, createdAtUtc: CreatedAtUtc);
        var coats = _service.CreateJobItemCoats(quoteItem, jobItemId: 70, companyId: 6, CreatedAtUtc);
        var prepServices = _service.CreateJobItemPrepServices(quoteItem, jobItemId: 70, companyId: 6, CreatedAtUtc);

        Assert.Equal(99, jobItem.JobId);
        Assert.True(jobItem.IsSalesItem);
        Assert.Equal("BRK-SET", jobItem.Sku);
        Assert.True(jobItem.IncludePrepCost);
        Assert.Equal(55, jobItem.AiPredictionId);
        Assert.Equal("bracket,steel", jobItem.AiTags);

        var coat = Assert.Single(coats);
        Assert.Equal("Gloss Black", coat.ColorName);
        Assert.Equal("RAL9005", coat.ColorCode);
        Assert.Equal("Gloss", coat.Finish);
        Assert.Equal(1.88m, coat.PowderToOrder);

        var prepService = Assert.Single(prepServices);
        Assert.Equal(41, prepService.BlastSetupId);
    }

    [Fact]
    public void CreateJobItem_FromQuoteItem_UsesStoredPowderToOrderWhenPresent()
    {
        var quoteItem = new QuoteItem
        {
            Description = "Wheel",
            Quantity = 4m,
            SurfaceAreaSqFt = 15m,
            Coats =
            [
                new QuoteItemCoat
                {
                    CoatName = "Primer",
                    Sequence = 1,
                    CoverageSqFtPerLb = 30m,
                    TransferEfficiency = 65m,
                    PowderToOrder = 9.5m
                }
            ]
        };

        var coat = Assert.Single(_service.CreateJobItemCoats(quoteItem, jobItemId: 5, companyId: 1, CreatedAtUtc));

        Assert.Equal(9.5m, coat.PowderToOrder);
    }

    // ─── IsAiItem propagation tests ──────────────────────────────────────────────
    // AI items use ManualUnitPrice as-is and are excluded from quote-level oven cost
    // (the pricing engine assumes oven is already baked into the AI estimate).
    // IsAiItem MUST survive every conversion path or the job will be mispriced.

    [Fact]
    public void PricingRoutingFlags_ExistOnBothQuoteItemAndJobItem()
    {
        // These bool flags are read by PricingCalculationService to route items to the
        // correct pricing path. They MUST exist on both QuoteItem and JobItem, and MUST
        // be mapped by JobItemAssemblyService in all three overloads.
        //
        // If this test fails: you added a pricing flag to one entity but not the other.
        // Fix: add the field to both entities, add it to JobItemSeed, map it in all three
        // CreateJobItem overloads, and add it to the known list below.
        var requiredPricingFlags = new[]
        {
            nameof(QuoteItem.IsGenericItem),
            nameof(QuoteItem.IsLaborItem),
            nameof(QuoteItem.IsSalesItem),
            nameof(QuoteItem.IsAiItem),
            nameof(QuoteItem.IsCustomFormulaItem),
        };

        foreach (var flag in requiredPricingFlags)
        {
            Assert.True(typeof(QuoteItem).GetProperty(flag) != null,
                $"QuoteItem is missing pricing flag '{flag}' — add it or remove it from this list.");
            Assert.True(typeof(JobItem).GetProperty(flag) != null,
                $"JobItem is missing pricing flag '{flag}' — add it to JobItem and map it in JobItemAssemblyService.");
            Assert.True(typeof(CreateQuoteItemDto).GetProperty(flag) != null,
                $"CreateQuoteItemDto is missing pricing flag '{flag}' — add it to the DTO and include it in all existingItemsData JSON blocks.");
        }
    }

    [Fact]
    public void CreateJobItem_FromDto_PreservesIsAiItemFlag()
    {
        var source = new CreateQuoteItemDto
        {
            Description     = "AI Photo Item",
            Quantity        = 1m,
            SurfaceAreaSqFt = 20m,
            EstimatedMinutes = 45,
            IsAiItem        = true,
            ManualUnitPrice = 500m,
            Complexity      = "Moderate",
            Coats           = [new CreateQuoteItemCoatDto { CoatName = "Coat 1", Sequence = 1 }]
        };
        var pricing = new QuoteItemPricingResult { UnitPrice = 500m, TotalPrice = 500m };

        var jobItem = _service.CreateJobItem(source, jobId: 1, companyId: 1, pricing: pricing, createdAtUtc: CreatedAtUtc);

        Assert.True(jobItem.IsAiItem,
            "IsAiItem must survive DTO → JobItem conversion. Without it, saved AI jobs are repriced as " +
            "calculated items on next edit and oven cost is double-charged.");
    }

    [Fact]
    public void CreateJobItem_FromQuoteItem_PreservesIsAiItemFlag()
    {
        var quoteItem = new QuoteItem
        {
            Description     = "AI Photo Item",
            Quantity        = 1m,
            SurfaceAreaSqFt = 20m,
            IsAiItem        = true,
            ManualUnitPrice = 500m,
            UnitPrice       = 500m,
            TotalPrice      = 500m,
            Coats           = [new QuoteItemCoat { CoatName = "Coat 1", Sequence = 1 }]
        };

        var jobItem = _service.CreateJobItem(quoteItem, jobId: 1, companyId: 1, createdAtUtc: CreatedAtUtc);

        Assert.True(jobItem.IsAiItem,
            "IsAiItem must survive QuoteItem → JobItem conversion (quote-approval / CreateJobFromQuote path).");
    }

    [Fact]
    public void CreateJobItem_FromExistingJobItem_PreservesIsAiItemFlag()
    {
        var source = new JobItem
        {
            Description     = "AI Photo Item",
            Quantity        = 1m,
            SurfaceAreaSqFt = 20m,
            IsAiItem        = true,
            ManualUnitPrice = 500m,
            UnitPrice       = 500m,
            TotalPrice      = 500m,
            LaborCost       = 200m,
            Coats           = [new JobItemCoat { CoatName = "Coat 1", Sequence = 1 }]
        };

        var jobItem = _service.CreateJobItem(source, jobId: 1, companyId: 1, createdAtUtc: CreatedAtUtc);

        Assert.True(jobItem.IsAiItem,
            "IsAiItem must survive JobItem → JobItem copy (rework path).");
    }

    [Fact]
    public void CreateJobItem_FromExistingJobItem_PreservesTransferableShapeForRework()
    {
        var source = new JobItem
        {
            Description = "Gate panel",
            Quantity = 1m,
            ColorName = "Bronze",
            ColorCode = "BZ-22",
            Finish = "Textured",
            SurfaceArea = 22m,
            SurfaceAreaSqFt = 22m,
            CatalogItemId = 8,
            IsGenericItem = false,
            IsLaborItem = false,
            IsSalesItem = true,
            Sku = "GATE-BRZ",
            ManualUnitPrice = 140m,
            PowderCostOverride = 9m,
            UnitPrice = 140m,
            TotalPrice = 140m,
            LaborCost = 56m,
            RequiresSandblasting = true,
            RequiresMasking = true,
            EstimatedMinutes = 90,
            Notes = "Rework copy",
            IncludePrepCost = false,
            Complexity = "Extreme",
            AiTags = "gate,outdoor",
            AiPredictionId = 12,
            Coats =
            [
                new JobItemCoat
                {
                    CoatName = "Top Coat",
                    Sequence = 1,
                    InventoryItemId = 21,
                    ColorName = "Bronze",
                    VendorId = 13,
                    ColorCode = "BZ-22",
                    Finish = "Textured",
                    CoverageSqFtPerLb = 26m,
                    TransferEfficiency = 70m,
                    PowderCostPerLb = 8m,
                    PowderToOrder = 2.75m,
                    Notes = "Keep order qty"
                }
            ],
            PrepServices =
            [
                new JobItemPrepService
                {
                    PrepServiceId = 3,
                    EstimatedMinutes = 45,
                    BlastSetupId = 77
                }
            ]
        };

        var jobItem = _service.CreateJobItem(source, jobId: 222, companyId: 9, createdAtUtc: CreatedAtUtc);
        var coats = _service.CreateJobItemCoats(source, jobItemId: 333, companyId: 9, CreatedAtUtc);
        var prepServices = _service.CreateJobItemPrepServices(source, jobItemId: 333, companyId: 9, CreatedAtUtc);

        Assert.Equal(222, jobItem.JobId);
        Assert.Equal("Bronze", jobItem.ColorName);
        Assert.True(jobItem.IsSalesItem);
        Assert.Equal("GATE-BRZ", jobItem.Sku);
        Assert.False(jobItem.IncludePrepCost);
        Assert.Equal(56m, jobItem.LaborCost);
        Assert.Equal(12, jobItem.AiPredictionId);

        var coat = Assert.Single(coats);
        Assert.Equal(2.75m, coat.PowderToOrder);
        Assert.Equal("Bronze", coat.ColorName);

        var prepService = Assert.Single(prepServices);
        Assert.Equal(77, prepService.BlastSetupId);
        Assert.Equal(45, prepService.EstimatedMinutes);
    }
}
