using HoneyBox.Core.Services;
using HoneyBox.Model.Data;
using HoneyBox.Model.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;

namespace HoneyBox.Tests.Integration;

/// <summary>
/// 优惠券服务集成测试
/// Requirements: 3.1-7.5
/// </summary>
public class CouponServiceIntegrationTests
{
    private HoneyBoxDbContext CreateInMemoryDbContext()
    {
        var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .ConfigureWarnings(w => w.Ignore(InMemoryEventId.TransactionIgnoredWarning))
            .Options;
        return new HoneyBoxDbContext(options);
    }

    private CouponService CreateCouponService(HoneyBoxDbContext dbContext)
    {
        var mockLogger = new Mock<ILogger<CouponService>>();
        return new CouponService(dbContext, mockLogger.Object);
    }

    private User CreateTestUser(int id, decimal integral = 1000)
    {
        return new User
        {
            Id = id,
            OpenId = $"openid_{Guid.NewGuid():N}",
            Uid = $"uid_{Guid.NewGuid():N}",
            Nickname = "测试用户",
            HeadImg = "",
            Integral = integral,
            CreatedAt = DateTime.Now,
            UpdatedAt = DateTime.Now
        };
    }

    [Fact]
    public async Task GetCouponList_UnusedStatus_ReturnsUnusedCoupons()
    {
        var dbContext = CreateInMemoryDbContext();
        var service = CreateCouponService(dbContext);
        var userId = 1;

        await dbContext.UserCoupons.AddRangeAsync(new List<UserCoupon>
        {
            new() { UserId = userId, Level = 3, Num = 100, Status = 1, Type = 1, Title = "高级赏券", FromId = "0", CreatedAt = DateTime.Now },
            new() { UserId = userId, Level = 4, Num = 50, Status = 1, Type = 1, Title = "普通赏券", FromId = "0", CreatedAt = DateTime.Now },
            new() { UserId = userId, Level = 3, Num = 80, Status = 2, Type = 1, Title = "已分享券", FromId = "0", ShareTime = DateTime.Now, CreatedAt = DateTime.Now }
        });
        await dbContext.Users.AddAsync(CreateTestUser(userId));
        await dbContext.SaveChangesAsync();

        var result = await service.GetCouponListAsync(userId, 1, 1, 15);
        Assert.Equal(2, result.List.Count);
    }

    [Fact]
    public async Task GetCouponList_SharedStatus_ReturnsSharedCoupons()
    {
        var dbContext = CreateInMemoryDbContext();
        var service = CreateCouponService(dbContext);
        var userId = 1;

        await dbContext.UserCoupons.AddRangeAsync(new List<UserCoupon>
        {
            new() { UserId = userId, Level = 3, Num = 100, Status = 1, Type = 1, Title = "未分享券", FromId = "0", CreatedAt = DateTime.Now },
            new() { UserId = userId, Level = 3, Num = 80, Status = 2, Type = 1, Title = "已分享券1", FromId = "0", ShareTime = DateTime.Now.AddHours(-1), CreatedAt = DateTime.Now },
            new() { UserId = userId, Level = 4, Num = 60, Status = 2, Type = 1, Title = "已分享券2", FromId = "0", ShareTime = DateTime.Now, CreatedAt = DateTime.Now }
        });
        await dbContext.Users.AddAsync(CreateTestUser(userId));
        await dbContext.SaveChangesAsync();

        var result = await service.GetCouponListAsync(userId, 2, 1, 15);
        Assert.Equal(2, result.List.Count);
    }

    [Theory]
    [InlineData(1, "特级赏券")]
    [InlineData(2, "终极赏券")]
    [InlineData(3, "高级赏券")]
    [InlineData(4, "普通赏券")]
    public async Task GetCouponList_LevelMapping_ReturnsCorrectLevelText(int level, string expectedText)
    {
        var dbContext = CreateInMemoryDbContext();
        var service = CreateCouponService(dbContext);
        var userId = 1;

        await dbContext.UserCoupons.AddAsync(new UserCoupon { UserId = userId, Level = level, Num = 100, Status = 1, Type = 1, Title = "测试券", FromId = "0", CreatedAt = DateTime.Now });
        await dbContext.Users.AddAsync(CreateTestUser(userId));
        await dbContext.SaveChangesAsync();

        var result = await service.GetCouponListAsync(userId, 1, 1, 15);
        Assert.Single(result.List);
        Assert.Equal(expectedText, result.List[0].LevelText);
    }

    [Fact]
    public async Task GetCouponList_ReturnsStatistics()
    {
        var dbContext = CreateInMemoryDbContext();
        var service = CreateCouponService(dbContext);
        var userId = 1;

        await dbContext.Users.AddAsync(CreateTestUser(userId, 500));
        await dbContext.SaveChangesAsync();

        var result = await service.GetCouponListAsync(userId, 1, 1, 15);
        Assert.Equal(50, result.ZCount);
        Assert.Equal(20, result.KeHcCount);
        Assert.Equal("10%", result.SunHao);
        Assert.Equal(500, result.UserIntegral);
    }

    [Fact]
    public async Task ShareCoupon_Success_UpdatesStatusAndShareTime()
    {
        var dbContext = CreateInMemoryDbContext();
        var service = CreateCouponService(dbContext);
        var userId = 1;

        var coupon = new UserCoupon { UserId = userId, Level = 3, Num = 100, Status = 1, Type = 1, Title = "测试券", FromId = "0", CreatedAt = DateTime.Now };
        await dbContext.UserCoupons.AddAsync(coupon);
        await dbContext.SaveChangesAsync();

        var result = await service.ShareCouponAsync(userId, coupon.Id);
        Assert.True(result);
        var updatedCoupon = await dbContext.UserCoupons.FindAsync(coupon.Id);
        Assert.Equal(2, updatedCoupon!.Status);
        Assert.NotNull(updatedCoupon.ShareTime);
    }

    [Fact]
    public async Task ShareCoupon_NotOwner_ThrowsException()
    {
        var dbContext = CreateInMemoryDbContext();
        var service = CreateCouponService(dbContext);

        var coupon = new UserCoupon { UserId = 1, Level = 3, Num = 100, Status = 1, Type = 1, Title = "测试券", FromId = "0", CreatedAt = DateTime.Now };
        await dbContext.UserCoupons.AddAsync(coupon);
        await dbContext.SaveChangesAsync();

        await Assert.ThrowsAsync<InvalidOperationException>(() => service.ShareCouponAsync(2, coupon.Id));
    }

    [Fact]
    public async Task ClaimCoupon_OwnCoupon_ThrowsException()
    {
        var dbContext = CreateInMemoryDbContext();
        var service = CreateCouponService(dbContext);
        var userId = 1;

        var coupon = new UserCoupon { UserId = userId, Level = 3, Num = 100, Status = 2, Type = 1, Title = "测试券", FromId = "0", Other = 20, KlNum = 5, CreatedAt = DateTime.Now };
        await dbContext.UserCoupons.AddAsync(coupon);
        await dbContext.SaveChangesAsync();

        var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => service.ClaimCouponAsync(userId, coupon.Id));
        Assert.Equal("请勿开启自己的劵", ex.Message);
    }

    [Fact]
    public async Task ClaimCoupon_AlreadyClaimed_ThrowsException()
    {
        var dbContext = CreateInMemoryDbContext();
        var service = CreateCouponService(dbContext);

        var coupon = new UserCoupon { UserId = 1, Level = 3, Num = 100, Status = 2, Type = 1, Title = "测试券", FromId = "0", Other = 20, KlNum = 5, CreatedAt = DateTime.Now };
        await dbContext.UserCoupons.AddAsync(coupon);
        await dbContext.SaveChangesAsync();

        var claimRecord = new UserCoupon { UserId = 2, Level = 3, Num = 0, LNum = 5, Status = 3, Type = 2, Title = "测试券", FromId = coupon.Id.ToString(), CreatedAt = DateTime.Now };
        await dbContext.UserCoupons.AddAsync(claimRecord);
        await dbContext.SaveChangesAsync();

        var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => service.ClaimCouponAsync(2, coupon.Id));
        Assert.Equal("你已经领取过了", ex.Message);
    }

    [Fact]
    public async Task ClaimCoupon_NoRemainingAmount_ThrowsException()
    {
        var dbContext = CreateInMemoryDbContext();
        var service = CreateCouponService(dbContext);

        var coupon = new UserCoupon { UserId = 1, Level = 3, Num = 100, Status = 2, Type = 1, Title = "测试券", FromId = "0", Other = 0, KlNum = 0, CreatedAt = DateTime.Now };
        await dbContext.UserCoupons.AddAsync(coupon);
        await dbContext.SaveChangesAsync();

        var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => service.ClaimCouponAsync(2, coupon.Id));
        Assert.Equal("来晚了, 已经被人领完了", ex.Message);
    }

    [Fact]
    public async Task CalculateSynthesis_AppliesLossRate()
    {
        var dbContext = CreateInMemoryDbContext();
        var service = CreateCouponService(dbContext);
        var userId = 1;

        var coupons = new List<UserCoupon>
        {
            new() { UserId = userId, Level = 4, Num = 100, Status = 1, Type = 1, Title = "普通赏券1", FromId = "0", CreatedAt = DateTime.Now },
            new() { UserId = userId, Level = 4, Num = 100, Status = 1, Type = 1, Title = "普通赏券2", FromId = "0", CreatedAt = DateTime.Now }
        };
        await dbContext.UserCoupons.AddRangeAsync(coupons);
        await dbContext.SaveChangesAsync();

        var result = await service.CalculateSynthesisAsync(userId, string.Join(",", coupons.Select(c => c.Id)));
        Assert.Equal(200, result.SumNum);
        Assert.Equal(180, result.ShNum);
    }

    [Theory]
    [InlineData(1)]
    [InlineData(2)]
    public async Task CalculateSynthesis_HighLevelCoupon_ThrowsException(int level)
    {
        var dbContext = CreateInMemoryDbContext();
        var service = CreateCouponService(dbContext);
        var userId = 1;

        var coupon = new UserCoupon { UserId = userId, Level = level, Num = 5000, Status = 1, Type = 1, Title = "高级券", FromId = "0", CreatedAt = DateTime.Now };
        await dbContext.UserCoupons.AddAsync(coupon);
        await dbContext.SaveChangesAsync();

        var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => service.CalculateSynthesisAsync(userId, coupon.Id.ToString()));
        Assert.Equal("特级,终极赏券不能合成", ex.Message);
    }

    [Fact]
    public async Task CalculateSynthesis_ExceedsMaxCount_ThrowsException()
    {
        var dbContext = CreateInMemoryDbContext();
        var service = CreateCouponService(dbContext);
        var userId = 1;

        var coupons = Enumerable.Range(1, 21).Select(i => new UserCoupon { UserId = userId, Level = 4, Num = 10, Status = 1, Type = 1, Title = $"普通赏券{i}", FromId = "0", CreatedAt = DateTime.Now }).ToList();
        await dbContext.UserCoupons.AddRangeAsync(coupons);
        await dbContext.SaveChangesAsync();

        var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => service.CalculateSynthesisAsync(userId, string.Join(",", coupons.Select(c => c.Id))));
        Assert.Equal("最多只能20个合成", ex.Message);
    }

    [Fact]
    public async Task SynthesisCoupons_Success_CreatesNewCoupon()
    {
        var dbContext = CreateInMemoryDbContext();
        var service = CreateCouponService(dbContext);
        var userId = 1;

        var coupons = new List<UserCoupon>
        {
            new() { UserId = userId, Level = 4, Num = 300, Status = 1, Type = 1, Title = "普通赏券1", FromId = "0", CreatedAt = DateTime.Now },
            new() { UserId = userId, Level = 4, Num = 300, Status = 1, Type = 1, Title = "普通赏券2", FromId = "0", CreatedAt = DateTime.Now }
        };
        await dbContext.UserCoupons.AddRangeAsync(coupons);
        await dbContext.SaveChangesAsync();

        var result = await service.SynthesisCouponsAsync(userId, string.Join(",", coupons.Select(c => c.Id)));
        Assert.True(result);

        var originalCoupons = await dbContext.UserCoupons.Where(c => coupons.Select(x => x.Id).Contains(c.Id)).ToListAsync();
        Assert.All(originalCoupons, c => Assert.Equal(4, c.Status));

        var newCoupon = await dbContext.UserCoupons.FirstOrDefaultAsync(c => c.UserId == userId && c.Type == 3);
        Assert.NotNull(newCoupon);
        Assert.Equal(1, newCoupon.Status);
        Assert.Equal(3, newCoupon.Level);
    }

    [Theory]
    [InlineData(5000, 1)]
    [InlineData(2000, 2)]
    [InlineData(500, 3)]
    [InlineData(100, 4)]
    public async Task CalculateSynthesis_CalculatesCorrectLevel(int totalNum, int expectedLevel)
    {
        var dbContext = CreateInMemoryDbContext();
        var service = CreateCouponService(dbContext);
        var userId = 1;

        var coupon = new UserCoupon { UserId = userId, Level = 4, Num = totalNum, Status = 1, Type = 1, Title = "普通赏券", FromId = "0", CreatedAt = DateTime.Now };
        await dbContext.UserCoupons.AddAsync(coupon);
        await dbContext.SaveChangesAsync();

        var result = await service.CalculateSynthesisAsync(userId, coupon.Id.ToString());
        Assert.Equal(expectedLevel, result.Coupon.Level);
    }
}
