using FluentAssertions;
using StorageService.Domain.AggregatesModel.FileAggregate;
using Xunit;

namespace StorageService.UnitTests.Domain;
/// <summary>
/// EN: Tests for StorageFile aggregate root.
/// VI: Kiểm thử cho aggregate root StorageFile.
/// </summary>
public class StorageFileTests
{
    private const string ValidFileName = "test-file.pdf";
    private const string ValidBucketName = "storage-bucket";
    private const string ValidObjectKey = "private/user-123/20260115/abc123_test-file.pdf";
    private const string ValidContentType = "application/pdf";
    private const long ValidFileSize = 1024 * 1024; // 1MB
    private const string ValidUserId = "user-123";

    #region Constructor Tests

    [Fact]
    public void Constructor_ValidParams_CreatesFileWithCorrectProperties()
    {
        // Arrange & Act
        var file = new StorageFile(
            ValidFileName,
            ValidBucketName,
            ValidObjectKey,
            ValidContentType,
            ValidFileSize,
            ValidUserId,
            StorageProvider.MinIO,
            FileAccessLevel.Private,
            tenantId: "tenant-1",
            checksum: "abc123");

        // Assert
        file.FileName.Should().Be(ValidFileName);
        file.BucketName.Should().Be(ValidBucketName);
        file.ObjectKey.Should().Be(ValidObjectKey);
        file.ContentType.Should().Be(ValidContentType);
        file.FileSizeBytes.Should().Be(ValidFileSize);
        file.UserId.Should().Be(ValidUserId);
        file.Provider.Should().Be(StorageProvider.MinIO);
        file.AccessLevel.Should().Be(FileAccessLevel.Private);
        file.TenantId.Should().Be("tenant-1");
        file.Checksum.Should().Be("abc123");
        file.IsDeleted.Should().BeFalse();
        file.UploadedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void Constructor_ValidParams_RaisesFileUploadedDomainEvent()
    {
        // Arrange & Act
        var file = CreateValidStorageFile();

        // Assert
        file.DomainEvents.Should().ContainSingle(e => e is FileUploadedDomainEvent);
        
        var domainEvent = file.DomainEvents.OfType<FileUploadedDomainEvent>().First();
        domainEvent.FileId.Should().Be(file.Id);
        domainEvent.FileName.Should().Be(ValidFileName);
        domainEvent.UserId.Should().Be(ValidUserId);
        domainEvent.FileSizeBytes.Should().Be(ValidFileSize);
    }

    [Fact]
    public void Constructor_NullFileName_ThrowsArgumentNullException()
    {
        // Arrange & Act
        var act = () => new StorageFile(
            null!,
            ValidBucketName,
            ValidObjectKey,
            ValidContentType,
            ValidFileSize,
            ValidUserId,
            StorageProvider.MinIO);

        // Assert
        act.Should().Throw<ArgumentNullException>()
            .WithParameterName("fileName");
    }

    [Fact]
    public void Constructor_NullBucketName_ThrowsArgumentNullException()
    {
        // Arrange & Act
        var act = () => new StorageFile(
            ValidFileName,
            null!,
            ValidObjectKey,
            ValidContentType,
            ValidFileSize,
            ValidUserId,
            StorageProvider.MinIO);

        // Assert
        act.Should().Throw<ArgumentNullException>()
            .WithParameterName("bucketName");
    }

    [Fact]
    public void Constructor_NullObjectKey_ThrowsArgumentNullException()
    {
        // Arrange & Act
        var act = () => new StorageFile(
            ValidFileName,
            ValidBucketName,
            null!,
            ValidContentType,
            ValidFileSize,
            ValidUserId,
            StorageProvider.MinIO);

        // Assert
        act.Should().Throw<ArgumentNullException>()
            .WithParameterName("objectKey");
    }

    [Fact]
    public void Constructor_NullUserId_ThrowsArgumentNullException()
    {
        // Arrange & Act
        var act = () => new StorageFile(
            ValidFileName,
            ValidBucketName,
            ValidObjectKey,
            ValidContentType,
            ValidFileSize,
            null!,
            StorageProvider.MinIO);

        // Assert
        act.Should().Throw<ArgumentNullException>()
            .WithParameterName("userId");
    }

    [Fact]
    public void Constructor_NullContentType_SetsDefaultContentType()
    {
        // Arrange & Act
        var file = new StorageFile(
            ValidFileName,
            ValidBucketName,
            ValidObjectKey,
            null!,
            ValidFileSize,
            ValidUserId,
            StorageProvider.MinIO);

        // Assert
        file.ContentType.Should().Be("application/octet-stream");
    }

    #endregion

    #region MarkAccessed Tests

    [Fact]
    public void MarkAccessed_UpdatesLastAccessedAt()
    {
        // Arrange
        var file = CreateValidStorageFile();
        var beforeAccess = DateTime.UtcNow;

        // Act
        file.MarkAccessed();

        // Assert
        file.LastAccessedAt.Should().NotBeNull();
        file.LastAccessedAt.Should().BeOnOrAfter(beforeAccess);
        file.LastAccessedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
    }

    [Fact]
    public void MarkAccessed_CalledMultipleTimes_UpdatesToLatestTime()
    {
        // Arrange
        var file = CreateValidStorageFile();
        file.MarkAccessed();
        var firstAccess = file.LastAccessedAt;

        // Act
        Thread.Sleep(10); // Small delay
        file.MarkAccessed();

        // Assert
        file.LastAccessedAt.Should().BeAfter(firstAccess!.Value);
    }

    #endregion

    #region UpdateAccessLevel Tests

    [Fact]
    public void UpdateAccessLevel_NonDeletedFile_UpdatesAccessLevel()
    {
        // Arrange
        var file = CreateValidStorageFile();
        file.AccessLevel.Should().Be(FileAccessLevel.Private);

        // Act
        file.UpdateAccessLevel(FileAccessLevel.Public);

        // Assert
        file.AccessLevel.Should().Be(FileAccessLevel.Public);
    }

    [Fact]
    public void UpdateAccessLevel_ToShared_UpdatesAccessLevel()
    {
        // Arrange
        var file = CreateValidStorageFile();

        // Act
        file.UpdateAccessLevel(FileAccessLevel.Shared);

        // Assert
        file.AccessLevel.Should().Be(FileAccessLevel.Shared);
    }

    [Fact]
    public void UpdateAccessLevel_DeletedFile_ThrowsInvalidOperationException()
    {
        // Arrange
        var file = CreateValidStorageFile();
        file.Delete();

        // Act
        var act = () => file.UpdateAccessLevel(FileAccessLevel.Public);

        // Assert
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*deleted*");
    }

    #endregion

    #region Delete Tests

    [Fact]
    public void Delete_SetsIsDeletedAndRaisesEvent()
    {
        // Arrange
        var file = CreateValidStorageFile();
        file.ClearDomainEvents(); // Clear constructor event

        // Act
        file.Delete();

        // Assert
        file.IsDeleted.Should().BeTrue();
        file.DeletedAt.Should().NotBeNull();
        file.DeletedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
        file.DomainEvents.Should().ContainSingle(e => e is FileDeletedDomainEvent);
        
        var deletedEvent = file.DomainEvents.OfType<FileDeletedDomainEvent>().First();
        deletedEvent.FileId.Should().Be(file.Id);
        deletedEvent.UserId.Should().Be(ValidUserId);
        deletedEvent.FileSizeBytes.Should().Be(ValidFileSize);
    }

    [Fact]
    public void Delete_AlreadyDeleted_DoesNothing()
    {
        // Arrange
        var file = CreateValidStorageFile();
        file.Delete();
        var firstDeletedAt = file.DeletedAt;
        file.ClearDomainEvents();

        // Act
        Thread.Sleep(10);
        file.Delete();

        // Assert
        file.DeletedAt.Should().Be(firstDeletedAt);
        file.DomainEvents.Should().BeEmpty(); // No new event raised
    }

    #endregion

    #region SetExpiration Tests

    [Fact]
    public void SetExpiration_FutureDate_SetsExpiresAt()
    {
        // Arrange
        var file = CreateValidStorageFile();
        var futureDate = DateTime.UtcNow.AddDays(7);

        // Act
        file.SetExpiration(futureDate);

        // Assert
        file.ExpiresAt.Should().Be(futureDate);
    }

    [Fact]
    public void SetExpiration_PastDate_ThrowsArgumentException()
    {
        // Arrange
        var file = CreateValidStorageFile();
        var pastDate = DateTime.UtcNow.AddHours(-1);

        // Act
        var act = () => file.SetExpiration(pastDate);

        // Assert
        act.Should().Throw<ArgumentException>()
            .WithParameterName("expiresAt")
            .WithMessage("*future*");
    }

    [Fact]
    public void SetExpiration_CurrentTime_ThrowsArgumentException()
    {
        // Arrange
        var file = CreateValidStorageFile();

        // Act
        var act = () => file.SetExpiration(DateTime.UtcNow);

        // Assert
        act.Should().Throw<ArgumentException>();
    }

    #endregion

    #region UpdateFromVersion Tests

    [Fact]
    public void UpdateFromVersion_NonDeletedFile_UpdatesProperties()
    {
        // Arrange
        var file = CreateValidStorageFile();
        var newObjectKey = "private/user-123/20260115/new_version.pdf";
        var newSize = 2 * 1024 * 1024L; // 2MB
        var newContentType = "application/vnd.pdf";

        // Act
        file.UpdateFromVersion(newObjectKey, newSize, newContentType);

        // Assert
        file.ObjectKey.Should().Be(newObjectKey);
        file.FileSizeBytes.Should().Be(newSize);
        file.ContentType.Should().Be(newContentType);
    }

    [Fact]
    public void UpdateFromVersion_DeletedFile_ThrowsInvalidOperationException()
    {
        // Arrange
        var file = CreateValidStorageFile();
        file.Delete();

        // Act
        var act = () => file.UpdateFromVersion("new-key", 1024, "text/plain");

        // Assert
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*deleted*");
    }

    #endregion

    #region Helper Methods

    private static StorageFile CreateValidStorageFile()
    {
        return new StorageFile(
            ValidFileName,
            ValidBucketName,
            ValidObjectKey,
            ValidContentType,
            ValidFileSize,
            ValidUserId,
            StorageProvider.MinIO,
            FileAccessLevel.Private);
    }

    #endregion
}
