using Knot.Contracts.Profiles.Domain;
using Knot.Contracts.Profiles.Application.DTOs;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;

namespace Knot.Modules.Profiles.Application.Profiles.Avatar;

public sealed record CropAvatarCommand(
    Guid UserId,
    Stream FileStream,
    string FileName,
    string ContentType,
    int X, int Y, int Width, int Height,
    int SourceWidth, int SourceHeight) : ICommand<UserProfileDto>;

internal sealed class CropAvatarCommandHandler : ICommandHandler<CropAvatarCommand, UserProfileDto>
{
    private readonly IProfileRepository _repository;
    private readonly IAvatarStorageService _avatarStorage;

    public CropAvatarCommandHandler(IProfileRepository repository, IAvatarStorageService avatarStorage)
    {
        _repository = repository;
        _avatarStorage = avatarStorage;
    }

    public async Task<Result<UserProfileDto>> Handle(CropAvatarCommand request, CancellationToken cancellationToken)
    {
        var profile = await _repository.GetAsync(request.UserId, cancellationToken);
        if (profile is null)
            return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);

        using var ms = await CropAndResizeAsync(request, cancellationToken);

        if (!string.IsNullOrEmpty(profile.Avatar))
            await _avatarStorage.DeleteAsync(profile.Avatar, cancellationToken);

        var fileId = await _avatarStorage.UploadAsync(ms, "avatar.jpg", "image/jpeg", cancellationToken);
        var avatarUrl = $"/api/files/{fileId}";

        profile.Avatar = avatarUrl;
        var result = await _repository.UpdateAsync(profile, cancellationToken);
        if (result.IsFailure)
            return Result.Failure<UserProfileDto>(result.Error);

        return Result.Success(result.Value);
    }

    private static async Task<MemoryStream> CropAndResizeAsync(CropAvatarCommand request, CancellationToken ct)
    {
        using var image = await Image.LoadAsync(request.FileStream, ct);
        image.Mutate(x => x.AutoOrient());

        // Рассчитываем коэффициент масштабирования между тем, что видел фронтенд и тем, что загрузил бэкенд
        double scaleX = (double)image.Width / request.SourceWidth;
        double scaleY = (double)image.Height / request.SourceHeight;

        var startX = Math.Clamp((int)(request.X * scaleX), 0, image.Width - 1);
        var startY = Math.Clamp((int)(request.Y * scaleY), 0, image.Height - 1);
        var width  = Math.Clamp((int)(request.Width * scaleX), 1, image.Width - startX);
        var height = Math.Clamp((int)(request.Height * scaleY), 1, image.Height - startY);

        image.Mutate(ctx => ctx
            .Crop(new Rectangle(startX, startY, width, height))
            .Resize(400, 400));

        var output = new MemoryStream();
        await image.SaveAsJpegAsync(output, ct);
        output.Position = 0;
        return output;
    }
}
