using Microsoft.EntityFrameworkCore;
using PowderCoating.Application.Interfaces;
using PowderCoating.Core.Entities;
using PowderCoating.Core.Enums;
using PowderCoating.Infrastructure.Data;
using PowderCoating.Infrastructure.Repositories;
using PowderCoating.Infrastructure.Services;
using Xunit;

namespace PowderCoating.UnitTests;

public class SubscriptionServiceTests
{
    [Fact]
    public async Task GetUserCountAsync_PrefersCompanyOverrideOverPlanDefault()
    {
        await using var context = CreateContext();
        SeedCompanyAndPlan(context, companyId: 7, plan: 1, maxUsers: 3);
        var company = context.Companies.Local.Single(c => c.Id == 7);
        company.MaxUsersOverride = 7;

        context.Users.AddRange(
            new ApplicationUser { Id = "u1", CompanyId = 7, UserName = "u1", Email = "u1@example.com", FirstName = "A", LastName = "One", IsActive = true },
            new ApplicationUser { Id = "u2", CompanyId = 7, UserName = "u2", Email = "u2@example.com", FirstName = "B", LastName = "Two", IsActive = true });
        await context.SaveChangesAsync();

        var service = new SubscriptionService(new UnitOfWork(context), context, new NullPlatformSettingsService());

        var (used, max) = await service.GetUserCountAsync(7);

        Assert.Equal(2, used);
        Assert.Equal(7, max);
    }

    [Fact]
    public async Task GetJobCountAsync_ExcludesTerminalStatuses()
    {
        await using var context = CreateContext();
        SeedCompanyAndPlan(context, companyId: 8, plan: 2, maxActiveJobs: 50);
        SeedJobStatuses(context, 8);
        context.Jobs.AddRange(
            new Job { Id = 1, CompanyId = 8, JobNumber = "JOB-1", CustomerId = 1, Description = "Active", JobStatusId = 1, JobPriorityId = 1 },
            new Job { Id = 2, CompanyId = 8, JobNumber = "JOB-2", CustomerId = 1, Description = "Done", JobStatusId = 2, JobPriorityId = 1 },
            new Job { Id = 3, CompanyId = 8, JobNumber = "JOB-3", CustomerId = 1, Description = "Delivered", JobStatusId = 3, JobPriorityId = 1 });
        await context.SaveChangesAsync();

        var service = new SubscriptionService(new UnitOfWork(context), context, new NullPlatformSettingsService());

        var (used, max) = await service.GetJobCountAsync(8);

        Assert.Equal(1, used);
        Assert.Equal(50, max);
    }

    [Fact]
    public async Task GetQuoteCountAsync_CountsOnlyCurrentMonth()
    {
        await using var context = CreateContext();
        SeedCompanyAndPlan(context, companyId: 9, plan: 3, maxQuotes: 5);
        var currentQuote = new Quote
        {
            Id = 1,
            CompanyId = 9,
            QuoteNumber = "Q-001",
            QuoteStatusId = 1
        };
        var oldQuote = new Quote
        {
            Id = 2,
            CompanyId = 9,
            QuoteNumber = "Q-OLD",
            QuoteStatusId = 1
        };
        context.Quotes.AddRange(currentQuote, oldQuote);
        await context.SaveChangesAsync();

        oldQuote.CreatedAt = DateTime.UtcNow.AddMonths(-1);
        await context.SaveChangesAsync();

        var service = new SubscriptionService(new UnitOfWork(context), context, new NullPlatformSettingsService());

        var (used, max) = await service.GetQuoteCountAsync(9);

        Assert.Equal(1, used);
        Assert.Equal(5, max);
    }

    [Fact]
    public async Task CanAddCustomerAsync_CompedCompany_BypassesPlanLimits()
    {
        await using var context = CreateContext();
        SeedCompanyAndPlan(context, companyId: 10, plan: 4, maxCustomers: 0);
        var company = await context.Companies.FindAsync(10);
        company!.IsComped = true;
        context.Customers.Add(new Customer { Id = 1, CompanyId = 10, CompanyName = "Customer A" });
        await context.SaveChangesAsync();

        var service = new SubscriptionService(new UnitOfWork(context), context, new NullPlatformSettingsService());

        var allowed = await service.CanAddCustomerAsync(10);

        Assert.True(allowed);
    }

    [Fact]
    public async Task CanUseAiPhotoQuoteAsync_RequiresFeatureEnabledAndQuotaAvailable()
    {
        await using var context = CreateContext();
        SeedCompanyAndPlan(context, companyId: 11, plan: 5, maxAiPhotoQuotesPerMonth: 2, allowAiPhotoQuotes: true);
        context.AiItemPredictions.Add(new AiItemPrediction { Id = 1, CompanyId = 11, CreatedAt = DateTime.UtcNow.AddDays(-1) });
        await context.SaveChangesAsync();

        var service = new SubscriptionService(new UnitOfWork(context), context, new NullPlatformSettingsService());

        var allowed = await service.CanUseAiPhotoQuoteAsync(11);

        Assert.True(allowed);
    }

    [Fact]
    public async Task CanUseAiPhotoQuoteAsync_ReturnsFalse_WhenPlanDisablesFeature()
    {
        await using var context = CreateContext();
        SeedCompanyAndPlan(context, companyId: 12, plan: 6, maxAiPhotoQuotesPerMonth: 10, allowAiPhotoQuotes: false);
        await context.SaveChangesAsync();

        var service = new SubscriptionService(new UnitOfWork(context), context, new NullPlatformSettingsService());

        var allowed = await service.CanUseAiPhotoQuoteAsync(12);

        Assert.False(allowed);
    }

    [Fact]
    public async Task GetStatusAsync_ReturnsGracePeriod_WhenSubscriptionRecentlyExpired()
    {
        await using var context = CreateContext();
        context.Companies.Add(new Company
        {
            Id = 20,
            CompanyId = 20,
            CompanyName = "Grace Co",
            PrimaryContactName = "Owner",
            PrimaryContactEmail = "grace@example.com",
            SubscriptionStatus = SubscriptionStatus.Active,
            SubscriptionEndDate = DateTime.UtcNow.Date.AddDays(-5),
            StripeSubscriptionId = "sub_grace_test",   // paid sub — qualifies for grace period
            IsActive = true
        });
        await context.SaveChangesAsync();

        var service = new SubscriptionService(new UnitOfWork(context), context, new NullPlatformSettingsService());

        var status = await service.GetStatusAsync(20);

        Assert.Equal(SubscriptionStatus.GracePeriod, status);
    }

    [Fact]
    public async Task GetStatusAsync_ReturnsExpired_WhenPastGraceWindow()
    {
        await using var context = CreateContext();
        context.Companies.Add(new Company
        {
            Id = 21,
            CompanyId = 21,
            CompanyName = "Expired Co",
            PrimaryContactName = "Owner",
            PrimaryContactEmail = "expired@example.com",
            SubscriptionStatus = SubscriptionStatus.Active,
            SubscriptionEndDate = DateTime.UtcNow.Date.AddDays(-15),
            StripeSubscriptionId = "sub_expired_test",  // paid sub — 15 days > 14-day grace window
            IsActive = true
        });
        await context.SaveChangesAsync();

        var service = new SubscriptionService(new UnitOfWork(context), context, new NullPlatformSettingsService());

        var status = await service.GetStatusAsync(21);

        Assert.Equal(SubscriptionStatus.Expired, status);
    }

    [Fact]
    public async Task GetStatusAsync_ReturnsActive_ForCompedCompanyEvenWhenExpired()
    {
        await using var context = CreateContext();
        context.Companies.Add(new Company
        {
            Id = 22,
            CompanyId = 22,
            CompanyName = "Comped Co",
            PrimaryContactName = "Owner",
            PrimaryContactEmail = "comped@example.com",
            SubscriptionStatus = SubscriptionStatus.Active,
            SubscriptionEndDate = DateTime.UtcNow.Date.AddDays(-30),
            IsActive = true,
            IsComped = true
        });
        await context.SaveChangesAsync();

        var service = new SubscriptionService(new UnitOfWork(context), context, new NullPlatformSettingsService());

        var status = await service.GetStatusAsync(22);

        Assert.Equal(SubscriptionStatus.Active, status);
    }

    [Fact]
    public async Task IsAiInventoryAssistEnabledAsync_RequiresCompanyToggle()
    {
        await using var context = CreateContext();
        SeedCompanyAndPlan(context, companyId: 13, plan: 7, allowAiInventoryAssist: true);
        var company = await context.Companies.FindAsync(13);
        company!.AiInventoryAssistEnabled = false;
        await context.SaveChangesAsync();

        var service = new SubscriptionService(new UnitOfWork(context), context, new NullPlatformSettingsService());

        var enabled = await service.IsAiInventoryAssistEnabledAsync(13);

        Assert.False(enabled);
    }

    [Fact]
    public async Task GetJobPhotoCountAsync_ExcludesAiAnalysisPhotos()
    {
        await using var context = CreateContext();
        SeedCompanyAndPlan(context, companyId: 14, plan: 8, maxJobPhotos: 5);
        context.JobPhotos.AddRange(
            new JobPhoto
            {
                Id = 1,
                CompanyId = 14,
                JobId = 100,
                FilePath = "jobs/100/1.jpg",
                FileName = "1.jpg",
                FileSize = 100,
                ContentType = "image/jpeg",
                UploadedById = "u1"
            },
            new JobPhoto
            {
                Id = 2,
                CompanyId = 14,
                JobId = 100,
                FilePath = "jobs/100/2.jpg",
                FileName = "2.jpg",
                FileSize = 100,
                ContentType = "image/jpeg",
                UploadedById = "u1",
                IsAiAnalysisPhoto = true
            });
        await context.SaveChangesAsync();

        var service = new SubscriptionService(new UnitOfWork(context), context, new NullPlatformSettingsService());

        var (used, max) = await service.GetJobPhotoCountAsync(14, 100);

        Assert.Equal(1, used);
        Assert.Equal(5, max);
    }

    [Fact]
    public async Task GetQuotePhotoCountAsync_ExcludesAiAnalysisPhotos()
    {
        await using var context = CreateContext();
        SeedCompanyAndPlan(context, companyId: 15, plan: 9, maxQuotePhotos: 4);
        context.QuotePhotos.AddRange(
            new QuotePhoto
            {
                Id = 1,
                CompanyId = 15,
                QuoteId = 200,
                TempId = "temp-1",
                FilePath = "quotes/200/1.jpg",
                FileName = "1.jpg",
                FileSize = 100,
                ContentType = "image/jpeg",
                IsAiAnalysisPhoto = false
            },
            new QuotePhoto
            {
                Id = 2,
                CompanyId = 15,
                QuoteId = 200,
                TempId = "temp-2",
                FilePath = "quotes/200/2.jpg",
                FileName = "2.jpg",
                FileSize = 100,
                ContentType = "image/jpeg",
                IsAiAnalysisPhoto = true
            });
        await context.SaveChangesAsync();

        var service = new SubscriptionService(new UnitOfWork(context), context, new NullPlatformSettingsService());

        var (used, max) = await service.GetQuotePhotoCountAsync(15, 200);

        Assert.Equal(1, used);
        Assert.Equal(4, max);
    }

    [Fact]
    public async Task CanUseAiPhotoQuoteAsync_ReturnsFalse_WhenUsageEqualsLimit()
    {
        await using var context = CreateContext();
        SeedCompanyAndPlan(context, companyId: 16, plan: 10, maxAiPhotoQuotesPerMonth: 2, allowAiPhotoQuotes: true);
        context.AiItemPredictions.AddRange(
            new AiItemPrediction { Id = 1, CompanyId = 16, CreatedAt = DateTime.UtcNow.AddDays(-1) },
            new AiItemPrediction { Id = 2, CompanyId = 16, CreatedAt = DateTime.UtcNow.AddDays(-2) });
        await context.SaveChangesAsync();

        var service = new SubscriptionService(new UnitOfWork(context), context, new NullPlatformSettingsService());

        var allowed = await service.CanUseAiPhotoQuoteAsync(16);

        Assert.False(allowed);
    }

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

        return new ApplicationDbContext(options);
    }

    private static void SeedCompanyAndPlan(
        ApplicationDbContext context,
        int companyId,
        int plan,
        int maxUsers = -1,
        int maxActiveJobs = -1,
        int maxCustomers = -1,
        int maxQuotes = -1,
        int maxAiPhotoQuotesPerMonth = -1,
        int maxJobPhotos = -1,
        int maxQuotePhotos = -1,
        bool allowAiPhotoQuotes = true,
        bool allowAiInventoryAssist = true)
    {
        context.Companies.Add(new Company
        {
            Id = companyId,
            CompanyId = companyId,
            CompanyName = $"Company {companyId}",
            PrimaryContactName = "Owner",
            PrimaryContactEmail = $"owner{companyId}@example.com",
            SubscriptionPlan = plan,
            SubscriptionStatus = SubscriptionStatus.Active,
            IsActive = true
        });

        context.SubscriptionPlanConfigs.Add(new SubscriptionPlanConfig
        {
            Id = companyId,
            CompanyId = 0,
            Plan = plan,
            DisplayName = $"Plan {plan}",
            IsActive = true,
            MaxUsers = maxUsers,
            MaxActiveJobs = maxActiveJobs,
            MaxCustomers = maxCustomers,
            MaxQuotes = maxQuotes,
            MaxJobPhotos = maxJobPhotos,
            MaxQuotePhotos = maxQuotePhotos,
            MaxAiPhotoQuotesPerMonth = maxAiPhotoQuotesPerMonth,
            AllowAiPhotoQuotes = allowAiPhotoQuotes,
            AllowAiInventoryAssist = allowAiInventoryAssist
        });

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

    private static void SeedJobStatuses(ApplicationDbContext context, int companyId)
    {
        context.JobStatusLookups.AddRange(
            new JobStatusLookup
            {
                Id = 1,
                CompanyId = companyId,
                StatusCode = "Pending",
                DisplayName = "Pending",
                DisplayOrder = 1,
                IsTerminalStatus = false
            },
            new JobStatusLookup
            {
                Id = 2,
                CompanyId = companyId,
                StatusCode = "Completed",
                DisplayName = "Completed",
                DisplayOrder = 2,
                IsTerminalStatus = true
            },
            new JobStatusLookup
            {
                Id = 3,
                CompanyId = companyId,
                StatusCode = "Delivered",
                DisplayName = "Delivered",
                DisplayOrder = 3,
                IsTerminalStatus = true
            });
    }
}

/// <summary>No-op stub for IPlatformSettingsService — returns defaults for all queries.</summary>
file sealed class NullPlatformSettingsService : IPlatformSettingsService
{
    public Task<string?> GetAsync(string key) => Task.FromResult<string?>(null);
    public Task<bool> GetBoolAsync(string key, bool defaultValue = false) => Task.FromResult(defaultValue);
    public Task<int> GetIntAsync(string key, int defaultValue) => Task.FromResult(defaultValue);
    public Task SetAsync(string key, string? value, string? updatedBy = null) => Task.CompletedTask;
    public Task<IReadOnlyList<PlatformSetting>> GetAllAsync() => Task.FromResult<IReadOnlyList<PlatformSetting>>(Array.Empty<PlatformSetting>().ToList());
}
