namespace Meezi.API.Services;

public interface IMediaStorageService
{
    Task<string?> SaveMenuImageAsync(string cafeId, IFormFile file, CancellationToken cancellationToken = default);
    Task<string?> SaveMenuVideoAsync(string cafeId, IFormFile file, CancellationToken cancellationToken = default);
    Task<string?> SaveTableImageAsync(string cafeId, IFormFile file, CancellationToken cancellationToken = default);
    Task<string?> SaveTableVideoAsync(string cafeId, IFormFile file, CancellationToken cancellationToken = default);
    Task<string?> SaveCafeLogoAsync(string cafeId, IFormFile file, CancellationToken cancellationToken = default);
    Task<string?> SaveCafeCoverAsync(string cafeId, IFormFile file, CancellationToken cancellationToken = default);
    Task<string?> SaveMenuModel3dAsync(string cafeId, IFormFile file, CancellationToken cancellationToken = default);
    Task<string?> SaveMenuModel3dFromBytesAsync(string cafeId, byte[] glbBytes, CancellationToken cancellationToken = default);
    Task<string?> SaveReviewPhotoAsync(string cafeId, IFormFile file, CancellationToken cancellationToken = default);
    Task<string?> SaveCafeGalleryPhotoAsync(string cafeId, IFormFile file, CancellationToken cancellationToken = default);
}

public class MediaStorageService : IMediaStorageService
{
    private static readonly HashSet<string> ImageMime = new(StringComparer.OrdinalIgnoreCase)
    {
        "image/jpeg", "image/png", "image/webp"
    };

    private static readonly HashSet<string> VideoMime = new(StringComparer.OrdinalIgnoreCase)
    {
        "video/mp4", "video/webm", "video/quicktime"
    };

    private const long MaxImageBytes = 5 * 1024 * 1024;
    private const long MaxVideoBytes = 25 * 1024 * 1024;
    private const long MaxModel3dBytes = 8 * 1024 * 1024;

    private static readonly HashSet<string> Model3dMime = new(StringComparer.OrdinalIgnoreCase)
    {
        "model/gltf-binary", "application/octet-stream"
    };

    private readonly IWebHostEnvironment _env;
    private readonly ILogger<MediaStorageService> _logger;

    public MediaStorageService(IWebHostEnvironment env, ILogger<MediaStorageService> logger)
    {
        _env = env;
        _logger = logger;
    }

    public Task<string?> SaveMenuImageAsync(string cafeId, IFormFile file, CancellationToken cancellationToken = default)
        => SaveAsync(cafeId, file, "menu_img", ImageMime, MaxImageBytes, cancellationToken);

    public Task<string?> SaveMenuVideoAsync(string cafeId, IFormFile file, CancellationToken cancellationToken = default)
        => SaveAsync(cafeId, file, "menu_vid", VideoMime, MaxVideoBytes, cancellationToken);

    public Task<string?> SaveTableImageAsync(string cafeId, IFormFile file, CancellationToken cancellationToken = default)
        => SaveAsync(cafeId, file, "table_img", ImageMime, MaxImageBytes, cancellationToken);

    public Task<string?> SaveTableVideoAsync(string cafeId, IFormFile file, CancellationToken cancellationToken = default)
        => SaveAsync(cafeId, file, "table_vid", VideoMime, MaxVideoBytes, cancellationToken);

    public Task<string?> SaveCafeLogoAsync(string cafeId, IFormFile file, CancellationToken cancellationToken = default)
        => SaveAsync(cafeId, file, "logo", ImageMime, MaxImageBytes, cancellationToken);

    public Task<string?> SaveCafeCoverAsync(string cafeId, IFormFile file, CancellationToken cancellationToken = default)
        => SaveAsync(cafeId, file, "cover", ImageMime, MaxImageBytes, cancellationToken);

    public Task<string?> SaveReviewPhotoAsync(string cafeId, IFormFile file, CancellationToken cancellationToken = default)
        => SaveAsync(cafeId, file, "review", ImageMime, MaxImageBytes, cancellationToken);

    public Task<string?> SaveCafeGalleryPhotoAsync(string cafeId, IFormFile file, CancellationToken cancellationToken = default)
        => SaveAsync(cafeId, file, "gallery", ImageMime, MaxImageBytes, cancellationToken);

    public Task<string?> SaveMenuModel3dAsync(string cafeId, IFormFile file, CancellationToken cancellationToken = default)
        => SaveModel3dAsync(cafeId, file, cancellationToken);

    public async Task<string?> SaveMenuModel3dFromBytesAsync(
        string cafeId,
        byte[] glbBytes,
        CancellationToken cancellationToken = default)
    {
        if (glbBytes.Length == 0 || glbBytes.Length > MaxModel3dBytes) return null;

        var dir = Path.Combine(_env.ContentRootPath, "uploads", cafeId);
        Directory.CreateDirectory(dir);
        var savedName = $"menu_3d_ai_{Guid.NewGuid():N}.glb";
        var path = Path.Combine(dir, savedName);

        await File.WriteAllBytesAsync(path, glbBytes, cancellationToken);
        _logger.LogInformation("Saved AI 3D model for cafe {CafeId}", cafeId);
        return $"/uploads/{cafeId}/{savedName}";
    }

    private async Task<string?> SaveModel3dAsync(
        string cafeId,
        IFormFile file,
        CancellationToken cancellationToken)
    {
        if (file.Length == 0 || file.Length > MaxModel3dBytes) return null;

        var fileName = file.FileName.ToLowerInvariant();
        var isGlb = fileName.EndsWith(".glb", StringComparison.OrdinalIgnoreCase)
            || Model3dMime.Contains(file.ContentType);
        if (!isGlb) return null;

        var dir = Path.Combine(_env.ContentRootPath, "uploads", cafeId);
        Directory.CreateDirectory(dir);
        var savedName = $"menu_3d_{Guid.NewGuid():N}.glb";
        var path = Path.Combine(dir, savedName);

        await using var stream = File.Create(path);
        await file.CopyToAsync(stream, cancellationToken);

        _logger.LogInformation("Saved 3D model media for cafe {CafeId}", cafeId);
        return $"/uploads/{cafeId}/{savedName}";
    }

    private async Task<string?> SaveAsync(
        string cafeId,
        IFormFile file,
        string prefix,
        HashSet<string> allowedMime,
        long maxBytes,
        CancellationToken cancellationToken)
    {
        if (file.Length == 0 || file.Length > maxBytes) return null;
        if (!allowedMime.Contains(file.ContentType)) return null;

        var ext = file.ContentType.ToLowerInvariant() switch
        {
            "image/png" => ".png",
            "image/webp" => ".webp",
            "video/webm" => ".webm",
            "video/quicktime" => ".mov",
            "video/mp4" => ".mp4",
            _ => ".jpg"
        };

        var dir = Path.Combine(_env.ContentRootPath, "uploads", cafeId);
        Directory.CreateDirectory(dir);
        var fileName = $"{prefix}_{Guid.NewGuid():N}{ext}";
        var path = Path.Combine(dir, fileName);

        await using var stream = File.Create(path);
        await file.CopyToAsync(stream, cancellationToken);

        _logger.LogInformation("Saved {Prefix} media for cafe {CafeId}", prefix, cafeId);
        return $"/uploads/{cafeId}/{fileName}";
    }
}
