using HoneyBox.Admin.Business.Models;
using HoneyBox.Admin.Business.Models.User;
using HoneyBox.Admin.Business.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.Services;

/// <summary>
/// UserBusinessService 单元测试
/// </summary>
public class UserBusinessServiceTests : IDisposable
{
    private readonly HoneyBoxDbContext _dbContext;
    private readonly Mock<ILogger<UserBusinessService>> _mockLogger;
    private readonly UserBusinessService _userService;

    public UserBusinessServiceTests()
    {
        var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .ConfigureWarnings(w => w.Ignore(InMemoryEventId.TransactionIgnoredWarning))
            .Options;

        _dbContext = new HoneyBoxDbContext(options);
        _mockLogger = new Mock<ILogger<UserBusinessService>>();

        _userService = new UserBusinessService(_dbContext, _mockLogger.Object);
    }

    public void Dispose()
    {
        _dbContext.Dispose();
    }

    #region Helper Methods

    private async Task<User> CreateTestUserAsync(
        string nickname = "TestUser",
        decimal money = 100,
        decimal integral = 50,
        decimal score = 30,
        byte status = 1,
        int isTest = 0,
        int pid = 0)
    {
        var user = new User
        {
            OpenId = Guid.NewGuid().ToString("N"),
            Uid = $"UID{DateTime.Now.Ticks}",
            Nickname = nickname,
            HeadImg = "https://example.com/avatar.png",
            Mobile = "13800138000",
            Money = money,
            Integral = integral,
            Score = score,
            Status = status,
            IsTest = isTest,
            Pid = pid,
            Vip = 1,
            CreatedAt = DateTime.Now,
            UpdatedAt = DateTime.Now
        };
        _dbContext.Users.Add(user);
        await _dbContext.SaveChangesAsync();
        return user;
    }

    private async Task<Coupon> CreateTestCouponAsync()
    {
        var coupon = new Coupon
        {
            Title = "测试优惠券",
            Price = 10,
            ManPrice = 100,
            EffectiveDay = 30,
            Status = 1,
            CreatedAt = DateTime.Now
        };
        _dbContext.Coupons.Add(coupon);
        await _dbContext.SaveChangesAsync();
        return coupon;
    }

    private async Task<(Good goods, GoodsItem item)> CreateTestGoodsWithItemAsync()
    {
        var goods = new Good
        {
            Title = "测试盒子",
            Price = 10,
            ImgUrl = "https://example.com/goods.png",
            ImgUrlDetail = "https://example.com/goods_detail.png",
            Type = 1,
            Status = 1,
            Stock = 100,
            SaleStock = 0,
            CreatedAt = DateTime.Now,
            UpdatedAt = DateTime.Now
        };
        _dbContext.Goods.Add(goods);
        await _dbContext.SaveChangesAsync();

        var item = new GoodsItem
        {
            GoodsId = goods.Id,
            Title = "测试奖品",
            ImgUrl = "https://example.com/item.png",
            Price = 50,
            Money = 30,
            Stock = 10,
            SurplusStock = 10,
            Type = 1,
            ShangId = 1,
            UpdatedAt = DateTime.Now
        };
        _dbContext.GoodsItems.Add(item);
        await _dbContext.SaveChangesAsync();

        return (goods, item);
    }

    #endregion

    #region GetUserListAsync Tests

    [Fact]
    public async Task GetUserListAsync_ReturnsPagedResult()
    {
        // Arrange
        await CreateTestUserAsync("User1");
        await CreateTestUserAsync("User2");
        await CreateTestUserAsync("User3");

        var request = new UserListRequest { Page = 1, PageSize = 2 };

        // Act
        var result = await _userService.GetUserListAsync(request);

        // Assert
        Assert.NotNull(result);
        Assert.Equal(3, result.Total);
        Assert.Equal(2, result.List.Count);
        Assert.Equal(1, result.Page);
        Assert.Equal(2, result.PageSize);
    }

    [Fact]
    public async Task GetUserListAsync_WithNicknameFilter_ReturnsFilteredResult()
    {
        // Arrange
        await CreateTestUserAsync("Alice");
        await CreateTestUserAsync("Bob");
        await CreateTestUserAsync("AliceSmith");

        var request = new UserListRequest { Nickname = "Alice" };

        // Act
        var result = await _userService.GetUserListAsync(request);

        // Assert
        Assert.Equal(2, result.Total);
        Assert.All(result.List, u => Assert.Contains("Alice", u.Nickname!));
    }

    [Fact]
    public async Task GetUserListAsync_WithParentIdFilter_ReturnsSubordinates()
    {
        // Arrange
        var parent = await CreateTestUserAsync("Parent");
        await CreateTestUserAsync("Child1", pid: parent.Id);
        await CreateTestUserAsync("Child2", pid: parent.Id);
        await CreateTestUserAsync("Other");

        var request = new UserListRequest { ParentId = parent.Id };

        // Act
        var result = await _userService.GetUserListAsync(request);

        // Assert
        Assert.Equal(2, result.Total);
        Assert.All(result.List, u => Assert.Equal(parent.Id, u.ParentId));
    }

    [Fact]
    public async Task GetUserListAsync_WithDateRangeFilter_ReturnsFilteredResult()
    {
        // Arrange
        var user1 = await CreateTestUserAsync("User1");
        user1.CreatedAt = DateTime.Now.AddDays(-5);
        var user2 = await CreateTestUserAsync("User2");
        user2.CreatedAt = DateTime.Now.AddDays(-1);
        await _dbContext.SaveChangesAsync();

        var request = new UserListRequest
        {
            StartDate = DateTime.Now.AddDays(-3),
            EndDate = DateTime.Now
        };

        // Act
        var result = await _userService.GetUserListAsync(request);

        // Assert
        Assert.Equal(1, result.Total);
    }

    #endregion

    #region GetUserDetailAsync Tests

    [Fact]
    public async Task GetUserDetailAsync_WithExistingUser_ReturnsDetail()
    {
        // Arrange
        var user = await CreateTestUserAsync("DetailUser", money: 200, integral: 100, score: 50);

        // Act
        var result = await _userService.GetUserDetailAsync(user.Id);

        // Assert
        Assert.NotNull(result);
        Assert.Equal(user.Id, result.Id);
        Assert.Equal("DetailUser", result.Nickname);
        Assert.Equal(200, result.Balance);
        Assert.Equal(100, result.Integral);
        Assert.Equal(50, result.Diamond);
    }

    [Fact]
    public async Task GetUserDetailAsync_WithNonExistingUser_ReturnsNull()
    {
        // Act
        var result = await _userService.GetUserDetailAsync(99999);

        // Assert
        Assert.Null(result);
    }

    #endregion


    #region ChangeUserMoneyAsync Tests

    [Fact]
    public async Task ChangeUserMoneyAsync_AddBalance_IncreasesBalance()
    {
        // Arrange
        var user = await CreateTestUserAsync(money: 100);
        var request = new UserMoneyChangeRequest
        {
            Type = MoneyChangeType.Balance,
            Amount = 50,
            Operation = OperationType.Add,
            Remark = "测试增加"
        };

        // Act
        var result = await _userService.ChangeUserMoneyAsync(user.Id, request, 1);

        // Assert
        Assert.True(result);
        var updatedUser = await _dbContext.Users.FindAsync(user.Id);
        Assert.Equal(150, updatedUser!.Money);

        // 验证记录了变动日志
        var profitLog = await _dbContext.ProfitMoneys.FirstOrDefaultAsync(p => p.UserId == user.Id);
        Assert.NotNull(profitLog);
        Assert.Equal(50, profitLog.ChangeMoney);
    }

    [Fact]
    public async Task ChangeUserMoneyAsync_SubtractBalance_DecreasesBalance()
    {
        // Arrange
        var user = await CreateTestUserAsync(money: 100);
        var request = new UserMoneyChangeRequest
        {
            Type = MoneyChangeType.Balance,
            Amount = 30,
            Operation = OperationType.Subtract
        };

        // Act
        var result = await _userService.ChangeUserMoneyAsync(user.Id, request, 1);

        // Assert
        Assert.True(result);
        var updatedUser = await _dbContext.Users.FindAsync(user.Id);
        Assert.Equal(70, updatedUser!.Money);
    }

    [Fact]
    public async Task ChangeUserMoneyAsync_SubtractMoreThanBalance_ThrowsException()
    {
        // Arrange
        var user = await CreateTestUserAsync(money: 50);
        var request = new UserMoneyChangeRequest
        {
            Type = MoneyChangeType.Balance,
            Amount = 100,
            Operation = OperationType.Subtract
        };

        // Act & Assert
        var ex = await Assert.ThrowsAsync<BusinessException>(
            () => _userService.ChangeUserMoneyAsync(user.Id, request, 1));
        Assert.Equal(BusinessErrorCodes.ValidationFailed, ex.Code);
        Assert.Contains("不足", ex.Message);
    }

    [Fact]
    public async Task ChangeUserMoneyAsync_AddIntegral_IncreasesIntegral()
    {
        // Arrange
        var user = await CreateTestUserAsync(integral: 50);
        var request = new UserMoneyChangeRequest
        {
            Type = MoneyChangeType.Integral,
            Amount = 25,
            Operation = OperationType.Add
        };

        // Act
        var result = await _userService.ChangeUserMoneyAsync(user.Id, request, 1);

        // Assert
        Assert.True(result);
        var updatedUser = await _dbContext.Users.FindAsync(user.Id);
        Assert.Equal(75, updatedUser!.Integral);
    }

    [Fact]
    public async Task ChangeUserMoneyAsync_AddDiamond_IncreasesDiamond()
    {
        // Arrange
        var user = await CreateTestUserAsync(score: 30);
        var request = new UserMoneyChangeRequest
        {
            Type = MoneyChangeType.Diamond,
            Amount = 20,
            Operation = OperationType.Add
        };

        // Act
        var result = await _userService.ChangeUserMoneyAsync(user.Id, request, 1);

        // Assert
        Assert.True(result);
        var updatedUser = await _dbContext.Users.FindAsync(user.Id);
        Assert.Equal(50, updatedUser!.Score);
    }

    [Fact]
    public async Task ChangeUserMoneyAsync_NonExistingUser_ThrowsException()
    {
        // Arrange
        var request = new UserMoneyChangeRequest
        {
            Type = MoneyChangeType.Balance,
            Amount = 50,
            Operation = OperationType.Add
        };

        // Act & Assert
        var ex = await Assert.ThrowsAsync<BusinessException>(
            () => _userService.ChangeUserMoneyAsync(99999, request, 1));
        Assert.Equal(BusinessErrorCodes.NotFound, ex.Code);
    }

    #endregion

    #region SetUserStatusAsync Tests

    [Fact]
    public async Task SetUserStatusAsync_BanUser_SetsStatusToDisabled()
    {
        // Arrange
        var user = await CreateTestUserAsync(status: 1);

        // Act
        var result = await _userService.SetUserStatusAsync(user.Id, 0);

        // Assert
        Assert.True(result);
        var updatedUser = await _dbContext.Users.FindAsync(user.Id);
        Assert.Equal(0, updatedUser!.Status);
    }

    [Fact]
    public async Task SetUserStatusAsync_UnbanUser_SetsStatusToEnabled()
    {
        // Arrange
        var user = await CreateTestUserAsync(status: 0);

        // Act
        var result = await _userService.SetUserStatusAsync(user.Id, 1);

        // Assert
        Assert.True(result);
        var updatedUser = await _dbContext.Users.FindAsync(user.Id);
        Assert.Equal(1, updatedUser!.Status);
    }

    [Fact]
    public async Task SetUserStatusAsync_NonExistingUser_ThrowsException()
    {
        // Act & Assert
        var ex = await Assert.ThrowsAsync<BusinessException>(
            () => _userService.SetUserStatusAsync(99999, 0));
        Assert.Equal(BusinessErrorCodes.NotFound, ex.Code);
    }

    #endregion

    #region SetTestAccountAsync Tests

    [Fact]
    public async Task SetTestAccountAsync_SetAsTest_UpdatesIsTest()
    {
        // Arrange
        var user = await CreateTestUserAsync(isTest: 0);

        // Act
        var result = await _userService.SetTestAccountAsync(user.Id, 1);

        // Assert
        Assert.True(result);
        var updatedUser = await _dbContext.Users.FindAsync(user.Id);
        Assert.Equal(1, updatedUser!.IsTest);
    }

    [Fact]
    public async Task SetTestAccountAsync_RemoveTest_UpdatesIsTest()
    {
        // Arrange
        var user = await CreateTestUserAsync(isTest: 1);

        // Act
        var result = await _userService.SetTestAccountAsync(user.Id, 0);

        // Assert
        Assert.True(result);
        var updatedUser = await _dbContext.Users.FindAsync(user.Id);
        Assert.Equal(0, updatedUser!.IsTest);
    }

    #endregion

    #region ClearMobileAsync Tests

    [Fact]
    public async Task ClearMobileAsync_ClearsMobileAndSavesOld()
    {
        // Arrange
        var user = await CreateTestUserAsync();
        var originalMobile = user.Mobile;

        // Act
        var result = await _userService.ClearMobileAsync(user.Id);

        // Assert
        Assert.True(result);
        var updatedUser = await _dbContext.Users.FindAsync(user.Id);
        Assert.Null(updatedUser!.Mobile);
        Assert.Equal(originalMobile, updatedUser.OldMobile);
    }

    #endregion

    #region ClearWeChatAsync Tests

    [Fact]
    public async Task ClearWeChatAsync_GeneratesNewOpenId()
    {
        // Arrange
        var user = await CreateTestUserAsync();
        var originalOpenId = user.OpenId;

        // Act
        var result = await _userService.ClearWeChatAsync(user.Id);

        // Assert
        Assert.True(result);
        var updatedUser = await _dbContext.Users.FindAsync(user.Id);
        Assert.NotEqual(originalOpenId, updatedUser!.OpenId);
        Assert.StartsWith("cleared_", updatedUser.OpenId);
        Assert.Null(updatedUser.UnionId);
        Assert.Null(updatedUser.GzhOpenId);
    }

    #endregion


    #region GiftCouponAsync Tests

    [Fact]
    public async Task GiftCouponAsync_CreatesCouponReceiveRecords()
    {
        // Arrange
        var user = await CreateTestUserAsync();
        var coupon = await CreateTestCouponAsync();
        var request = new GiftCouponRequest
        {
            CouponId = coupon.Id,
            Quantity = 3
        };

        // Act
        var result = await _userService.GiftCouponAsync(user.Id, request);

        // Assert
        Assert.True(result);
        var receives = await _dbContext.CouponReceives
            .Where(r => r.UserId == user.Id && r.CouponId == coupon.Id)
            .ToListAsync();
        Assert.Equal(3, receives.Count);
        Assert.All(receives, r =>
        {
            Assert.Equal(coupon.Title, r.Title);
            Assert.Equal(coupon.Price, r.Price);
            Assert.Equal((byte)1, r.Status);
            Assert.Equal((byte?)0, r.State);
        });
    }

    [Fact]
    public async Task GiftCouponAsync_NonExistingUser_ThrowsException()
    {
        // Arrange
        var coupon = await CreateTestCouponAsync();
        var request = new GiftCouponRequest { CouponId = coupon.Id, Quantity = 1 };

        // Act & Assert
        var ex = await Assert.ThrowsAsync<BusinessException>(
            () => _userService.GiftCouponAsync(99999, request));
        Assert.Equal(BusinessErrorCodes.NotFound, ex.Code);
    }

    [Fact]
    public async Task GiftCouponAsync_NonExistingCoupon_ThrowsException()
    {
        // Arrange
        var user = await CreateTestUserAsync();
        var request = new GiftCouponRequest { CouponId = 99999, Quantity = 1 };

        // Act & Assert
        var ex = await Assert.ThrowsAsync<BusinessException>(
            () => _userService.GiftCouponAsync(user.Id, request));
        Assert.Equal(BusinessErrorCodes.NotFound, ex.Code);
    }

    #endregion

    #region GiftCardAsync Tests

    [Fact]
    public async Task GiftCardAsync_CreatesOrderAndOrderItems()
    {
        // Arrange
        var user = await CreateTestUserAsync();
        var (goods, item) = await CreateTestGoodsWithItemAsync();
        var request = new GiftCardRequest
        {
            GoodsId = goods.Id,
            GoodsListId = item.Id,
            Quantity = 2
        };

        // Act
        var result = await _userService.GiftCardAsync(user.Id, request);

        // Assert
        Assert.True(result);

        // 验证订单
        var order = await _dbContext.Orders
            .FirstOrDefaultAsync(o => o.UserId == user.Id && o.OrderType == 9);
        Assert.NotNull(order);
        Assert.Equal(0, order.Price);
        Assert.Equal(2, order.Num);

        // 验证订单详情
        var orderItems = await _dbContext.OrderItems
            .Where(oi => oi.OrderId == order.Id)
            .ToListAsync();
        Assert.Equal(2, orderItems.Count);
        Assert.All(orderItems, oi =>
        {
            Assert.Equal(item.Title, oi.GoodslistTitle);
            Assert.Equal(9, oi.OrderType);
            Assert.NotNull(oi.PrizeCode);
        });
    }

    [Fact]
    public async Task GiftCardAsync_NonExistingGoods_ThrowsException()
    {
        // Arrange
        var user = await CreateTestUserAsync();
        var request = new GiftCardRequest { GoodsId = 99999, GoodsListId = 1, Quantity = 1 };

        // Act & Assert
        var ex = await Assert.ThrowsAsync<BusinessException>(
            () => _userService.GiftCardAsync(user.Id, request));
        Assert.Equal(BusinessErrorCodes.NotFound, ex.Code);
    }

    #endregion

    #region GetSubordinateUsersAsync Tests

    [Fact]
    public async Task GetSubordinateUsersAsync_ReturnsSubordinates()
    {
        // Arrange
        var parent = await CreateTestUserAsync("Parent");
        await CreateTestUserAsync("Child1", pid: parent.Id);
        await CreateTestUserAsync("Child2", pid: parent.Id);

        // Act
        var result = await _userService.GetSubordinateUsersAsync(parent.Id, 1, 10);

        // Assert
        Assert.Equal(2, result.Total);
        Assert.Equal(2, result.List.Count);
        Assert.All(result.List, u => Assert.Equal(parent.Id, u.ParentId));
    }

    [Fact]
    public async Task GetSubordinateUsersAsync_NonExistingUser_ThrowsException()
    {
        // Act & Assert
        var ex = await Assert.ThrowsAsync<BusinessException>(
            () => _userService.GetSubordinateUsersAsync(99999, 1, 10));
        Assert.Equal(BusinessErrorCodes.NotFound, ex.Code);
    }

    #endregion

    #region GetUserProfitLossAsync Tests

    [Fact]
    public async Task GetUserProfitLossAsync_ReturnsStatistics()
    {
        // Arrange
        var user = await CreateTestUserAsync(money: 100, integral: 50, score: 30);

        // Act
        var result = await _userService.GetUserProfitLossAsync(user.Id, null);

        // Assert
        Assert.Equal(user.Id, result.UserId);
        Assert.Equal(100, result.CurrentBalance);
        Assert.Equal(50, result.CurrentIntegral);
        Assert.Equal(30, result.CurrentDiamond);
    }

    [Fact]
    public async Task GetUserProfitLossAsync_NonExistingUser_ThrowsException()
    {
        // Act & Assert
        var ex = await Assert.ThrowsAsync<BusinessException>(
            () => _userService.GetUserProfitLossAsync(99999, null));
        Assert.Equal(BusinessErrorCodes.NotFound, ex.Code);
    }

    #endregion
}
