using FsCheck;
using FsCheck.Xunit;
using HoneyBox.Admin.Business.Services.Storage;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;

namespace HoneyBox.Tests.Services;

/// <summary>
/// LocalStorageProvider 单元测试
/// </summary>
public class LocalStorageProviderTests : IDisposable
{
    private readonly Mock<IWebHostEnvironment> _mockEnvironment;
    private readonly Mock<ILogger<LocalStorageProvider>> _mockLogger;
    private readonly string _testWebRootPath;
    private readonly LocalStorageProvider _provider;

    public LocalStorageProviderTests()
    {
        _mockEnvironment = new Mock<IWebHostEnvironment>();
        _mockLogger = new Mock<ILogger<LocalStorageProvider>>();
        
        // 创建临时测试目录
        _testWebRootPath = Path.Combine(Path.GetTempPath(), $"LocalStorageTest_{Guid.NewGuid():N}");
        Directory.CreateDirectory(_testWebRootPath);
        
        _mockEnvironment.Setup(e => e.WebRootPath).Returns(_testWebRootPath);
        
        _provider = new LocalStorageProvider(_mockEnvironment.Object, _mockLogger.Object);
    }

    public void Dispose()
    {
        // 清理测试目录
        if (Directory.Exists(_testWebRootPath))
        {
            try
            {
                Directory.Delete(_testWebRootPath, true);
            }
            catch
            {
                // 忽略清理错误
            }
        }
    }

    #region StorageType Tests

    [Fact]
    public void StorageType_ShouldReturn1()
    {
        // Assert
        Assert.Equal("1", _provider.StorageType);
    }

    #endregion

    #region Directory Creation Tests

    [Fact]
    public async Task UploadAsync_ShouldCreateDateBasedDirectory()
    {
        // Arrange
        var content = "test content"u8.ToArray();
        using var stream = new MemoryStream(content);
        var fileName = "test.jpg";

        // Act
        var result = await _provider.UploadAsync(stream, fileName, "image/jpeg");

        // Assert
        Assert.True(result.Success);
        
        // 验证目录结构
        var now = DateTime.Now;
        var expectedDir = Path.Combine(_testWebRootPath, "uploads", 
            now.Year.ToString(), 
            now.Month.ToString("D2"), 
            now.Day.ToString("D2"));
        Assert.True(Directory.Exists(expectedDir));
    }

    [Fact]
    public async Task UploadAsync_ShouldCreateNestedDirectories()
    {
        // Arrange - 确保目录不存在
        var uploadsDir = Path.Combine(_testWebRootPath, "uploads");
        if (Directory.Exists(uploadsDir))
        {
            Directory.Delete(uploadsDir, true);
        }

        var content = "test content"u8.ToArray();
        using var stream = new MemoryStream(content);
        var fileName = "test.png";

        // Act
        var result = await _provider.UploadAsync(stream, fileName, "image/png");

        // Assert
        Assert.True(result.Success);
        Assert.True(Directory.Exists(uploadsDir));
    }

    #endregion

    #region File Save Tests

    [Fact]
    public async Task UploadAsync_ShouldSaveFileContent()
    {
        // Arrange
        var expectedContent = "test file content for verification"u8.ToArray();
        using var stream = new MemoryStream(expectedContent);
        var fileName = "content_test.jpg";

        // Act
        var result = await _provider.UploadAsync(stream, fileName, "image/jpeg");

        // Assert
        Assert.True(result.Success);
        Assert.NotNull(result.Url);

        // 验证文件内容
        var physicalPath = Path.Combine(_testWebRootPath, result.Url!.TrimStart('/').Replace('/', Path.DirectorySeparatorChar));
        Assert.True(File.Exists(physicalPath));
        
        var savedContent = await File.ReadAllBytesAsync(physicalPath);
        Assert.Equal(expectedContent, savedContent);
    }

    [Fact]
    public async Task UploadAsync_ShouldPreserveFileExtension()
    {
        // Arrange
        var content = "test"u8.ToArray();
        using var stream = new MemoryStream(content);
        var fileName = "image.webp";

        // Act
        var result = await _provider.UploadAsync(stream, fileName, "image/webp");

        // Assert
        Assert.True(result.Success);
        Assert.NotNull(result.Url);
        Assert.EndsWith(".webp", result.Url);
    }

    [Theory]
    [InlineData("test.jpg", ".jpg")]
    [InlineData("test.JPEG", ".jpeg")]
    [InlineData("test.PNG", ".png")]
    [InlineData("test.gif", ".gif")]
    [InlineData("test.WebP", ".webp")]
    public async Task UploadAsync_ShouldNormalizeExtensionToLowercase(string fileName, string expectedExtension)
    {
        // Arrange
        var content = "test"u8.ToArray();
        using var stream = new MemoryStream(content);

        // Act
        var result = await _provider.UploadAsync(stream, fileName, "image/jpeg");

        // Assert
        Assert.True(result.Success);
        Assert.NotNull(result.Url);
        Assert.EndsWith(expectedExtension, result.Url);
    }

    #endregion

    #region URL Format Tests

    [Fact]
    public async Task UploadAsync_UrlShouldStartWithUploads()
    {
        // Arrange
        var content = "test"u8.ToArray();
        using var stream = new MemoryStream(content);
        var fileName = "test.jpg";

        // Act
        var result = await _provider.UploadAsync(stream, fileName, "image/jpeg");

        // Assert
        Assert.True(result.Success);
        Assert.NotNull(result.Url);
        Assert.StartsWith("/uploads/", result.Url);
    }

    [Fact]
    public async Task UploadAsync_UrlShouldContainDatePath()
    {
        // Arrange
        var content = "test"u8.ToArray();
        using var stream = new MemoryStream(content);
        var fileName = "test.jpg";
        var now = DateTime.Now;

        // Act
        var result = await _provider.UploadAsync(stream, fileName, "image/jpeg");

        // Assert
        Assert.True(result.Success);
        Assert.NotNull(result.Url);
        
        var expectedDatePath = $"/{now.Year}/{now.Month:D2}/{now.Day:D2}/";
        Assert.Contains(expectedDatePath, result.Url);
    }

    [Fact]
    public async Task UploadAsync_UrlShouldUseForwardSlashes()
    {
        // Arrange
        var content = "test"u8.ToArray();
        using var stream = new MemoryStream(content);
        var fileName = "test.jpg";

        // Act
        var result = await _provider.UploadAsync(stream, fileName, "image/jpeg");

        // Assert
        Assert.True(result.Success);
        Assert.NotNull(result.Url);
        Assert.DoesNotContain("\\", result.Url);
    }

    #endregion

    #region Delete Tests

    [Fact]
    public async Task DeleteAsync_ShouldDeleteExistingFile()
    {
        // Arrange - 先上传一个文件
        var content = "test"u8.ToArray();
        using var stream = new MemoryStream(content);
        var uploadResult = await _provider.UploadAsync(stream, "to_delete.jpg", "image/jpeg");
        Assert.True(uploadResult.Success);

        // Act
        var deleteResult = await _provider.DeleteAsync(uploadResult.Url!);

        // Assert
        Assert.True(deleteResult);
        
        var physicalPath = Path.Combine(_testWebRootPath, uploadResult.Url!.TrimStart('/').Replace('/', Path.DirectorySeparatorChar));
        Assert.False(File.Exists(physicalPath));
    }

    [Fact]
    public async Task DeleteAsync_ShouldReturnFalseForNonExistentFile()
    {
        // Arrange
        var nonExistentUrl = "/uploads/2026/01/19/nonexistent.jpg";

        // Act
        var result = await _provider.DeleteAsync(nonExistentUrl);

        // Assert
        Assert.False(result);
    }

    [Fact]
    public async Task DeleteAsync_ShouldReturnFalseForEmptyUrl()
    {
        // Act
        var result = await _provider.DeleteAsync("");

        // Assert
        Assert.False(result);
    }

    [Fact]
    public async Task DeleteAsync_ShouldReturnFalseForNullUrl()
    {
        // Act
        var result = await _provider.DeleteAsync(null!);

        // Assert
        Assert.False(result);
    }

    #endregion
}

/// <summary>
/// LocalStorageProvider 属性测试
/// **Property 4: 存储策略一致性 - 本地存储URL格式**
/// **Validates: Requirements 2.3**
/// </summary>
public class LocalStorageProviderPropertyTests : IDisposable
{
    private readonly string _testWebRootPath;

    public LocalStorageProviderPropertyTests()
    {
        _testWebRootPath = Path.Combine(Path.GetTempPath(), $"LocalStoragePropertyTest_{Guid.NewGuid():N}");
        Directory.CreateDirectory(_testWebRootPath);
    }

    public void Dispose()
    {
        if (Directory.Exists(_testWebRootPath))
        {
            try
            {
                Directory.Delete(_testWebRootPath, true);
            }
            catch
            {
                // 忽略清理错误
            }
        }
    }

    /// <summary>
    /// **Feature: image-upload-feature, Property 4: 存储策略一致性 - 本地存储URL格式**
    /// *For any* 上传操作，返回的URL格式 SHALL 与本地存储类型一致：URL以 `/uploads/` 开头
    /// **Validates: Requirements 2.3**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool LocalStorage_UrlFormat_ShouldStartWithUploads(PositiveInt seed)
    {
        var mockEnvironment = new Mock<IWebHostEnvironment>();
        var mockLogger = new Mock<ILogger<LocalStorageProvider>>();
        
        var testDir = Path.Combine(_testWebRootPath, $"test_{seed.Get}");
        Directory.CreateDirectory(testDir);
        mockEnvironment.Setup(e => e.WebRootPath).Returns(testDir);
        
        var provider = new LocalStorageProvider(mockEnvironment.Object, mockLogger.Object);
        
        var extensions = new[] { ".jpg", ".jpeg", ".png", ".gif", ".webp" };
        var extension = extensions[seed.Get % extensions.Length];
        var fileName = $"test{extension}";
        
        var content = System.Text.Encoding.UTF8.GetBytes($"test content {seed.Get}");
        using var stream = new MemoryStream(content);
        
        var result = provider.UploadAsync(stream, fileName, "image/jpeg").GetAwaiter().GetResult();
        
        if (!result.Success || result.Url == null)
            return false;
        
        // 验证URL以 /uploads/ 开头
        return result.Url.StartsWith("/uploads/");
    }

    /// <summary>
    /// **Feature: image-upload-feature, Property 4: 存储策略一致性 - 本地存储URL格式**
    /// *For any* 上传操作，返回的URL SHALL 包含日期路径格式 yyyy/MM/dd
    /// **Validates: Requirements 2.2, 2.3**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool LocalStorage_UrlFormat_ShouldContainDatePath(PositiveInt seed)
    {
        var mockEnvironment = new Mock<IWebHostEnvironment>();
        var mockLogger = new Mock<ILogger<LocalStorageProvider>>();
        
        var testDir = Path.Combine(_testWebRootPath, $"date_test_{seed.Get}");
        Directory.CreateDirectory(testDir);
        mockEnvironment.Setup(e => e.WebRootPath).Returns(testDir);
        
        var provider = new LocalStorageProvider(mockEnvironment.Object, mockLogger.Object);
        
        var fileName = $"test_{seed.Get}.jpg";
        var content = System.Text.Encoding.UTF8.GetBytes($"content {seed.Get}");
        using var stream = new MemoryStream(content);
        
        var result = provider.UploadAsync(stream, fileName, "image/jpeg").GetAwaiter().GetResult();
        
        if (!result.Success || result.Url == null)
            return false;
        
        var now = DateTime.Now;
        var expectedDatePath = $"/{now.Year}/{now.Month:D2}/{now.Day:D2}/";
        
        return result.Url.Contains(expectedDatePath);
    }

    /// <summary>
    /// **Feature: image-upload-feature, Property 3: 文件名唯一性**
    /// *For any* 两次上传操作，即使上传相同的文件，生成的文件名 SHALL 不同
    /// **Validates: Requirements 1.5**
    /// </summary>
    [Property(MaxTest = 50)]
    public bool LocalStorage_FileNames_ShouldBeUnique(PositiveInt seed)
    {
        var mockEnvironment = new Mock<IWebHostEnvironment>();
        var mockLogger = new Mock<ILogger<LocalStorageProvider>>();
        
        var testDir = Path.Combine(_testWebRootPath, $"unique_test_{seed.Get}");
        Directory.CreateDirectory(testDir);
        mockEnvironment.Setup(e => e.WebRootPath).Returns(testDir);
        
        var provider = new LocalStorageProvider(mockEnvironment.Object, mockLogger.Object);
        
        var fileName = "same_file.jpg";
        var content = "same content"u8.ToArray();
        
        // 第一次上传
        using var stream1 = new MemoryStream(content);
        var result1 = provider.UploadAsync(stream1, fileName, "image/jpeg").GetAwaiter().GetResult();
        
        // 第二次上传（相同文件名）
        using var stream2 = new MemoryStream(content);
        var result2 = provider.UploadAsync(stream2, fileName, "image/jpeg").GetAwaiter().GetResult();
        
        if (!result1.Success || !result2.Success)
            return false;
        
        // 验证两次上传的URL不同
        return result1.Url != result2.Url;
    }

    /// <summary>
    /// **Feature: image-upload-feature, Property 4: 存储策略一致性**
    /// *For any* 成功上传的文件，URL对应的物理文件 SHALL 存在
    /// **Validates: Requirements 2.3**
    /// </summary>
    [Property(MaxTest = 50)]
    public bool LocalStorage_UploadedFile_ShouldExistOnDisk(PositiveInt seed)
    {
        var mockEnvironment = new Mock<IWebHostEnvironment>();
        var mockLogger = new Mock<ILogger<LocalStorageProvider>>();
        
        var testDir = Path.Combine(_testWebRootPath, $"exist_test_{seed.Get}");
        Directory.CreateDirectory(testDir);
        mockEnvironment.Setup(e => e.WebRootPath).Returns(testDir);
        
        var provider = new LocalStorageProvider(mockEnvironment.Object, mockLogger.Object);
        
        var fileName = $"file_{seed.Get}.jpg";
        var content = System.Text.Encoding.UTF8.GetBytes($"content for {seed.Get}");
        using var stream = new MemoryStream(content);
        
        var result = provider.UploadAsync(stream, fileName, "image/jpeg").GetAwaiter().GetResult();
        
        if (!result.Success || result.Url == null)
            return false;
        
        // 验证物理文件存在
        var physicalPath = Path.Combine(testDir, result.Url.TrimStart('/').Replace('/', Path.DirectorySeparatorChar));
        return File.Exists(physicalPath);
    }
}
