using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Moq;
using PowderCoating.Application.DTOs.Job;
using PowderCoating.Application.Interfaces;
using PowderCoating.Application.Services;
using PowderCoating.Core.Entities;
using PowderCoating.Core.Enums;
using PowderCoating.Core.Interfaces;
using PowderCoating.Infrastructure.Data;
using PowderCoating.Infrastructure.Repositories;
using PowderCoating.Web.Controllers;
using PowderCoating.Web.Hubs;

namespace PowderCoating.UnitTests;

public class QuoteAndReworkControllerFlowTests
{
    [Fact]
    public async Task UpdateQuoteStatus_ApprovedQuote_CopiesItemLevelFieldsIntoCreatedJob()
    {
        await using var context = CreateContext();
        SeedQuoteConversionData(context);
        await context.SaveChangesAsync();

        var quoteStatuses = await context.QuoteStatusLookups.OrderBy(s => s.Id).ToListAsync();
        var lookupCache = new Mock<ILookupCacheService>();
        lookupCache
            .Setup(x => x.GetQuoteStatusLookupsAsync(1))
            .ReturnsAsync(quoteStatuses);
        var tenantContext = CreateTenantContext();

        var controller = new QuotesController(
            new UnitOfWork(context),
            Mock.Of<AutoMapper.IMapper>(),
            Mock.Of<IPricingCalculationService>(),
            CreateUserManager().Object,
            Mock.Of<ILogger<QuotesController>>(),
            Mock.Of<IPdfService>(),
            tenantContext.Object,
            Mock.Of<IMeasurementConversionService>(),
            lookupCache.Object,
            Mock.Of<INotificationService>(),
            Mock.Of<ISubscriptionService>(),
            new JobItemAssemblyService(),
            Mock.Of<IQuotePricingAssemblyService>(),
            new ConfigurationBuilder().Build(),
            Mock.Of<IPlatformSettingsService>(),
            Mock.Of<IQuotePhotoService>(),
            Mock.Of<IAiQuoteService>(),
            Mock.Of<IWebHostEnvironment>(),
            Mock.Of<IJobPhotoService>(),
            Mock.Of<IAiUsageLogger>(),
            Mock.Of<ICompanyLogoService>(),
            Mock.Of<IInventoryAiLookupService>());

        var result = await controller.UpdateQuoteStatus(new UpdateQuoteStatusRequest
        {
            QuoteId = 1,
            StatusId = 2
        });

        Assert.IsType<JsonResult>(result);

        var quote = await context.Quotes.SingleAsync();
        Assert.Equal(3, quote.QuoteStatusId);
        Assert.True(quote.ConvertedToJobId.HasValue);

        var job = await context.Jobs.SingleAsync();
        Assert.Equal(quote.Id, job.QuoteId);

        var jobItem = await context.JobItems.SingleAsync();
        Assert.True(jobItem.IsSalesItem);
        Assert.Equal("MUG-01", jobItem.Sku);
        Assert.False(jobItem.IncludePrepCost);

        var jobCoat = await context.JobItemCoats.SingleAsync();
        Assert.Equal(1.5m, jobCoat.PowderToOrder);
        Assert.Equal(50, jobCoat.InventoryItemId);

        var jobItemPrep = await context.JobItemPrepServices.SingleAsync();
        Assert.Equal(77, jobItemPrep.BlastSetupId);

        var jobPrep = await context.JobPrepServices.SingleAsync();
        Assert.Equal(5, jobPrep.PrepServiceId);
    }

    [Fact]
    public async Task AddReworkRecord_CopiesFullItemShapeToReworkJob()
    {
        await using var context = CreateContext();
        SeedReworkData(context);
        await context.SaveChangesAsync();

        var lookupCache = new Mock<ILookupCacheService>();
        lookupCache
            .Setup(x => x.GetJobStatusLookupsAsync(1))
            .ReturnsAsync(await context.JobStatusLookups.ToListAsync());
        lookupCache
            .Setup(x => x.GetJobPriorityLookupsAsync(1))
            .ReturnsAsync(await context.JobPriorityLookups.ToListAsync());
        var tenantContext = CreateTenantContext();

        var mapper = new Mock<AutoMapper.IMapper>();
        mapper
            .Setup(x => x.Map<ReworkRecordDto>(It.IsAny<object>()))
            .Returns<object>(source =>
            {
                var record = Assert.IsType<ReworkRecord>(source);
                return new ReworkRecordDto
                {
                    Id = record.Id,
                    JobId = record.JobId,
                    JobItemId = record.JobItemId,
                    ReworkJobId = record.ReworkJobId
                };
            });

        var controller = new JobsController(
            new UnitOfWork(context),
            mapper.Object,
            Mock.Of<IJobPhotoService>(),
            CreateUserManager().Object,
            Mock.Of<ILogger<JobsController>>(),
            tenantContext.Object,
            Mock.Of<IMeasurementConversionService>(),
            lookupCache.Object,
            Mock.Of<INotificationService>(),
            Mock.Of<ISubscriptionService>(),
            Mock.Of<IPricingCalculationService>(),
            new JobItemAssemblyService(),
            Mock.Of<IHubContext<NotificationHub>>(),
            Mock.Of<IHubContext<ShopHub>>(),
            Mock.Of<IAccountBalanceService>());

        var result = await controller.AddReworkRecord(new CreateReworkRecordDto
        {
            JobId = 1,
            JobItemId = 10,
            ReworkType = ReworkType.InternalDefect,
            Reason = ReworkReason.InsufficientCoverage,
            DefectDescription = "Thin coverage on one edge",
            DiscoveredBy = ReworkDiscoveredBy.Internal,
            DiscoveredDate = new DateTime(2026, 5, 9),
            EstimatedReworkCost = 65m,
            CreateReworkJob = true,
            ReworkJobItemIds = [10],
            ReworkPricingType = ReworkPricingType.CustomerFull
        });

        Assert.IsType<JsonResult>(result);

        var reworkJob = await context.Jobs.SingleAsync(j => j.IsReworkJob);
        Assert.Equal(1, reworkJob.OriginalJobId);
        Assert.Equal("JOB-2605-0001-R1", reworkJob.JobNumber);
        Assert.Equal(2, reworkJob.JobStatusId); // first non-Pending status

        var reworkItem = await context.JobItems.SingleAsync(i => i.JobId == reworkJob.Id);
        Assert.True(reworkItem.IsSalesItem);
        Assert.Equal("GATE-BRZ", reworkItem.Sku);
        Assert.False(reworkItem.IncludePrepCost);
        Assert.Equal(140m, reworkItem.UnitPrice);
        Assert.Equal(140m, reworkItem.TotalPrice);

        var reworkCoat = await context.JobItemCoats.SingleAsync(c => c.JobItemId == reworkItem.Id);
        Assert.Equal(2.75m, reworkCoat.PowderToOrder);

        var reworkPrep = await context.JobItemPrepServices.SingleAsync(p => p.JobItemId == reworkItem.Id);
        Assert.Equal(88, reworkPrep.BlastSetupId);
    }

    private static void SeedQuoteConversionData(ApplicationDbContext context)
    {
        context.Customers.Add(new Customer
        {
            Id = 1,
            CompanyId = 1,
            CompanyName = "Acme Fabrication"
        });

        context.InventoryItems.Add(new InventoryItem
        {
            Id = 50,
            CompanyId = 1,
            SKU = "POW-1",
            Name = "Gloss Black",
            ColorCode = "RAL9005",
            Finish = "Gloss",
            Category = "Powder",
            UnitOfMeasure = "lbs"
        });

        context.QuoteStatusLookups.AddRange(
            new QuoteStatusLookup { Id = 1, CompanyId = 1, StatusCode = "DRAFT", DisplayName = "Draft" },
            new QuoteStatusLookup { Id = 2, CompanyId = 1, StatusCode = "APPROVED", DisplayName = "Approved" },
            new QuoteStatusLookup { Id = 3, CompanyId = 1, StatusCode = "CONVERTED", DisplayName = "Converted" });

        context.JobStatusLookups.Add(new JobStatusLookup
        {
            Id = 10,
            CompanyId = 1,
            StatusCode = "APPROVED",
            DisplayName = "Approved"
        });

        context.JobPriorityLookups.AddRange(
            new JobPriorityLookup { Id = 20, CompanyId = 1, PriorityCode = "NORMAL", DisplayName = "Normal" },
            new JobPriorityLookup { Id = 21, CompanyId = 1, PriorityCode = "RUSH", DisplayName = "Rush" });

        context.PrepServices.Add(new PrepService
        {
            Id = 5,
            CompanyId = 1,
            ServiceName = "Sandblasting",
            DisplayOrder = 1,
            IsActive = true,
            RequiresBlastSetup = true
        });

        context.Quotes.Add(new Quote
        {
            Id = 1,
            CompanyId = 1,
            QuoteNumber = "Q-1001",
            CustomerId = 1,
            QuoteStatusId = 1,
            Total = 50m,
            ShopSuppliesAmount = 2m,
            ShopSuppliesPercent = 4m
        });

        context.QuoteItems.Add(new QuoteItem
        {
            Id = 100,
            QuoteId = 1,
            CompanyId = 1,
            Description = "Merch mug",
            Quantity = 2m,
            SurfaceAreaSqFt = 10m,
            IsSalesItem = true,
            Sku = "MUG-01",
            UnitPrice = 25m,
            TotalPrice = 50m,
            IncludePrepCost = false,
            EstimatedMinutes = 12
        });

        context.QuoteItemCoats.Add(new QuoteItemCoat
        {
            Id = 101,
            QuoteItemId = 100,
            CompanyId = 1,
            CoatName = "Top Coat",
            Sequence = 1,
            InventoryItemId = 50,
            ColorName = "Old Name",
            ColorCode = "OLD",
            Finish = "Old",
            CoverageSqFtPerLb = 30m,
            TransferEfficiency = 65m,
            PowderToOrder = 1.5m
        });

        context.QuoteItemPrepServices.Add(new QuoteItemPrepService
        {
            Id = 102,
            QuoteItemId = 100,
            CompanyId = 1,
            PrepServiceId = 5,
            EstimatedMinutes = 9,
            BlastSetupId = 77
        });
    }

    private static void SeedReworkData(ApplicationDbContext context)
    {
        context.Customers.Add(new Customer
        {
            Id = 1,
            CompanyId = 1,
            CompanyName = "Acme Fabrication"
        });

        context.JobStatusLookups.AddRange(
            new JobStatusLookup { Id = 1, CompanyId = 1, StatusCode = "PENDING",        DisplayName = "Pending",        DisplayOrder = 1 },
            new JobStatusLookup { Id = 2, CompanyId = 1, StatusCode = "IN_PREPARATION", DisplayName = "In Preparation", DisplayOrder = 2 });

        context.JobPriorityLookups.Add(new JobPriorityLookup
        {
            Id = 2,
            CompanyId = 1,
            PriorityCode = "NORMAL",
            DisplayName = "Normal"
        });

        context.Jobs.Add(new Job
        {
            Id = 1,
            CompanyId = 1,
            JobNumber = "JOB-2605-0001",
            Description = "Original gate job",
            CustomerId = 1,
            JobStatusId = 1,
            JobPriorityId = 2
        });

        context.JobItems.Add(new JobItem
        {
            Id = 10,
            JobId = 1,
            CompanyId = 1,
            Description = "Gate panel",
            Quantity = 1m,
            SurfaceArea = 22m,
            SurfaceAreaSqFt = 22m,
            CatalogItemId = 8,
            IsSalesItem = true,
            Sku = "GATE-BRZ",
            UnitPrice = 140m,
            TotalPrice = 140m,
            LaborCost = 56m,
            IncludePrepCost = false,
            EstimatedMinutes = 90,
            ColorName = "Bronze",
            ColorCode = "BZ-22",
            Finish = "Textured"
        });

        context.JobItemCoats.Add(new JobItemCoat
        {
            Id = 11,
            JobItemId = 10,
            CompanyId = 1,
            CoatName = "Top Coat",
            Sequence = 1,
            CoverageSqFtPerLb = 26m,
            TransferEfficiency = 70m,
            PowderToOrder = 2.75m
        });

        context.JobItemPrepServices.Add(new JobItemPrepService
        {
            Id = 12,
            JobItemId = 10,
            CompanyId = 1,
            PrepServiceId = 3,
            EstimatedMinutes = 45,
            BlastSetupId = 88
        });
    }

    private static Mock<UserManager<ApplicationUser>> CreateUserManager()
    {
        var store = new Mock<IUserStore<ApplicationUser>>();
        return new Mock<UserManager<ApplicationUser>>(
            store.Object,
            null!,
            null!,
            null!,
            null!,
            null!,
            null!,
            null!,
            null!);
    }

    private static Mock<ITenantContext> CreateTenantContext()
    {
        var tenantContext = new Mock<ITenantContext>();
        tenantContext.Setup(x => x.GetCurrentCompanyId()).Returns(1);
        tenantContext.Setup(x => x.IsSuperAdmin()).Returns(true);
        tenantContext.Setup(x => x.IsPlatformAdmin()).Returns(true);
        return tenantContext;
    }

    private static ApplicationDbContext CreateContext()
    {
        var options = new DbContextOptionsBuilder<ApplicationDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .ConfigureWarnings(x => x.Ignore(InMemoryEventId.TransactionIgnoredWarning))
            .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!);
    }
}
