using Microsoft.EntityFrameworkCore;
using MilitaryTrainingManagement.Data;
using MilitaryTrainingManagement.Models.Entities;
using MilitaryTrainingManagement.Models.Enums;
using MilitaryTrainingManagement.Services.Implementations;

namespace MilitaryTrainingManagement.Tests.Services;

/// <summary>
/// 物资配额服务单元测试
/// 测试配额创建、验证逻辑、分发算法和错误处理场景
/// _需求：2.1, 2.2, 2.3, 2.4, 2.5_
/// </summary>
public class AllocationServiceTests
{
    private static ApplicationDbContext CreateInMemoryContext(string dbName)
    {
        var options = new DbContextOptionsBuilder<ApplicationDbContext>()
            .UseInMemoryDatabase(databaseName: dbName)
            .Options;
        return new ApplicationDbContext(options);
    }

    private static Dictionary<OrganizationalLevel, OrganizationalUnit> CreateHierarchy(
        ApplicationDbContext context)
    {
        var division = new OrganizationalUnit
        {
            Name = "师团",
            Level = OrganizationalLevel.Division,
            CreatedAt = DateTime.UtcNow
        };
        context.OrganizationalUnits.Add(division);
        context.SaveChanges();

        var regiment = new OrganizationalUnit
        {
            Name = "团",
            Level = OrganizationalLevel.Regiment,
            ParentId = division.Id,
            CreatedAt = DateTime.UtcNow
        };
        context.OrganizationalUnits.Add(regiment);
        context.SaveChanges();

        var battalion = new OrganizationalUnit
        {
            Name = "营",
            Level = OrganizationalLevel.Battalion,
            ParentId = regiment.Id,
            CreatedAt = DateTime.UtcNow
        };
        context.OrganizationalUnits.Add(battalion);
        context.SaveChanges();

        var company = new OrganizationalUnit
        {
            Name = "连",
            Level = OrganizationalLevel.Company,
            ParentId = battalion.Id,
            CreatedAt = DateTime.UtcNow
        };
        context.OrganizationalUnits.Add(company);
        context.SaveChanges();

        return new Dictionary<OrganizationalLevel, OrganizationalUnit>
        {
            { OrganizationalLevel.Division, division },
            { OrganizationalLevel.Regiment, regiment },
            { OrganizationalLevel.Battalion, battalion },
            { OrganizationalLevel.Company, company }
        };
    }

    #region 配额创建测试 (需求2.1, 2.2)

    /// <summary>
    /// 测试创建配额成功
    /// </summary>
    [Fact]
    public async Task CreateAsync_WithValidData_CreatesAllocation()
    {
        // Arrange
        var dbName = Guid.NewGuid().ToString();
        using var context = CreateInMemoryContext(dbName);
        var hierarchy = CreateHierarchy(context);
        var organizationService = new OrganizationService(context);
        var service = new AllocationService(context, organizationService);

        var division = hierarchy[OrganizationalLevel.Division];
        var regiment = hierarchy[OrganizationalLevel.Regiment];

        var distributions = new Dictionary<int, decimal>
        {
            { regiment.Id, 50m }
        };

        // Act
        var allocation = await service.CreateAsync(
            "弹药",
            "5.56mm子弹",
            "发",
            100m,
            division.Id,
            distributions);

        // Assert
        Assert.NotNull(allocation);
        Assert.Equal("弹药", allocation.Category);
        Assert.Equal("5.56mm子弹", allocation.MaterialName);
        Assert.Equal("发", allocation.Unit);
        Assert.Equal(100m, allocation.TotalQuota);
        Assert.Equal(division.Id, allocation.CreatedByUnitId);
    }

    /// <summary>
    /// 测试创建配额时必填字段验证 - 空类别
    /// </summary>
    [Fact]
    public async Task CreateAsync_WithEmptyCategory_ThrowsArgumentException()
    {
        // Arrange
        var dbName = Guid.NewGuid().ToString();
        using var context = CreateInMemoryContext(dbName);
        var hierarchy = CreateHierarchy(context);
        var organizationService = new OrganizationService(context);
        var service = new AllocationService(context, organizationService);

        var division = hierarchy[OrganizationalLevel.Division];

        // Act & Assert
        var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
            service.CreateAsync("", "测试物资", "件", 100m, division.Id, new Dictionary<int, decimal>()));

        Assert.Equal("类别不能为空", exception.Message);
    }

    /// <summary>
    /// 测试创建配额时必填字段验证 - 空物资名称
    /// </summary>
    [Fact]
    public async Task CreateAsync_WithEmptyMaterialName_ThrowsArgumentException()
    {
        // Arrange
        var dbName = Guid.NewGuid().ToString();
        using var context = CreateInMemoryContext(dbName);
        var hierarchy = CreateHierarchy(context);
        var organizationService = new OrganizationService(context);
        var service = new AllocationService(context, organizationService);

        var division = hierarchy[OrganizationalLevel.Division];

        // Act & Assert
        var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
            service.CreateAsync("弹药", "", "件", 100m, division.Id, new Dictionary<int, decimal>()));

        Assert.Equal("物资名称不能为空", exception.Message);
    }

    /// <summary>
    /// 测试创建配额时必填字段验证 - 零配额
    /// </summary>
    [Fact]
    public async Task CreateAsync_WithZeroTotalQuota_ThrowsArgumentException()
    {
        // Arrange
        var dbName = Guid.NewGuid().ToString();
        using var context = CreateInMemoryContext(dbName);
        var hierarchy = CreateHierarchy(context);
        var organizationService = new OrganizationService(context);
        var service = new AllocationService(context, organizationService);

        var division = hierarchy[OrganizationalLevel.Division];

        // Act & Assert
        var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
            service.CreateAsync("弹药", "测试物资", "件", 0m, division.Id, new Dictionary<int, decimal>()));

        Assert.Equal("总配额必须大于0", exception.Message);
    }

    #endregion

    #region 配额验证测试 (需求2.3, 2.5)

    /// <summary>
    /// 测试配额验证 - 有效分配
    /// </summary>
    [Fact]
    public async Task ValidateDistributionQuotasAsync_WithValidDistributions_ReturnsTrue()
    {
        // Arrange
        var dbName = Guid.NewGuid().ToString();
        using var context = CreateInMemoryContext(dbName);
        var organizationService = new OrganizationService(context);
        var service = new AllocationService(context, organizationService);

        var distributions = new Dictionary<int, decimal>
        {
            { 1, 30m },
            { 2, 40m },
            { 3, 30m }
        };

        // Act
        var isValid = await service.ValidateDistributionQuotasAsync(100m, distributions);

        // Assert
        Assert.True(isValid);
    }

    /// <summary>
    /// 测试配额验证 - 超出总配额
    /// </summary>
    [Fact]
    public async Task ValidateDistributionQuotasAsync_WithExceedingDistributions_ReturnsFalse()
    {
        // Arrange
        var dbName = Guid.NewGuid().ToString();
        using var context = CreateInMemoryContext(dbName);
        var organizationService = new OrganizationService(context);
        var service = new AllocationService(context, organizationService);

        var distributions = new Dictionary<int, decimal>
        {
            { 1, 60m },
            { 2, 50m }
        };

        // Act
        var isValid = await service.ValidateDistributionQuotasAsync(100m, distributions);

        // Assert
        Assert.False(isValid);
    }

    /// <summary>
    /// 测试创建配额时配额验证失败
    /// </summary>
    [Fact]
    public async Task CreateAsync_WithExceedingDistributions_ThrowsArgumentException()
    {
        // Arrange
        var dbName = Guid.NewGuid().ToString();
        using var context = CreateInMemoryContext(dbName);
        var hierarchy = CreateHierarchy(context);
        var organizationService = new OrganizationService(context);
        var service = new AllocationService(context, organizationService);

        var division = hierarchy[OrganizationalLevel.Division];
        var regiment = hierarchy[OrganizationalLevel.Regiment];
        var battalion = hierarchy[OrganizationalLevel.Battalion];

        var distributions = new Dictionary<int, decimal>
        {
            { regiment.Id, 60m },
            { battalion.Id, 50m }
        };

        // Act & Assert
        var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
            service.CreateAsync("弹药", "测试物资", "件", 100m, division.Id, distributions));

        Assert.Equal("分配配额总和超过总配额指标", exception.Message);
    }

    /// <summary>
    /// 测试创建配额时负配额验证
    /// </summary>
    [Fact]
    public async Task CreateAsync_WithNegativeQuota_ThrowsArgumentException()
    {
        // Arrange
        var dbName = Guid.NewGuid().ToString();
        using var context = CreateInMemoryContext(dbName);
        var hierarchy = CreateHierarchy(context);
        var organizationService = new OrganizationService(context);
        var service = new AllocationService(context, organizationService);

        var division = hierarchy[OrganizationalLevel.Division];
        var regiment = hierarchy[OrganizationalLevel.Regiment];

        var distributions = new Dictionary<int, decimal>
        {
            { regiment.Id, -10m }
        };

        // Act & Assert
        var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
            service.CreateAsync("弹药", "测试物资", "件", 100m, division.Id, distributions));

        Assert.Equal("单位配额不能为负数", exception.Message);
    }

    #endregion

    #region 自动分发测试 (需求2.4)

    /// <summary>
    /// 测试创建配额时自动创建分配记录
    /// </summary>
    [Fact]
    public async Task CreateAsync_AutomaticallyCreatesDistributionRecords()
    {
        // Arrange
        var dbName = Guid.NewGuid().ToString();
        using var context = CreateInMemoryContext(dbName);
        var hierarchy = CreateHierarchy(context);
        var organizationService = new OrganizationService(context);
        var service = new AllocationService(context, organizationService);

        var division = hierarchy[OrganizationalLevel.Division];
        var regiment = hierarchy[OrganizationalLevel.Regiment];
        var battalion = hierarchy[OrganizationalLevel.Battalion];

        var distributions = new Dictionary<int, decimal>
        {
            { regiment.Id, 40m },
            { battalion.Id, 30m }
        };

        // Act
        var allocation = await service.CreateAsync(
            "弹药",
            "测试物资",
            "件",
            100m,
            division.Id,
            distributions);

        // Assert
        Assert.Equal(2, allocation.Distributions.Count);
        Assert.Contains(allocation.Distributions, d => d.TargetUnitId == regiment.Id && d.UnitQuota == 40m);
        Assert.Contains(allocation.Distributions, d => d.TargetUnitId == battalion.Id && d.UnitQuota == 30m);
    }

    /// <summary>
    /// 测试分配记录正确关联到配额
    /// </summary>
    [Fact]
    public async Task CreateAsync_DistributionRecordsAreLinkedToAllocation()
    {
        // Arrange
        var dbName = Guid.NewGuid().ToString();
        using var context = CreateInMemoryContext(dbName);
        var hierarchy = CreateHierarchy(context);
        var organizationService = new OrganizationService(context);
        var service = new AllocationService(context, organizationService);

        var division = hierarchy[OrganizationalLevel.Division];
        var regiment = hierarchy[OrganizationalLevel.Regiment];

        var distributions = new Dictionary<int, decimal>
        {
            { regiment.Id, 50m }
        };

        // Act
        var allocation = await service.CreateAsync(
            "弹药",
            "测试物资",
            "件",
            100m,
            division.Id,
            distributions);

        // Assert
        var dbDistributions = await context.AllocationDistributions
            .Where(d => d.AllocationId == allocation.Id)
            .ToListAsync();

        Assert.Single(dbDistributions);
        Assert.Equal(allocation.Id, dbDistributions[0].AllocationId);
    }

    #endregion

    #region 更新和删除测试

    /// <summary>
    /// 测试更新配额基本信息
    /// </summary>
    [Fact]
    public async Task UpdateAsync_WithValidData_UpdatesAllocation()
    {
        // Arrange
        var dbName = Guid.NewGuid().ToString();
        using var context = CreateInMemoryContext(dbName);
        var hierarchy = CreateHierarchy(context);
        var organizationService = new OrganizationService(context);
        var service = new AllocationService(context, organizationService);

        var division = hierarchy[OrganizationalLevel.Division];
        var regiment = hierarchy[OrganizationalLevel.Regiment];

        var distributions = new Dictionary<int, decimal>
        {
            { regiment.Id, 50m }
        };

        var allocation = await service.CreateAsync(
            "弹药",
            "测试物资",
            "件",
            100m,
            division.Id,
            distributions);

        // Act
        var updated = await service.UpdateAsync(
            allocation.Id,
            "器材",
            "更新后的物资",
            "套",
            200m);

        // Assert
        Assert.Equal("器材", updated.Category);
        Assert.Equal("更新后的物资", updated.MaterialName);
        Assert.Equal("套", updated.Unit);
        Assert.Equal(200m, updated.TotalQuota);
    }

    /// <summary>
    /// 测试更新配额时总配额不能小于已分配总和
    /// </summary>
    [Fact]
    public async Task UpdateAsync_WithTotalQuotaLessThanDistributions_ThrowsArgumentException()
    {
        // Arrange
        var dbName = Guid.NewGuid().ToString();
        using var context = CreateInMemoryContext(dbName);
        var hierarchy = CreateHierarchy(context);
        var organizationService = new OrganizationService(context);
        var service = new AllocationService(context, organizationService);

        var division = hierarchy[OrganizationalLevel.Division];
        var regiment = hierarchy[OrganizationalLevel.Regiment];

        var distributions = new Dictionary<int, decimal>
        {
            { regiment.Id, 80m }
        };

        var allocation = await service.CreateAsync(
            "弹药",
            "测试物资",
            "件",
            100m,
            division.Id,
            distributions);

        // Act & Assert
        var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
            service.UpdateAsync(allocation.Id, "弹药", "测试物资", "件", 50m));

        Assert.Contains("总配额不能小于已分配的配额总和", exception.Message);
    }

    /// <summary>
    /// 测试删除配额
    /// </summary>
    [Fact]
    public async Task DeleteAsync_WithExistingAllocation_DeletesAllocation()
    {
        // Arrange
        var dbName = Guid.NewGuid().ToString();
        using var context = CreateInMemoryContext(dbName);
        var hierarchy = CreateHierarchy(context);
        var organizationService = new OrganizationService(context);
        var service = new AllocationService(context, organizationService);

        var division = hierarchy[OrganizationalLevel.Division];

        var allocation = await service.CreateAsync(
            "弹药",
            "测试物资",
            "件",
            100m,
            division.Id,
            new Dictionary<int, decimal>());

        // Act
        await service.DeleteAsync(allocation.Id);

        // Assert
        var deleted = await service.GetByIdAsync(allocation.Id);
        Assert.Null(deleted);
    }

    /// <summary>
    /// 测试删除不存在的配额
    /// </summary>
    [Fact]
    public async Task DeleteAsync_WithNonExistingAllocation_ThrowsArgumentException()
    {
        // Arrange
        var dbName = Guid.NewGuid().ToString();
        using var context = CreateInMemoryContext(dbName);
        var organizationService = new OrganizationService(context);
        var service = new AllocationService(context, organizationService);

        // Act & Assert
        var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
            service.DeleteAsync(999));

        Assert.Equal("配额不存在", exception.Message);
    }

    #endregion

    #region 目标单位验证测试

    /// <summary>
    /// 测试目标单位存在验证 - 有效单位
    /// </summary>
    [Fact]
    public async Task ValidateTargetUnitsExistAsync_WithExistingUnits_ReturnsTrue()
    {
        // Arrange
        var dbName = Guid.NewGuid().ToString();
        using var context = CreateInMemoryContext(dbName);
        var hierarchy = CreateHierarchy(context);
        var organizationService = new OrganizationService(context);
        var service = new AllocationService(context, organizationService);

        var regiment = hierarchy[OrganizationalLevel.Regiment];
        var battalion = hierarchy[OrganizationalLevel.Battalion];

        // Act
        var isValid = await service.ValidateTargetUnitsExistAsync(new[] { regiment.Id, battalion.Id });

        // Assert
        Assert.True(isValid);
    }

    /// <summary>
    /// 测试目标单位存在验证 - 无效单位
    /// </summary>
    [Fact]
    public async Task ValidateTargetUnitsExistAsync_WithNonExistingUnits_ReturnsFalse()
    {
        // Arrange
        var dbName = Guid.NewGuid().ToString();
        using var context = CreateInMemoryContext(dbName);
        CreateHierarchy(context);
        var organizationService = new OrganizationService(context);
        var service = new AllocationService(context, organizationService);

        // Act
        var isValid = await service.ValidateTargetUnitsExistAsync(new[] { 999, 1000 });

        // Assert
        Assert.False(isValid);
    }

    /// <summary>
    /// 测试创建配额时目标单位不存在
    /// </summary>
    [Fact]
    public async Task CreateAsync_WithNonExistingTargetUnit_ThrowsArgumentException()
    {
        // Arrange
        var dbName = Guid.NewGuid().ToString();
        using var context = CreateInMemoryContext(dbName);
        var hierarchy = CreateHierarchy(context);
        var organizationService = new OrganizationService(context);
        var service = new AllocationService(context, organizationService);

        var division = hierarchy[OrganizationalLevel.Division];

        var distributions = new Dictionary<int, decimal>
        {
            { 999, 50m }
        };

        // Act & Assert
        var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
            service.CreateAsync("弹药", "测试物资", "件", 100m, division.Id, distributions));

        Assert.Equal("目标单位不存在", exception.Message);
    }

    #endregion
}
