using FsCheck;
using FsCheck.Xunit;
using HoneyBox.Admin.Business.Models;
using HoneyBox.Admin.Business.Models.Announcement;
using HoneyBox.Admin.Business.Services;
using HoneyBox.Model.Data;
using HoneyBox.Model.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;

namespace HoneyBox.Tests.Services;

/// <summary>
/// AnnouncementService 属性测试
/// 测试首页中大奖公告功能的核心属性
/// </summary>
public class AnnouncementServicePropertyTests
{
    private readonly Mock<ILogger<AnnouncementService>> _mockLogger = new();

    private (HoneyBoxDbContext dbContext, AnnouncementService service) CreateService()
    {
        var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;
        var dbContext = new HoneyBoxDbContext(options);
        var service = new AnnouncementService(dbContext, _mockLogger.Object);
        return (dbContext, service);
    }

    /// <summary>
    /// 生成有效的非空字符串（用于必填字段）
    /// </summary>
    private static string GenerateValidString(string prefix, int seed)
    {
        return $"{prefix}_{Math.Abs(seed) % 1000}";
    }

    #region Property 1: CRUD Round-Trip Consistency

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 1: CRUD Round-Trip Consistency**
    /// For any valid announcement data, creating an announcement and then reading it back 
    /// should return the same data.
    /// **Validates: Requirements 1.1, 1.2, 1.3, 2.4**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool CrudRoundTrip_CreateAndRead_ShouldReturnSameData(
        NonEmptyString userName,
        NonEmptyString prizeLevel,
        NonEmptyString prizeName,
        PositiveInt sort,
        bool isEnabled)
    {
        var actualUserName = userName.Get.Trim();
        var actualPrizeLevel = prizeLevel.Get.Trim();
        var actualPrizeName = prizeName.Get.Trim();
        var actualSort = sort.Get % 1000;

        // Skip if any required field becomes empty after trim
        if (string.IsNullOrWhiteSpace(actualUserName) ||
            string.IsNullOrWhiteSpace(actualPrizeLevel) ||
            string.IsNullOrWhiteSpace(actualPrizeName))
        {
            return true; // Skip this test case
        }

        var (dbContext, service) = CreateService();

        try
        {
            // Create announcement
            var createRequest = new CreateAnnouncementRequest
            {
                UserAvatar = "http://test.com/avatar.jpg",
                UserName = actualUserName,
                PrizeLevel = actualPrizeLevel,
                PrizeName = actualPrizeName,
                Sort = actualSort,
                IsEnabled = isEnabled
            };

            var created = service.CreateAsync(createRequest).GetAwaiter().GetResult();

            // Read it back
            var retrieved = service.GetByIdAsync(created.Id).GetAwaiter().GetResult();

            // Verify data consistency
            return retrieved.UserName == actualUserName &&
                   retrieved.PrizeLevel == actualPrizeLevel &&
                   retrieved.PrizeName == actualPrizeName &&
                   retrieved.Sort == actualSort &&
                   retrieved.IsEnabled == isEnabled &&
                   retrieved.UserAvatar == "http://test.com/avatar.jpg";
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 1: CRUD Round-Trip Consistency**
    /// For any valid announcement data, updating it and reading again should reflect the updates.
    /// **Validates: Requirements 1.1, 1.2, 1.3, 2.4**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool CrudRoundTrip_UpdateAndRead_ShouldReflectUpdates(
        PositiveInt seed1,
        PositiveInt seed2,
        PositiveInt sort1,
        PositiveInt sort2,
        bool isEnabled1,
        bool isEnabled2)
    {
        var userName1 = GenerateValidString("User", seed1.Get);
        var prizeLevel1 = GenerateValidString("Level", seed1.Get);
        var prizeName1 = GenerateValidString("Prize", seed1.Get);
        var userName2 = GenerateValidString("UpdatedUser", seed2.Get);
        var prizeLevel2 = GenerateValidString("UpdatedLevel", seed2.Get);
        var prizeName2 = GenerateValidString("UpdatedPrize", seed2.Get);
        var actualSort1 = sort1.Get % 1000;
        var actualSort2 = sort2.Get % 1000;

        var (dbContext, service) = CreateService();

        try
        {
            // Create initial announcement
            var createRequest = new CreateAnnouncementRequest
            {
                UserAvatar = "http://test.com/avatar1.jpg",
                UserName = userName1,
                PrizeLevel = prizeLevel1,
                PrizeName = prizeName1,
                Sort = actualSort1,
                IsEnabled = isEnabled1
            };

            var created = service.CreateAsync(createRequest).GetAwaiter().GetResult();

            // Update the announcement
            var updateRequest = new UpdateAnnouncementRequest
            {
                UserAvatar = "http://test.com/avatar2.jpg",
                UserName = userName2,
                PrizeLevel = prizeLevel2,
                PrizeName = prizeName2,
                Sort = actualSort2,
                IsEnabled = isEnabled2
            };

            service.UpdateAsync(created.Id, updateRequest).GetAwaiter().GetResult();

            // Read it back
            var retrieved = service.GetByIdAsync(created.Id).GetAwaiter().GetResult();

            // Verify updates are reflected
            return retrieved.UserName == userName2 &&
                   retrieved.PrizeLevel == prizeLevel2 &&
                   retrieved.PrizeName == prizeName2 &&
                   retrieved.Sort == actualSort2 &&
                   retrieved.IsEnabled == isEnabled2 &&
                   retrieved.UserAvatar == "http://test.com/avatar2.jpg";
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 1: CRUD Round-Trip Consistency**
    /// For any valid announcement, deleting it should make it no longer retrievable.
    /// **Validates: Requirements 1.1, 1.2, 1.3, 2.4**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool CrudRoundTrip_Delete_ShouldMakeUnretrievable(PositiveInt seed)
    {
        var userName = GenerateValidString("User", seed.Get);
        var prizeLevel = GenerateValidString("Level", seed.Get);
        var prizeName = GenerateValidString("Prize", seed.Get);

        var (dbContext, service) = CreateService();

        try
        {
            // Create announcement
            var createRequest = new CreateAnnouncementRequest
            {
                UserName = userName,
                PrizeLevel = prizeLevel,
                PrizeName = prizeName,
                Sort = 0,
                IsEnabled = true
            };

            var created = service.CreateAsync(createRequest).GetAwaiter().GetResult();
            var createdId = created.Id;

            // Delete the announcement
            var deleteResult = service.DeleteAsync(createdId).GetAwaiter().GetResult();

            if (!deleteResult)
            {
                return false;
            }

            // Try to retrieve it - should throw NotFound exception
            try
            {
                service.GetByIdAsync(createdId).GetAwaiter().GetResult();
                return false; // Should not reach here
            }
            catch (BusinessException ex)
            {
                return ex.Code == BusinessErrorCodes.NotFound;
            }
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 1: CRUD Round-Trip Consistency**
    /// Partial updates should only modify specified fields, leaving others unchanged.
    /// **Validates: Requirements 1.1, 1.2, 1.3, 2.4**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool CrudRoundTrip_PartialUpdate_ShouldOnlyModifySpecifiedFields(
        PositiveInt seed,
        PositiveInt newSort)
    {
        var userName = GenerateValidString("User", seed.Get);
        var prizeLevel = GenerateValidString("Level", seed.Get);
        var prizeName = GenerateValidString("Prize", seed.Get);
        var originalSort = seed.Get % 500;
        var updatedSort = (newSort.Get % 500) + 500; // Ensure different value

        var (dbContext, service) = CreateService();

        try
        {
            // Create announcement
            var createRequest = new CreateAnnouncementRequest
            {
                UserAvatar = "http://test.com/original.jpg",
                UserName = userName,
                PrizeLevel = prizeLevel,
                PrizeName = prizeName,
                Sort = originalSort,
                IsEnabled = true
            };

            var created = service.CreateAsync(createRequest).GetAwaiter().GetResult();

            // Partial update - only update Sort
            var updateRequest = new UpdateAnnouncementRequest
            {
                Sort = updatedSort
            };

            service.UpdateAsync(created.Id, updateRequest).GetAwaiter().GetResult();

            // Read it back
            var retrieved = service.GetByIdAsync(created.Id).GetAwaiter().GetResult();

            // Verify only Sort changed, other fields remain the same
            return retrieved.Sort == updatedSort &&
                   retrieved.UserName == userName &&
                   retrieved.PrizeLevel == prizeLevel &&
                   retrieved.PrizeName == prizeName &&
                   retrieved.UserAvatar == "http://test.com/original.jpg" &&
                   retrieved.IsEnabled == true;
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 1: CRUD Round-Trip Consistency**
    /// Multiple CRUD operations should maintain data consistency.
    /// **Validates: Requirements 1.1, 1.2, 1.3, 2.4**
    /// </summary>
    [Property(MaxTest = 50)]
    public bool CrudRoundTrip_MultipleCrudOperations_ShouldMaintainConsistency(PositiveInt operationCount)
    {
        var actualCount = Math.Max(2, operationCount.Get % 5 + 2);

        var (dbContext, service) = CreateService();

        try
        {
            var createdIds = new List<int>();

            // Create multiple announcements
            for (int i = 0; i < actualCount; i++)
            {
                var createRequest = new CreateAnnouncementRequest
                {
                    UserName = $"User_{i}",
                    PrizeLevel = $"Level_{i}",
                    PrizeName = $"Prize_{i}",
                    Sort = i,
                    IsEnabled = true
                };

                var created = service.CreateAsync(createRequest).GetAwaiter().GetResult();
                createdIds.Add(created.Id);
            }

            // Update each announcement
            for (int i = 0; i < actualCount; i++)
            {
                var updateRequest = new UpdateAnnouncementRequest
                {
                    UserName = $"UpdatedUser_{i}",
                    Sort = i + 100
                };

                service.UpdateAsync(createdIds[i], updateRequest).GetAwaiter().GetResult();
            }

            // Verify all updates
            for (int i = 0; i < actualCount; i++)
            {
                var retrieved = service.GetByIdAsync(createdIds[i]).GetAwaiter().GetResult();
                if (retrieved.UserName != $"UpdatedUser_{i}" || retrieved.Sort != i + 100)
                {
                    return false;
                }
            }

            // Delete half of them
            for (int i = 0; i < actualCount / 2; i++)
            {
                service.DeleteAsync(createdIds[i]).GetAwaiter().GetResult();
            }

            // Verify deleted ones are not retrievable
            for (int i = 0; i < actualCount / 2; i++)
            {
                try
                {
                    service.GetByIdAsync(createdIds[i]).GetAwaiter().GetResult();
                    return false; // Should have thrown
                }
                catch (BusinessException ex)
                {
                    if (ex.Code != BusinessErrorCodes.NotFound)
                    {
                        return false;
                    }
                }
            }

            // Verify remaining ones are still retrievable
            for (int i = actualCount / 2; i < actualCount; i++)
            {
                var retrieved = service.GetByIdAsync(createdIds[i]).GetAwaiter().GetResult();
                if (retrieved.UserName != $"UpdatedUser_{i}")
                {
                    return false;
                }
            }

            return true;
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    #endregion

    #region Property 4: Required Field Validation

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 4: Required Field Validation**
    /// For any create request where UserName is empty or whitespace-only, 
    /// the system should reject the request and return a validation error.
    /// **Validates: Requirements 1.5**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool RequiredFieldValidation_EmptyUserName_ShouldRejectCreate(PositiveInt seed)
    {
        var prizeLevel = GenerateValidString("Level", seed.Get);
        var prizeName = GenerateValidString("Prize", seed.Get);

        var (dbContext, service) = CreateService();

        try
        {
            var emptyUserNames = new[] { "", "   ", "\t", "\n", "  \t\n  " };

            foreach (var emptyUserName in emptyUserNames)
            {
                var createRequest = new CreateAnnouncementRequest
                {
                    UserName = emptyUserName,
                    PrizeLevel = prizeLevel,
                    PrizeName = prizeName,
                    Sort = 0,
                    IsEnabled = true
                };

                try
                {
                    service.CreateAsync(createRequest).GetAwaiter().GetResult();
                    return false; // Should have thrown
                }
                catch (BusinessException ex)
                {
                    if (ex.Code != BusinessErrorCodes.ValidationFailed)
                    {
                        return false;
                    }
                }
            }

            // Verify database is not modified
            var count = dbContext.PrizeAnnouncements.Count();
            return count == 0;
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 4: Required Field Validation**
    /// For any create request where PrizeLevel is empty or whitespace-only, 
    /// the system should reject the request and return a validation error.
    /// **Validates: Requirements 1.5**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool RequiredFieldValidation_EmptyPrizeLevel_ShouldRejectCreate(PositiveInt seed)
    {
        var userName = GenerateValidString("User", seed.Get);
        var prizeName = GenerateValidString("Prize", seed.Get);

        var (dbContext, service) = CreateService();

        try
        {
            var emptyPrizeLevels = new[] { "", "   ", "\t", "\n", "  \t\n  " };

            foreach (var emptyPrizeLevel in emptyPrizeLevels)
            {
                var createRequest = new CreateAnnouncementRequest
                {
                    UserName = userName,
                    PrizeLevel = emptyPrizeLevel,
                    PrizeName = prizeName,
                    Sort = 0,
                    IsEnabled = true
                };

                try
                {
                    service.CreateAsync(createRequest).GetAwaiter().GetResult();
                    return false; // Should have thrown
                }
                catch (BusinessException ex)
                {
                    if (ex.Code != BusinessErrorCodes.ValidationFailed)
                    {
                        return false;
                    }
                }
            }

            // Verify database is not modified
            var count = dbContext.PrizeAnnouncements.Count();
            return count == 0;
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 4: Required Field Validation**
    /// For any create request where PrizeName is empty or whitespace-only, 
    /// the system should reject the request and return a validation error.
    /// **Validates: Requirements 1.5**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool RequiredFieldValidation_EmptyPrizeName_ShouldRejectCreate(PositiveInt seed)
    {
        var userName = GenerateValidString("User", seed.Get);
        var prizeLevel = GenerateValidString("Level", seed.Get);

        var (dbContext, service) = CreateService();

        try
        {
            var emptyPrizeNames = new[] { "", "   ", "\t", "\n", "  \t\n  " };

            foreach (var emptyPrizeName in emptyPrizeNames)
            {
                var createRequest = new CreateAnnouncementRequest
                {
                    UserName = userName,
                    PrizeLevel = prizeLevel,
                    PrizeName = emptyPrizeName,
                    Sort = 0,
                    IsEnabled = true
                };

                try
                {
                    service.CreateAsync(createRequest).GetAwaiter().GetResult();
                    return false; // Should have thrown
                }
                catch (BusinessException ex)
                {
                    if (ex.Code != BusinessErrorCodes.ValidationFailed)
                    {
                        return false;
                    }
                }
            }

            // Verify database is not modified
            var count = dbContext.PrizeAnnouncements.Count();
            return count == 0;
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 4: Required Field Validation**
    /// For any update request where UserName is set to empty or whitespace-only, 
    /// the system should reject the request and return a validation error without modifying the database.
    /// **Validates: Requirements 1.5**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool RequiredFieldValidation_EmptyUserName_ShouldRejectUpdate(PositiveInt seed)
    {
        var userName = GenerateValidString("User", seed.Get);
        var prizeLevel = GenerateValidString("Level", seed.Get);
        var prizeName = GenerateValidString("Prize", seed.Get);

        var (dbContext, service) = CreateService();

        try
        {
            // First create a valid announcement
            var createRequest = new CreateAnnouncementRequest
            {
                UserName = userName,
                PrizeLevel = prizeLevel,
                PrizeName = prizeName,
                Sort = 0,
                IsEnabled = true
            };

            var created = service.CreateAsync(createRequest).GetAwaiter().GetResult();

            var emptyUserNames = new[] { "", "   ", "\t", "\n" };

            foreach (var emptyUserName in emptyUserNames)
            {
                var updateRequest = new UpdateAnnouncementRequest
                {
                    UserName = emptyUserName
                };

                try
                {
                    service.UpdateAsync(created.Id, updateRequest).GetAwaiter().GetResult();
                    return false; // Should have thrown
                }
                catch (BusinessException ex)
                {
                    if (ex.Code != BusinessErrorCodes.ValidationFailed)
                    {
                        return false;
                    }
                }
            }

            // Verify original data is not modified
            var retrieved = service.GetByIdAsync(created.Id).GetAwaiter().GetResult();
            return retrieved.UserName == userName;
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 4: Required Field Validation**
    /// For any update request where PrizeLevel is set to empty or whitespace-only, 
    /// the system should reject the request and return a validation error without modifying the database.
    /// **Validates: Requirements 1.5**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool RequiredFieldValidation_EmptyPrizeLevel_ShouldRejectUpdate(PositiveInt seed)
    {
        var userName = GenerateValidString("User", seed.Get);
        var prizeLevel = GenerateValidString("Level", seed.Get);
        var prizeName = GenerateValidString("Prize", seed.Get);

        var (dbContext, service) = CreateService();

        try
        {
            // First create a valid announcement
            var createRequest = new CreateAnnouncementRequest
            {
                UserName = userName,
                PrizeLevel = prizeLevel,
                PrizeName = prizeName,
                Sort = 0,
                IsEnabled = true
            };

            var created = service.CreateAsync(createRequest).GetAwaiter().GetResult();

            var emptyPrizeLevels = new[] { "", "   ", "\t", "\n" };

            foreach (var emptyPrizeLevel in emptyPrizeLevels)
            {
                var updateRequest = new UpdateAnnouncementRequest
                {
                    PrizeLevel = emptyPrizeLevel
                };

                try
                {
                    service.UpdateAsync(created.Id, updateRequest).GetAwaiter().GetResult();
                    return false; // Should have thrown
                }
                catch (BusinessException ex)
                {
                    if (ex.Code != BusinessErrorCodes.ValidationFailed)
                    {
                        return false;
                    }
                }
            }

            // Verify original data is not modified
            var retrieved = service.GetByIdAsync(created.Id).GetAwaiter().GetResult();
            return retrieved.PrizeLevel == prizeLevel;
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 4: Required Field Validation**
    /// For any update request where PrizeName is set to empty or whitespace-only, 
    /// the system should reject the request and return a validation error without modifying the database.
    /// **Validates: Requirements 1.5**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool RequiredFieldValidation_EmptyPrizeName_ShouldRejectUpdate(PositiveInt seed)
    {
        var userName = GenerateValidString("User", seed.Get);
        var prizeLevel = GenerateValidString("Level", seed.Get);
        var prizeName = GenerateValidString("Prize", seed.Get);

        var (dbContext, service) = CreateService();

        try
        {
            // First create a valid announcement
            var createRequest = new CreateAnnouncementRequest
            {
                UserName = userName,
                PrizeLevel = prizeLevel,
                PrizeName = prizeName,
                Sort = 0,
                IsEnabled = true
            };

            var created = service.CreateAsync(createRequest).GetAwaiter().GetResult();

            var emptyPrizeNames = new[] { "", "   ", "\t", "\n" };

            foreach (var emptyPrizeName in emptyPrizeNames)
            {
                var updateRequest = new UpdateAnnouncementRequest
                {
                    PrizeName = emptyPrizeName
                };

                try
                {
                    service.UpdateAsync(created.Id, updateRequest).GetAwaiter().GetResult();
                    return false; // Should have thrown
                }
                catch (BusinessException ex)
                {
                    if (ex.Code != BusinessErrorCodes.ValidationFailed)
                    {
                        return false;
                    }
                }
            }

            // Verify original data is not modified
            var retrieved = service.GetByIdAsync(created.Id).GetAwaiter().GetResult();
            return retrieved.PrizeName == prizeName;
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 4: Required Field Validation**
    /// For any create request with all required fields empty, 
    /// the system should reject the request without modifying the database.
    /// **Validates: Requirements 1.5**
    /// </summary>
    [Property(MaxTest = 50)]
    public bool RequiredFieldValidation_AllFieldsEmpty_ShouldRejectCreate(PositiveInt seed)
    {
        var (dbContext, service) = CreateService();

        try
        {
            var createRequest = new CreateAnnouncementRequest
            {
                UserName = "",
                PrizeLevel = "",
                PrizeName = "",
                Sort = 0,
                IsEnabled = true
            };

            try
            {
                service.CreateAsync(createRequest).GetAwaiter().GetResult();
                return false; // Should have thrown
            }
            catch (BusinessException ex)
            {
                if (ex.Code != BusinessErrorCodes.ValidationFailed)
                {
                    return false;
                }
            }

            // Verify database is not modified
            var count = dbContext.PrizeAnnouncements.Count();
            return count == 0;
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 4: Required Field Validation**
    /// Valid requests with non-empty required fields should succeed.
    /// **Validates: Requirements 1.5**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool RequiredFieldValidation_ValidFields_ShouldSucceed(
        NonEmptyString userName,
        NonEmptyString prizeLevel,
        NonEmptyString prizeName)
    {
        var actualUserName = userName.Get.Trim();
        var actualPrizeLevel = prizeLevel.Get.Trim();
        var actualPrizeName = prizeName.Get.Trim();

        // Skip if any required field becomes empty after trim
        if (string.IsNullOrWhiteSpace(actualUserName) ||
            string.IsNullOrWhiteSpace(actualPrizeLevel) ||
            string.IsNullOrWhiteSpace(actualPrizeName))
        {
            return true; // Skip this test case
        }

        var (dbContext, service) = CreateService();

        try
        {
            var createRequest = new CreateAnnouncementRequest
            {
                UserName = actualUserName,
                PrizeLevel = actualPrizeLevel,
                PrizeName = actualPrizeName,
                Sort = 0,
                IsEnabled = true
            };

            var created = service.CreateAsync(createRequest).GetAwaiter().GetResult();

            // Verify creation succeeded
            return created.Id > 0 &&
                   created.UserName == actualUserName &&
                   created.PrizeLevel == actualPrizeLevel &&
                   created.PrizeName == actualPrizeName;
        }
        catch (BusinessException)
        {
            return false; // Should not throw for valid data
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    #endregion
}
