using FsCheck;
using FsCheck.Xunit;
using HoneyBox.Admin.Business.Models;
using HoneyBox.Admin.Business.Models.Config;
using HoneyBox.Admin.Business.Models.Upload;
using HoneyBox.Admin.Business.Services;
using HoneyBox.Admin.Business.Services.Interfaces;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;

namespace HoneyBox.Tests.Services;

/// <summary>
/// UploadService 单元测试
/// </summary>
public class UploadServiceTests
{
    private readonly Mock<IAdminConfigService> _mockConfigService;
    private readonly Mock<IStorageProvider> _mockLocalProvider;
    private readonly Mock<IStorageProvider> _mockCosProvider;
    private readonly Mock<ILogger<UploadService>> _mockLogger;
    private readonly UploadService _service;

    public UploadServiceTests()
    {
        _mockConfigService = new Mock<IAdminConfigService>();
        _mockLocalProvider = new Mock<IStorageProvider>();
        _mockCosProvider = new Mock<IStorageProvider>();
        _mockLogger = new Mock<ILogger<UploadService>>();

        // 设置存储提供者类型
        _mockLocalProvider.Setup(p => p.StorageType).Returns("1");
        _mockCosProvider.Setup(p => p.StorageType).Returns("3");

        var providers = new List<IStorageProvider> { _mockLocalProvider.Object, _mockCosProvider.Object };
        _service = new UploadService(_mockConfigService.Object, providers, _mockLogger.Object);
    }

    #region File Validation Tests

    [Fact]
    public void ValidateFile_NullFile_ReturnsError()
    {
        // Act
        var result = UploadService.ValidateFile(null);

        // Assert
        Assert.Equal("请选择要上传的文件", result);
    }

    [Fact]
    public void ValidateFile_EmptyFile_ReturnsError()
    {
        // Arrange
        var mockFile = new Mock<IFormFile>();
        mockFile.Setup(f => f.Length).Returns(0);

        // Act
        var result = UploadService.ValidateFile(mockFile.Object);

        // Assert
        Assert.Equal("请选择要上传的文件", result);
    }

    [Fact]
    public void ValidateFile_FileTooLarge_ReturnsError()
    {
        // Arrange
        var mockFile = new Mock<IFormFile>();
        mockFile.Setup(f => f.Length).Returns(11 * 1024 * 1024); // 11MB
        mockFile.Setup(f => f.FileName).Returns("test.jpg");
        mockFile.Setup(f => f.ContentType).Returns("image/jpeg");

        // Act
        var result = UploadService.ValidateFile(mockFile.Object);

        // Assert
        Assert.Equal("文件大小不能超过10MB", result);
    }

    [Fact]
    public void ValidateFile_InvalidExtension_ReturnsError()
    {
        // Arrange
        var mockFile = new Mock<IFormFile>();
        mockFile.Setup(f => f.Length).Returns(1024);
        mockFile.Setup(f => f.FileName).Returns("test.txt");
        mockFile.Setup(f => f.ContentType).Returns("text/plain");

        // Act
        var result = UploadService.ValidateFile(mockFile.Object);

        // Assert
        Assert.Equal("只支持 jpg、jpeg、png、gif、webp 格式的图片", result);
    }

    [Theory]
    [InlineData("test.jpg", "image/jpeg")]
    [InlineData("test.jpeg", "image/jpeg")]
    [InlineData("test.png", "image/png")]
    [InlineData("test.gif", "image/gif")]
    [InlineData("test.webp", "image/webp")]
    public void ValidateFile_ValidImageFormats_ReturnsNull(string fileName, string contentType)
    {
        // Arrange
        var mockFile = new Mock<IFormFile>();
        mockFile.Setup(f => f.Length).Returns(1024);
        mockFile.Setup(f => f.FileName).Returns(fileName);
        mockFile.Setup(f => f.ContentType).Returns(contentType);

        // Act
        var result = UploadService.ValidateFile(mockFile.Object);

        // Assert
        Assert.Null(result);
    }

    [Fact]
    public void ValidateFile_MaxSizeExactly_ReturnsNull()
    {
        // Arrange - 正好10MB
        var mockFile = new Mock<IFormFile>();
        mockFile.Setup(f => f.Length).Returns(10 * 1024 * 1024);
        mockFile.Setup(f => f.FileName).Returns("test.jpg");
        mockFile.Setup(f => f.ContentType).Returns("image/jpeg");

        // Act
        var result = UploadService.ValidateFile(mockFile.Object);

        // Assert
        Assert.Null(result);
    }

    #endregion

    #region Extension Validation Tests

    [Theory]
    [InlineData(".jpg", true)]
    [InlineData(".jpeg", true)]
    [InlineData(".png", true)]
    [InlineData(".gif", true)]
    [InlineData(".webp", true)]
    [InlineData(".JPG", true)]
    [InlineData(".JPEG", true)]
    [InlineData(".PNG", true)]
    [InlineData(".txt", false)]
    [InlineData(".pdf", false)]
    [InlineData(".exe", false)]
    [InlineData("", false)]
    [InlineData(null, false)]
    public void IsValidExtension_ReturnsExpectedResult(string? extension, bool expected)
    {
        // Act
        var result = UploadService.IsValidExtension(extension);

        // Assert
        Assert.Equal(expected, result);
    }

    #endregion

    #region File Size Validation Tests

    [Theory]
    [InlineData(1, true)]
    [InlineData(1024, true)]
    [InlineData(1024 * 1024, true)]
    [InlineData(10 * 1024 * 1024, true)]
    [InlineData(10 * 1024 * 1024 + 1, false)]
    [InlineData(0, false)]
    [InlineData(-1, false)]
    public void IsValidFileSize_ReturnsExpectedResult(long fileSize, bool expected)
    {
        // Act
        var result = UploadService.IsValidFileSize(fileSize);

        // Assert
        Assert.Equal(expected, result);
    }

    #endregion

    #region Unique FileName Tests

    [Fact]
    public void GenerateUniqueFileName_PreservesExtension()
    {
        // Arrange
        var originalFileName = "test.jpg";

        // Act
        var uniqueName = UploadService.GenerateUniqueFileName(originalFileName);

        // Assert
        Assert.EndsWith(".jpg", uniqueName);
    }

    [Fact]
    public void GenerateUniqueFileName_NormalizesExtensionToLowercase()
    {
        // Arrange
        var originalFileName = "test.JPG";

        // Act
        var uniqueName = UploadService.GenerateUniqueFileName(originalFileName);

        // Assert
        Assert.EndsWith(".jpg", uniqueName);
    }

    [Fact]
    public void GenerateUniqueFileName_GeneratesDifferentNames()
    {
        // Arrange
        var originalFileName = "test.jpg";

        // Act
        var name1 = UploadService.GenerateUniqueFileName(originalFileName);
        var name2 = UploadService.GenerateUniqueFileName(originalFileName);

        // Assert
        Assert.NotEqual(name1, name2);
    }

    #endregion

    #region Storage Provider Selection Tests

    [Fact]
    public async Task UploadImageAsync_UsesLocalStorageByDefault()
    {
        // Arrange
        _mockConfigService.Setup(c => c.GetConfigAsync<UploadSetting>(ConfigKeys.Uploads))
            .ReturnsAsync((UploadSetting?)null);

        _mockLocalProvider.Setup(p => p.UploadAsync(It.IsAny<Stream>(), It.IsAny<string>(), It.IsAny<string>()))
            .ReturnsAsync(UploadResult.Ok("/uploads/2026/01/19/test.jpg"));

        var mockFile = CreateMockFile("test.jpg", "image/jpeg", 1024);

        // Act
        var result = await _service.UploadImageAsync(mockFile.Object);

        // Assert
        _mockLocalProvider.Verify(p => p.UploadAsync(It.IsAny<Stream>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once);
        _mockCosProvider.Verify(p => p.UploadAsync(It.IsAny<Stream>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never);
    }

    [Fact]
    public async Task UploadImageAsync_UsesCosStorageWhenConfigured()
    {
        // Arrange
        var uploadSetting = new UploadSetting { Type = "3" };
        _mockConfigService.Setup(c => c.GetConfigAsync<UploadSetting>(ConfigKeys.Uploads))
            .ReturnsAsync(uploadSetting);

        _mockCosProvider.Setup(p => p.UploadAsync(It.IsAny<Stream>(), It.IsAny<string>(), It.IsAny<string>()))
            .ReturnsAsync(UploadResult.Ok("https://cdn.example.com/uploads/2026/01/19/test.jpg"));

        var mockFile = CreateMockFile("test.jpg", "image/jpeg", 1024);

        // Act
        var result = await _service.UploadImageAsync(mockFile.Object);

        // Assert
        _mockCosProvider.Verify(p => p.UploadAsync(It.IsAny<Stream>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once);
        _mockLocalProvider.Verify(p => p.UploadAsync(It.IsAny<Stream>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never);
    }

    [Fact]
    public async Task UploadImageAsync_FallsBackToLocalStorageForInvalidType()
    {
        // Arrange
        var uploadSetting = new UploadSetting { Type = "999" }; // 无效类型
        _mockConfigService.Setup(c => c.GetConfigAsync<UploadSetting>(ConfigKeys.Uploads))
            .ReturnsAsync(uploadSetting);

        _mockLocalProvider.Setup(p => p.UploadAsync(It.IsAny<Stream>(), It.IsAny<string>(), It.IsAny<string>()))
            .ReturnsAsync(UploadResult.Ok("/uploads/2026/01/19/test.jpg"));

        var mockFile = CreateMockFile("test.jpg", "image/jpeg", 1024);

        // Act
        var result = await _service.UploadImageAsync(mockFile.Object);

        // Assert
        _mockLocalProvider.Verify(p => p.UploadAsync(It.IsAny<Stream>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once);
    }

    #endregion

    #region Upload Response Tests

    [Fact]
    public async Task UploadImageAsync_ReturnsCorrectResponse()
    {
        // Arrange
        _mockConfigService.Setup(c => c.GetConfigAsync<UploadSetting>(ConfigKeys.Uploads))
            .ReturnsAsync((UploadSetting?)null);

        _mockLocalProvider.Setup(p => p.UploadAsync(It.IsAny<Stream>(), It.IsAny<string>(), It.IsAny<string>()))
            .ReturnsAsync(UploadResult.Ok("/uploads/2026/01/19/test.jpg"));

        var mockFile = CreateMockFile("original.jpg", "image/jpeg", 2048);

        // Act
        var result = await _service.UploadImageAsync(mockFile.Object);

        // Assert
        Assert.Equal("/uploads/2026/01/19/test.jpg", result.Url);
        Assert.Equal("original.jpg", result.FileName);
        Assert.Equal(2048, result.FileSize);
    }

    [Fact]
    public async Task UploadImageAsync_ThrowsOnUploadFailure()
    {
        // Arrange
        _mockConfigService.Setup(c => c.GetConfigAsync<UploadSetting>(ConfigKeys.Uploads))
            .ReturnsAsync((UploadSetting?)null);

        _mockLocalProvider.Setup(p => p.UploadAsync(It.IsAny<Stream>(), It.IsAny<string>(), It.IsAny<string>()))
            .ReturnsAsync(UploadResult.Fail("磁盘空间不足"));

        var mockFile = CreateMockFile("test.jpg", "image/jpeg", 1024);

        // Act & Assert
        var ex = await Assert.ThrowsAsync<BusinessException>(() => _service.UploadImageAsync(mockFile.Object));
        Assert.Equal(BusinessErrorCodes.OperationFailed, ex.Code);
        Assert.Equal("磁盘空间不足", ex.Message);
    }

    #endregion

    #region Batch Upload Tests

    [Fact]
    public async Task UploadImagesAsync_UploadsAllFiles()
    {
        // Arrange
        _mockConfigService.Setup(c => c.GetConfigAsync<UploadSetting>(ConfigKeys.Uploads))
            .ReturnsAsync((UploadSetting?)null);

        var uploadCount = 0;
        _mockLocalProvider.Setup(p => p.UploadAsync(It.IsAny<Stream>(), It.IsAny<string>(), It.IsAny<string>()))
            .ReturnsAsync(() => UploadResult.Ok($"/uploads/2026/01/19/file{++uploadCount}.jpg"));

        var files = new List<IFormFile>
        {
            CreateMockFile("file1.jpg", "image/jpeg", 1024).Object,
            CreateMockFile("file2.jpg", "image/jpeg", 2048).Object,
            CreateMockFile("file3.jpg", "image/jpeg", 3072).Object
        };

        // Act
        var results = await _service.UploadImagesAsync(files);

        // Assert
        Assert.Equal(3, results.Count);
        _mockLocalProvider.Verify(p => p.UploadAsync(It.IsAny<Stream>(), It.IsAny<string>(), It.IsAny<string>()), Times.Exactly(3));
    }

    [Fact]
    public async Task UploadImagesAsync_ThrowsOnEmptyList()
    {
        // Arrange
        var files = new List<IFormFile>();

        // Act & Assert
        var ex = await Assert.ThrowsAsync<BusinessException>(() => _service.UploadImagesAsync(files));
        Assert.Equal(BusinessErrorCodes.ValidationFailed, ex.Code);
    }

    [Fact]
    public async Task UploadImagesAsync_ThrowsOnNullList()
    {
        // Act & Assert
        var ex = await Assert.ThrowsAsync<BusinessException>(() => _service.UploadImagesAsync(null!));
        Assert.Equal(BusinessErrorCodes.ValidationFailed, ex.Code);
    }

    #endregion

    #region Helper Methods

    private static Mock<IFormFile> CreateMockFile(string fileName, string contentType, long length)
    {
        var mockFile = new Mock<IFormFile>();
        mockFile.Setup(f => f.FileName).Returns(fileName);
        mockFile.Setup(f => f.ContentType).Returns(contentType);
        mockFile.Setup(f => f.Length).Returns(length);
        mockFile.Setup(f => f.OpenReadStream()).Returns(new MemoryStream(new byte[length]));
        return mockFile;
    }

    #endregion
}
