using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Moq;
using PowderCoating.Core.Entities;
using PowderCoating.Infrastructure.Data;
using PowderCoating.Infrastructure.Repositories;
using PowderCoating.Infrastructure.Services;

namespace PowderCoating.UnitTests;

/// <summary>
/// Verifies that catalog sync propagation updates a linked inventory item's quoting reference price
/// and product data, while never touching the tenant-owned cost basis, quantity, notes, or image.
/// </summary>
public class PowderCatalogPropagationTests
{
    [Fact]
    public async Task Propagate_UpdatesReferencePriceAndSpecs_ButNotCostQuantityNotesOrImage()
    {
        await using var context = CreateContext();

        var catalog = new PowderCatalogItem
        {
            VendorName = "Columbia Coatings",
            Sku = "CS1693053",
            ColorName = "Joker Jewel",
            Source = "Columbia Coatings API",
            UnitPrice = 28m,                // new catalog price
            SdsUrl = "https://cc/sds.pdf",
            TdsUrl = "https://cc/tds.pdf",
            CureTemperatureF = 400m,
            CureTimeMinutes = 10,
            ColorFamilies = "Green,Purple",
        };
        context.PowderCatalogItems.Add(catalog);
        await context.SaveChangesAsync();

        var inv = new InventoryItem
        {
            CompanyId = 1,
            SKU = "POWD-2606-0001",
            Name = "Joker Jewel",
            PowderCatalogItemId = catalog.Id,
            UnitCost = 20m,                 // what they actually paid
            AverageCost = 20m,
            LastPurchasePrice = 20m,
            QuantityOnHand = 5m,
            Notes = "keep my note",
            ImageUrl = "my-own-photo.jpg",
            CatalogReferencePrice = null,   // not yet set
        };
        context.InventoryItems.Add(inv);
        await context.SaveChangesAsync();

        var service = new PowderCatalogUpsertService(
            new UnitOfWork(context),
            Mock.Of<ILogger<PowderCatalogUpsertService>>());

        var updated = await service.PropagateToLinkedInventoryAsync();

        Assert.Equal(1, updated);

        var refreshed = await context.InventoryItems.FindAsync(inv.Id);
        Assert.NotNull(refreshed);

        // Quoting reference price + product data refreshed from the catalog.
        Assert.Equal(28m, refreshed!.CatalogReferencePrice);
        Assert.NotNull(refreshed.CatalogPriceUpdatedAt);
        Assert.Equal("https://cc/sds.pdf", refreshed.SdsUrl);
        Assert.Equal("https://cc/tds.pdf", refreshed.TdsUrl);
        Assert.Equal(400m, refreshed.CureTemperatureF);
        Assert.Equal("Green,Purple", refreshed.ColorFamilies);

        // Tenant-owned fields untouched.
        Assert.Equal(20m, refreshed.UnitCost);
        Assert.Equal(20m, refreshed.AverageCost);
        Assert.Equal(20m, refreshed.LastPurchasePrice);
        Assert.Equal(5m, refreshed.QuantityOnHand);
        Assert.Equal("keep my note", refreshed.Notes);
        Assert.Equal("my-own-photo.jpg", refreshed.ImageUrl);
    }

    [Fact]
    public async Task Propagate_DoesNotSetReferencePrice_WhenCatalogPriceIsZero()
    {
        await using var context = CreateContext();

        var catalog = new PowderCatalogItem
        {
            VendorName = "Columbia Coatings",
            Sku = "X1",
            ColorName = "No Price",
            Source = "Columbia Coatings API",
            UnitPrice = 0m, // unknown price — must not wipe quoting with $0
        };
        context.PowderCatalogItems.Add(catalog);
        await context.SaveChangesAsync();

        var inv = new InventoryItem
        {
            CompanyId = 1,
            SKU = "POWD-2606-0002",
            Name = "No Price",
            PowderCatalogItemId = catalog.Id,
            UnitCost = 15m,
            CatalogReferencePrice = null,
        };
        context.InventoryItems.Add(inv);
        await context.SaveChangesAsync();

        var service = new PowderCatalogUpsertService(
            new UnitOfWork(context),
            Mock.Of<ILogger<PowderCatalogUpsertService>>());

        await service.PropagateToLinkedInventoryAsync();

        var refreshed = await context.InventoryItems.FindAsync(inv.Id);
        Assert.Null(refreshed!.CatalogReferencePrice); // stays null -> quoting falls back to UnitCost
    }

    [Fact]
    public async Task Propagate_LinksUnlinkedItem_ByManufacturerAndPartNumber()
    {
        await using var context = CreateContext();

        var catalog = new PowderCatalogItem
        {
            VendorName = "Columbia Coatings",
            Sku = "CS1693053",
            ColorName = "Joker Jewel",
            Source = "Columbia Coatings API",
            UnitPrice = 28m,
        };
        context.PowderCatalogItems.Add(catalog);
        await context.SaveChangesAsync();

        var inv = new InventoryItem
        {
            CompanyId = 1,
            SKU = "POWD-2606-0009",
            Name = "Joker Jewel",
            Manufacturer = "Columbia Coatings",
            ManufacturerPartNumber = "CS1693053", // matches catalog SKU
            PowderCatalogItemId = null,            // not linked yet (legacy item)
            UnitCost = 20m,
        };
        context.InventoryItems.Add(inv);
        await context.SaveChangesAsync();

        var service = new PowderCatalogUpsertService(
            new UnitOfWork(context),
            Mock.Of<ILogger<PowderCatalogUpsertService>>());

        await service.PropagateToLinkedInventoryAsync();

        var refreshed = await context.InventoryItems.FindAsync(inv.Id);
        Assert.Equal(catalog.Id, refreshed!.PowderCatalogItemId);   // self-healed link
        Assert.Equal(28m, refreshed.CatalogReferencePrice);          // and got the price
        Assert.Equal(20m, refreshed.UnitCost);                       // cost untouched
    }

    [Fact]
    public async Task Propagate_DoesNotLink_WhenPartNumberDoesNotMatch()
    {
        await using var context = CreateContext();

        context.PowderCatalogItems.Add(new PowderCatalogItem
        {
            VendorName = "Columbia Coatings",
            Sku = "CS1693053",
            ColorName = "Joker Jewel",
            UnitPrice = 28m,
        });
        await context.SaveChangesAsync();

        var inv = new InventoryItem
        {
            CompanyId = 1,
            SKU = "POWD-2606-0010",
            Name = "Something Else",
            Manufacturer = "Columbia Coatings",
            ManufacturerPartNumber = "NOPE-999", // no catalog match
            PowderCatalogItemId = null,
        };
        context.InventoryItems.Add(inv);
        await context.SaveChangesAsync();

        var service = new PowderCatalogUpsertService(
            new UnitOfWork(context),
            Mock.Of<ILogger<PowderCatalogUpsertService>>());

        await service.PropagateToLinkedInventoryAsync();

        var refreshed = await context.InventoryItems.FindAsync(inv.Id);
        Assert.Null(refreshed!.PowderCatalogItemId); // stays unlinked
    }

    private static ApplicationDbContext CreateContext()
    {
        var options = new DbContextOptionsBuilder<ApplicationDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .Options;

        var identity = new ClaimsIdentity([new Claim(ClaimTypes.Role, "SuperAdmin")], "Test");
        var principal = new ClaimsPrincipal(identity);

        byte[]? noBytes = null;
        var sessionMock = new Mock<ISession>();
        sessionMock.Setup(s => s.TryGetValue(It.IsAny<string>(), out noBytes)).Returns(false);

        var httpContextMock = new Mock<HttpContext>();
        httpContextMock.SetupGet(c => c.User).Returns(principal);
        httpContextMock.SetupGet(c => c.Session).Returns(sessionMock.Object);

        var accessor = new Mock<IHttpContextAccessor>();
        accessor.SetupGet(a => a.HttpContext).Returns(httpContextMock.Object);

        return new ApplicationDbContext(options, accessor.Object, null!);
    }
}
