using FluentAssertions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using StorageService.API.Application.Commands.FileShare;
using StorageService.Domain.AggregatesModel.FileAggregate;
using StorageService.Domain.AggregatesModel.FileShareAggregate;
using StorageService.Domain.SeedWork;
using Xunit;

// EN: Alias to avoid collision with System.IO.FileShare
// VI: Alias để tránh xung đột với System.IO.FileShare
using DomainFileShare = StorageService.Domain.AggregatesModel.FileShareAggregate.FileShare;

namespace StorageService.UnitTests.Handlers;

/// <summary>
/// EN: Unit tests for CreateFileShareCommandHandler.
/// VI: Unit tests cho CreateFileShareCommandHandler.
/// </summary>
public class FileShareCommandHandlerTests
{
    private readonly IFileRepository _fileRepository;
    private readonly IFileShareRepository _fileShareRepository;
    private readonly ILogger<CreateFileShareCommandHandler> _createLogger;
    private readonly IConfiguration _configuration;
    private readonly IUnitOfWork _unitOfWork;
    private readonly CreateFileShareCommandHandler _createHandler;

    private static readonly Guid TestFileId = Guid.NewGuid();
    private const string TestUserId = "user-123";
    private const string TestBaseUrl = "https://storage.example.com";

    public FileShareCommandHandlerTests()
    {
        _fileRepository = Substitute.For<IFileRepository>();
        _fileShareRepository = Substitute.For<IFileShareRepository>();
        _createLogger = Substitute.For<ILogger<CreateFileShareCommandHandler>>();
        _unitOfWork = Substitute.For<IUnitOfWork>();

        var configData = new Dictionary<string, string?>
        {
            { "App:BaseUrl", TestBaseUrl }
        };
        _configuration = new ConfigurationBuilder()
            .AddInMemoryCollection(configData)
            .Build();

        _fileShareRepository.UnitOfWork.Returns(_unitOfWork);

        _createHandler = new CreateFileShareCommandHandler(
            _fileRepository,
            _fileShareRepository,
            _createLogger,
            _configuration);
    }

    #region CreateShare Tests

    [Fact]
    public async Task CreateShare_ValidRequest_CreatesShare()
    {
        // Arrange
        var file = CreateRealStorageFile();
        var command = new CreateFileShareCommand(
            file.Id,
            TestUserId,
            SharePermission.Download);

        _fileRepository.GetByIdAsync(file.Id, Arg.Any<CancellationToken>())
            .Returns(file);
        _unitOfWork.SaveEntitiesAsync(Arg.Any<CancellationToken>()).Returns(true);

        // Act
        var result = await _createHandler.Handle(command, CancellationToken.None);

        // Assert
        result.Success.Should().BeTrue();
        result.ShareId.Should().NotBeNull();
        result.ShareToken.Should().NotBeNullOrEmpty();
        result.ShareUrl.Should().StartWith(TestBaseUrl);
        result.Error.Should().BeNull();
    }

    [Fact]
    public async Task CreateShare_ValidRequest_SavesShareToRepository()
    {
        // Arrange
        var file = CreateRealStorageFile();
        var command = new CreateFileShareCommand(
            file.Id,
            TestUserId,
            SharePermission.Download);

        _fileRepository.GetByIdAsync(file.Id, Arg.Any<CancellationToken>())
            .Returns(file);
        _unitOfWork.SaveEntitiesAsync(Arg.Any<CancellationToken>()).Returns(true);

        // Act
        await _createHandler.Handle(command, CancellationToken.None);

        // Assert
        await _fileShareRepository.Received(1).AddAsync(
            Arg.Is<DomainFileShare>(s => 
                s.FileId == file.Id && 
                s.SharedBy == TestUserId),
            Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task CreateShare_WithPassword_CreatesProtectedShare()
    {
        // Arrange
        var file = CreateRealStorageFile();
        var command = new CreateFileShareCommand(
            file.Id,
            TestUserId,
            SharePermission.Download,
            Password: "SecretP@ss123");

        _fileRepository.GetByIdAsync(file.Id, Arg.Any<CancellationToken>())
            .Returns(file);
        _unitOfWork.SaveEntitiesAsync(Arg.Any<CancellationToken>()).Returns(true);

        DomainFileShare? capturedShare = null;
        await _fileShareRepository.AddAsync(
            Arg.Do<DomainFileShare>(s => capturedShare = s),
            Arg.Any<CancellationToken>());

        // Act
        await _createHandler.Handle(command, CancellationToken.None);

        // Assert
        capturedShare.Should().NotBeNull();
        capturedShare!.PasswordHash.Should().NotBeNullOrEmpty();
    }

    [Fact]
    public async Task CreateShare_WithExpiration_SetsExpiresAt()
    {
        // Arrange
        var file = CreateRealStorageFile();
        var expiresAt = DateTime.UtcNow.AddDays(7);
        var command = new CreateFileShareCommand(
            file.Id,
            TestUserId,
            SharePermission.Download,
            ExpiresAt: expiresAt);

        _fileRepository.GetByIdAsync(file.Id, Arg.Any<CancellationToken>())
            .Returns(file);
        _unitOfWork.SaveEntitiesAsync(Arg.Any<CancellationToken>()).Returns(true);

        DomainFileShare? capturedShare = null;
        await _fileShareRepository.AddAsync(
            Arg.Do<DomainFileShare>(s => capturedShare = s),
            Arg.Any<CancellationToken>());

        // Act
        await _createHandler.Handle(command, CancellationToken.None);

        // Assert
        capturedShare.Should().NotBeNull();
        capturedShare!.ExpiresAt.Should().Be(expiresAt);
    }

    [Fact]
    public async Task CreateShare_WithMaxDownloads_SetsLimit()
    {
        // Arrange
        var file = CreateRealStorageFile();
        var command = new CreateFileShareCommand(
            file.Id,
            TestUserId,
            SharePermission.Download,
            MaxDownloads: 10);

        _fileRepository.GetByIdAsync(file.Id, Arg.Any<CancellationToken>())
            .Returns(file);
        _unitOfWork.SaveEntitiesAsync(Arg.Any<CancellationToken>()).Returns(true);

        DomainFileShare? capturedShare = null;
        await _fileShareRepository.AddAsync(
            Arg.Do<DomainFileShare>(s => capturedShare = s),
            Arg.Any<CancellationToken>());

        // Act
        await _createHandler.Handle(command, CancellationToken.None);

        // Assert
        capturedShare.Should().NotBeNull();
        capturedShare!.MaxDownloads.Should().Be(10);
    }

    #endregion

    #region File Not Found Tests

    [Fact]
    public async Task CreateShare_FileNotFound_ReturnsError()
    {
        // Arrange
        var command = new CreateFileShareCommand(
            TestFileId,
            TestUserId,
            SharePermission.Download);

        _fileRepository.GetByIdAsync(TestFileId, Arg.Any<CancellationToken>())
            .Returns((StorageFile?)null);

        // Act
        var result = await _createHandler.Handle(command, CancellationToken.None);

        // Assert
        result.Success.Should().BeFalse();
        result.Error.Should().Contain("not found");
    }

    [Fact]
    public async Task CreateShare_FileNotFound_DoesNotCreateShare()
    {
        // Arrange
        var command = new CreateFileShareCommand(
            TestFileId,
            TestUserId,
            SharePermission.Download);

        _fileRepository.GetByIdAsync(TestFileId, Arg.Any<CancellationToken>())
            .Returns((StorageFile?)null);

        // Act
        await _createHandler.Handle(command, CancellationToken.None);

        // Assert
        await _fileShareRepository.DidNotReceive().AddAsync(
            Arg.Any<DomainFileShare>(), Arg.Any<CancellationToken>());
    }

    #endregion

    #region Permission Tests

    [Fact]
    public async Task CreateShare_NotOwner_ReturnsError()
    {
        // Arrange
        var file = new StorageFile(
            "document.pdf",
            "bucket",
            "key",
            "application/pdf",
            1024,
            "different-user", // Different owner
            StorageProvider.MinIO);

        var command = new CreateFileShareCommand(
            file.Id,
            TestUserId,
            SharePermission.Download);

        _fileRepository.GetByIdAsync(file.Id, Arg.Any<CancellationToken>())
            .Returns(file);

        // Act
        var result = await _createHandler.Handle(command, CancellationToken.None);

        // Assert
        result.Success.Should().BeFalse();
        result.Error.Should().Contain("permission");
    }

    [Fact]
    public async Task CreateShare_NotOwner_DoesNotCreateShare()
    {
        // Arrange
        var file = new StorageFile(
            "document.pdf",
            "bucket",
            "key",
            "application/pdf",
            1024,
            "different-user",
            StorageProvider.MinIO);

        var command = new CreateFileShareCommand(
            file.Id,
            TestUserId,
            SharePermission.Download);

        _fileRepository.GetByIdAsync(file.Id, Arg.Any<CancellationToken>())
            .Returns(file);

        // Act
        await _createHandler.Handle(command, CancellationToken.None);

        // Assert
        await _fileShareRepository.DidNotReceive().AddAsync(
            Arg.Any<DomainFileShare>(), Arg.Any<CancellationToken>());
    }

    #endregion

    #region ShareUrl Generation Tests

    [Fact]
    public async Task CreateShare_GeneratesCorrectShareUrl()
    {
        // Arrange
        var file = CreateRealStorageFile();
        var command = new CreateFileShareCommand(
            file.Id,
            TestUserId,
            SharePermission.Download);

        _fileRepository.GetByIdAsync(file.Id, Arg.Any<CancellationToken>())
            .Returns(file);
        _unitOfWork.SaveEntitiesAsync(Arg.Any<CancellationToken>()).Returns(true);

        // Act
        var result = await _createHandler.Handle(command, CancellationToken.None);

        // Assert
        result.ShareUrl.Should().StartWith(TestBaseUrl);
        result.ShareUrl.Should().Contain("/api/v1/storage/shares/public/");
        result.ShareUrl.Should().Contain(result.ShareToken);
    }

    #endregion

    #region Exception Handling Tests

    [Fact]
    public async Task CreateShare_RepositoryThrows_ReturnsFailure()
    {
        // Arrange
        var command = new CreateFileShareCommand(
            TestFileId,
            TestUserId,
            SharePermission.Download);

        _fileRepository.GetByIdAsync(TestFileId, Arg.Any<CancellationToken>())
            .ThrowsAsync(new Exception("Database connection failed"));

        // Act
        var result = await _createHandler.Handle(command, CancellationToken.None);

        // Assert
        result.Success.Should().BeFalse();
        result.Error.Should().NotBeNullOrEmpty();
    }

    [Fact]
    public async Task CreateShare_SaveChangesThrows_ReturnsFailure()
    {
        // Arrange
        var file = CreateRealStorageFile();
        var command = new CreateFileShareCommand(
            file.Id,
            TestUserId,
            SharePermission.Download);

        _fileRepository.GetByIdAsync(file.Id, Arg.Any<CancellationToken>())
            .Returns(file);
        _unitOfWork.SaveEntitiesAsync(Arg.Any<CancellationToken>())
            .ThrowsAsync(new Exception("Save failed"));

        // Act
        var result = await _createHandler.Handle(command, CancellationToken.None);

        // Assert
        result.Success.Should().BeFalse();
    }

    #endregion

    #region Helper Methods

    private static StorageFile CreateRealStorageFile()
    {
        return new StorageFile(
            "document.pdf",
            "storage-bucket",
            "private/user-123/20260115/abc123_document.pdf",
            "application/pdf",
            1024,
            TestUserId,
            StorageProvider.MinIO);
    }

    #endregion
}
