using LiteCqrs;
using TeleWave.Api.Common;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Images;
using TeleWave.Application.Images.DeleteImage;
using TeleWave.Application.Images.GetImageFile;
using TeleWave.Application.Images.ListImages;
using TeleWave.Application.Images.UploadImage;
using TeleWave.Domain.Images;
using TeleWave.Infrastructure.Identity;

namespace TeleWave.Api.Endpoints;

public static class ImageEndpoints
{
    private const long MaxBytes = 50L * 1024 * 1024; // 50 МБ

    // Тип поля — HashSet, а не IReadOnlySet: поле приватное, и через интерфейс Contains уходит
    // в виртуальный вызов вместо прямого.
    private static readonly HashSet<string> AllowedExtensions = new(
        StringComparer.OrdinalIgnoreCase
    )
    {
        ".jpg",
        ".jpeg",
        ".png",
        ".webp",
        ".bmp",
        ".gif",
    };

    public static IEndpointRouteBuilder MapImageEndpoints(this IEndpointRouteBuilder app)
    {
        var admin = app.MapGroup("/api/admin/images")
            .WithTags("Admin.Images")
            .RequireAuthorization(policy => policy.RequireRole(RoleNames.Admin));

        admin.MapGet("", ListImages).Produces<IReadOnlyList<ImageDto>>();
        admin.MapPost("", UploadImage).Produces<CreatedIdResponse>(StatusCodes.Status201Created);
        admin.MapDelete("/{id:guid}", DeleteImage).Produces(StatusCodes.Status204NoContent);

        // Публичная отдача файла (для <img> у зрителя и в админке).
        app.MapGet("/api/images/{id:guid}", ServeImage).WithTags("Images");

        return app;
    }

    private static async Task<IResult> ListImages(
        ImageCategory category,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(new ListImagesQuery(category), cancellationToken);
        return result.ToHttpResult();
    }

    private static async Task<IResult> UploadImage(
        ImageCategory category,
        string fileName,
        HttpRequest request,
        IImageStore storage,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        if (request.ContentLength is > MaxBytes or 0 or null)
            return ImageErrors.InvalidFile.ToProblem();
        var ext = Path.GetExtension(fileName).ToLowerInvariant();
        if (!AllowedExtensions.Contains(ext))
            return ImageErrors.InvalidFile.ToProblem();

        var created = await sender.Send(
            new UploadImageCommand(category, ext, fileName),
            cancellationToken
        );
        if (!created.IsSuccess)
            return created.ToHttpResult();

        try
        {
            await storage.SaveAsync(created.Value, ext, request.Body, cancellationToken);
        }
        catch
        {
            // Файл не сохранился — не оставляем висячую запись реестра.
            await sender.Send(new DeleteImageCommand(created.Value), cancellationToken);
            throw;
        }

        return Results.Created(
            $"/api/images/{created.Value}",
            new CreatedIdResponse(created.Value)
        );
    }

    private static async Task<IResult> DeleteImage(
        Guid id,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(new DeleteImageCommand(id), cancellationToken);
        return result.ToHttpResult();
    }

    private static async Task<IResult> ServeImage(
        Guid id,
        HttpResponse response,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(new GetImageFileQuery(id), cancellationToken);
        if (!result.IsSuccess)
            return Results.NotFound();

        response.Headers.CacheControl = "public, max-age=86400";
        return Results.File(result.Value, ContentTypeFor(Path.GetExtension(result.Value)));
    }

    private static string ContentTypeFor(string extension) =>
        extension.ToLowerInvariant() switch
        {
            ".png" => "image/png",
            ".webp" => "image/webp",
            ".gif" => "image/gif",
            ".bmp" => "image/bmp",
            _ => "image/jpeg",
        };
}
