using System.Text.Json;
using PowderCoating.Application.DTOs.Columbia;
using PowderCoating.Infrastructure.Services.Columbia;

namespace PowderCoating.UnitTests;

/// <summary>
/// Tests for the Columbia catalog mapper, focused on the fields that are tricky in the real feed:
/// the free-text cure parser (multiple glyphs, multi-curve, partial-cure), manufacturer derivation
/// from a multi-brand distributor, pricing across simple/variable products, and HTML stripping.
/// Cases mirror records captured from the live API.
/// </summary>
public class ColumbiaCatalogMapperTests
{
    // ── Cure schedule parsing ─────────────────────────────────────────────

    [Fact]
    public void ParseCureCurves_SimpleSchedule_ReturnsSingleCurve()
    {
        var curves = ColumbiaCatalogMapper.ParseCureCurves("10 minutes @ 400°F");
        Assert.Single(curves);
        Assert.Equal(400, curves[0].TempF);
        Assert.Equal(10, curves[0].Minutes);
    }

    [Fact]
    public void ParseCureCurves_MetalTemperaturePrefixWithCelsius_ParsesFahrenheitOnly()
    {
        var curves = ColumbiaCatalogMapper.ParseCureCurves("Metal Temperature: 10 minutes at 400°F (204°C)");
        Assert.Single(curves);
        Assert.Equal(400, curves[0].TempF);
        Assert.Equal(10, curves[0].Minutes);
    }

    [Theory]
    [InlineData("10 minutes @ 400˚F (204˚C)")]  // U+02DA ring above
    [InlineData("10 minutes @ 400ºF (204ºC)")]  // U+00BA masculine ordinal
    [InlineData("Metal temperature: 10 minutes @ 400F (204C)")] // no degree glyph at all
    public void ParseCureCurves_DegreeGlyphVariants_AllParse(string schedule)
    {
        var curves = ColumbiaCatalogMapper.ParseCureCurves(schedule);
        Assert.Single(curves);
        Assert.Equal(400, curves[0].TempF);
    }

    [Fact]
    public void ParseCureCurves_MultiCurve_CapturesAllInOrder_PrimaryIsFirst()
    {
        // Real record S1790085-55: standard high-temp curve first, low-temp alternates after.
        var schedule = "Metal Temperature: 5 minutes at 400°F (204°C) -or- 10 minutes at 360°F (182°C)* 15 minutes at 340°F (171°C)* *Low-Temp cure curve";
        var curves = ColumbiaCatalogMapper.ParseCureCurves(schedule);

        Assert.Equal(3, curves.Count);
        Assert.Equal(new ColumbiaCatalogMapper.CureCurve(400, 5), curves[0]);   // primary
        Assert.Equal(new ColumbiaCatalogMapper.CureCurve(360, 10), curves[1]);  // alternate
        Assert.Equal(new ColumbiaCatalogMapper.CureCurve(340, 15), curves[2]);  // alternate
    }

    [Fact]
    public void ParseCureCurves_PartialCureInstructions_ReturnsEmpty()
    {
        // Illusion powder F1697027 — multi-step, no single temp/time pair.
        var schedule = "(1) - Apply a basecoat and partial cure. (2) - Apply this powder and partial cure (3). - Apply a clear coat and fully cure.";
        var curves = ColumbiaCatalogMapper.ParseCureCurves(schedule);
        Assert.Empty(curves);
    }

    [Fact]
    public void Map_MultiCurve_SetsPrimaryTempTimeAndStoresAllCurvesJson()
    {
        var product = new ColumbiaProduct
        {
            Sku = "S1790085-55",
            Name = "Multi Cure",
            CureSchedule = "5 minutes at 400°F -or- 10 minutes at 360°F",
        };

        var item = ColumbiaCatalogMapper.Map(product);

        Assert.Equal(400m, item.CureTemperatureF);
        Assert.Equal(5, item.CureTimeMinutes);
        Assert.False(string.IsNullOrEmpty(item.CureCurvesJson));
        Assert.Equal("5 minutes at 400°F -or- 10 minutes at 360°F", item.CureScheduleText);

        var curves = JsonSerializer.Deserialize<List<ColumbiaCatalogMapper.CureCurve>>(item.CureCurvesJson!);
        Assert.Equal(2, curves!.Count);
    }

    // ── Manufacturer derivation ───────────────────────────────────────────

    [Fact]
    public void DeriveManufacturer_AddSkuPrefix_IsKpPigments()
    {
        var p = new ColumbiaProduct { Sku = "ADD-BRBDS", Name = "Barbados Blue ColorShift Pearl" };
        Assert.Equal("KP Pigments", ColumbiaCatalogMapper.DeriveManufacturer(p));
        Assert.True(ColumbiaCatalogMapper.IsAdditive(p));
    }

    [Fact]
    public void DeriveManufacturer_PpgCategory_IsPpg()
    {
        var p = new ColumbiaProduct
        {
            Sku = "PCU75139",
            Name = "PPG Chrome Shadow",
            Categories = { new ColumbiaNamed { Name = "PPG Powders" } },
        };
        Assert.Equal("PPG", ColumbiaCatalogMapper.DeriveManufacturer(p));
    }

    [Fact]
    public void DeriveManufacturer_HouseBrand_IsColumbia()
    {
        var p = new ColumbiaProduct
        {
            Sku = "X5004124",
            Name = "Blue Wrinkle",
            Categories = { new ColumbiaNamed { Name = "Powders" }, new ColumbiaNamed { Name = "New Releases" } },
        };
        Assert.Equal("Columbia Coatings", ColumbiaCatalogMapper.DeriveManufacturer(p));
        Assert.False(ColumbiaCatalogMapper.IsAdditive(p));
    }

    // ── Excluded products (swatches + tester/sample size variants) ────────

    [Theory]
    [InlineData("T1696049-SW", "**SWATCH** - Copperhead II", true)]      // swatch card
    [InlineData("T1696049-SW", "Copperhead II", true)]                   // -SW suffix alone
    [InlineData("X1", "**SWATCH** - Something", true)]                   // name marker alone
    [InlineData("T1696049-04", "Copperhead II (4 Ounce Tester)", true)]  // tester = size variant
    [InlineData("XYZ", "Copperhead II (4 Ounce Tester)", true)]          // tester by name
    [InlineData("S1760090-S", "Black Beauty Sample (5 lbs)", true)]      // 5lb sample = size variant
    [InlineData("X5004124", "Blue Wrinkle", false)]                      // normal powder
    [InlineData("S5704126", "Smokey Blue", false)]                       // normal powder, SKU starts with S
    public void IsExcludedProduct_DetectsSwatchesTestersAndSamples(string sku, string name, bool expected)
    {
        var p = new ColumbiaProduct { Sku = sku, Name = name };
        Assert.Equal(expected, ColumbiaCatalogMapper.IsExcludedProduct(p));
    }

    // ── Pricing ───────────────────────────────────────────────────────────

    [Fact]
    public void ParseBasePrice_SimpleProduct_UsesTopLevelPrice()
    {
        var p = new ColumbiaProduct { Price = "18.85", RegularPrice = "18.85" };
        Assert.Equal(18.85m, ColumbiaCatalogMapper.ParseBasePrice(p));
    }

    [Fact]
    public void ParseBasePrice_VariableProductWithZeroRegular_FallsBackToPriceThenVariants()
    {
        // Variable parent: price carries the lead variant, regular_price is "0".
        var p = new ColumbiaProduct
        {
            Price = "18.85",
            RegularPrice = "0",
            VariationPricing = new List<ColumbiaVariationPricing>
            {
                new() { Sku = "X-B", Price = "18.85" },
                new() { Sku = "X-P", Price = "18.85" },
            },
        };
        Assert.Equal(18.85m, ColumbiaCatalogMapper.ParseBasePrice(p));
    }

    [Fact]
    public void BuildPriceTiersJson_VariableProduct_SerializesVariationPricing()
    {
        var p = new ColumbiaProduct
        {
            VariationPricing = new List<ColumbiaVariationPricing>
            {
                new() { Sku = "X-B", Price = "18.85" },
            },
        };
        var json = ColumbiaCatalogMapper.BuildPriceTiersJson(p);
        Assert.NotNull(json);
        Assert.Contains("X-B", json);
    }

    [Fact]
    public void BuildPriceTiersJson_EmptyTieredPricingArray_ReturnsNull()
    {
        // Variable products carry tiered_pricing as an empty array.
        var p = new ColumbiaProduct { TieredPricing = JsonDocument.Parse("[]").RootElement };
        Assert.Null(ColumbiaCatalogMapper.BuildPriceTiersJson(p));
    }

    // ── Chemistry, color, HTML ────────────────────────────────────────────

    [Theory]
    [InlineData("Polyester/TGIC", "Polyester/TGIC")]
    [InlineData("Polyester TGIC", "Polyester/TGIC")]
    [InlineData("TGIC Polyester", "Polyester/TGIC")]
    [InlineData("TGIC", "TGIC")]
    [InlineData("", null)]
    public void NormalizeChemistry_CollapsesPolyesterTgicVariants(string input, string? expected)
    {
        Assert.Equal(expected, ColumbiaCatalogMapper.NormalizeChemistry(input));
    }

    [Fact]
    public void BuildColorFamilies_JoinsColorGroupNames()
    {
        var p = new ColumbiaProduct
        {
            PaColorGroup = { new ColumbiaNamed { Name = "Blue" }, new ColumbiaNamed { Name = "Green" } },
        };
        Assert.Equal("Blue,Green", ColumbiaCatalogMapper.BuildColorFamilies(p));
    }

    [Fact]
    public void BuildColorFamilies_FallsBackToColorGroupAttribute()
    {
        var p = new ColumbiaProduct
        {
            Attributes =
            {
                new ColumbiaAttribute
                {
                    Name = "Color Group",
                    Options = { new ColumbiaNamed { Name = "Black" } },
                },
            },
        };
        Assert.Equal("Black", ColumbiaCatalogMapper.BuildColorFamilies(p));
    }

    [Fact]
    public void StripHtml_RemovesTagsEntitiesAndCollapsesWhitespace()
    {
        var html = "<strong>Blue Wrinkle</strong>\r\n\r\nThis is a vibrant &amp; bright coating.";
        var text = ColumbiaCatalogMapper.StripHtml(html);
        Assert.Equal("Blue Wrinkle This is a vibrant & bright coating.", text);
    }

    [Fact]
    public void DetectRequiresClearCoat_ExplicitRequirement_IsTrue()
    {
        var p = new ColumbiaProduct
        {
            Description = "This powder requires a clear coat to activate the effect.",
        };
        Assert.True(ColumbiaCatalogMapper.DetectRequiresClearCoat(p));
    }

    [Fact]
    public void DetectRequiresClearCoat_IllusionLine_IsTrue()
    {
        var p = new ColumbiaProduct { Name = "Illusion Cherry", Description = "A translucent red." };
        Assert.True(ColumbiaCatalogMapper.DetectRequiresClearCoat(p));
    }

    [Fact]
    public void DetectRequiresClearCoat_PartialCureSchedule_IsTrue()
    {
        var p = new ColumbiaProduct
        {
            Name = "Some Base",
            CureSchedule = "Partial Cure: 15 min total time in oven preheated to 400°F. Then apply clear.",
        };
        Assert.True(ColumbiaCatalogMapper.DetectRequiresClearCoat(p));
    }

    [Fact]
    public void DetectRequiresClearCoat_CasualMention_IsFalse()
    {
        // The over-flagging case: a passing mention is not a requirement.
        var p = new ColumbiaProduct
        {
            Name = "Gloss Black",
            Description = "Durable gloss black. Apply a clear coat for added protection if desired.",
        };
        Assert.False(ColumbiaCatalogMapper.DetectRequiresClearCoat(p));
    }

    // ── End-to-end mapping invariants ─────────────────────────────────────

    // ── Tolerant image deserialization (WordPress returns [] / false when empty) ──

    private static readonly JsonSerializerOptions ClientJsonOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
        PropertyNameCaseInsensitive = true,
        Converters = { new ColumbiaImageJsonConverter() },
    };

    [Fact]
    public void Deserialize_FeaturedImageEmptyArray_YieldsNullImage()
    {
        // WordPress returns featured_image: [] for products with no image.
        var json = """{ "sku": "X1", "name": "No Image", "featured_image": [] }""";
        var product = JsonSerializer.Deserialize<ColumbiaProduct>(json, ClientJsonOptions);

        Assert.NotNull(product);
        Assert.Null(product!.FeaturedImage);
        Assert.Null(ColumbiaCatalogMapper.Map(product).ImageUrl);
    }

    [Fact]
    public void Deserialize_FeaturedImageObject_ParsesSrc()
    {
        var json = """{ "sku": "X1", "name": "Has Image", "featured_image": { "id": 5, "src": "https://x/img.png", "name": "i", "alt": "" } }""";
        var product = JsonSerializer.Deserialize<ColumbiaProduct>(json, ClientJsonOptions);

        Assert.Equal("https://x/img.png", product!.FeaturedImage!.Src);
        Assert.Equal("https://x/img.png", ColumbiaCatalogMapper.Map(product).ImageUrl);
    }

    [Fact]
    public void Map_AlwaysStampsSource_AndLeavesEnrichmentFieldsNull()
    {
        var p = new ColumbiaProduct { Sku = "X5004124", Name = "Blue Wrinkle", Price = "18.85" };
        var item = ColumbiaCatalogMapper.Map(p);

        Assert.Equal("Columbia Coatings API", item.Source);
        // Enrichment fields are not in the feed and must stay null for lazy TDS/AI enrichment.
        Assert.Null(item.SpecificGravity);
        Assert.Null(item.CoverageSqFtPerLb);
        Assert.Null(item.TransferEfficiency);
    }
}
