using SkiaSharp;

namespace ErsatzTV.Core.Images;

/// <summary>
///     The single source of truth for which image content types the artwork surfaces accept, plus a
///     byte-sniffing detector used to derive the true content type from an upload's actual contents
///     rather than trusting the client-declared value (issue #283 — stored XSS via a spoofed
///     content type). png/jpeg/gif/webp are the formats SkiaSharp decodes and FFmpeg reads, matching
///     what the artwork upload has always accepted; format expansion is tracked by ersatztv#66.
/// </summary>
public static class ImageContentTypes
{
    private static readonly IReadOnlyDictionary<SKEncodedImageFormat, string> FormatContentTypes =
        new Dictionary<SKEncodedImageFormat, string>
        {
            [SKEncodedImageFormat.Png] = "image/png",
            [SKEncodedImageFormat.Jpeg] = "image/jpeg",
            [SKEncodedImageFormat.Gif] = "image/gif",
            [SKEncodedImageFormat.Webp] = "image/webp"
        };

    public static readonly IReadOnlySet<string> Accepted =
        new System.Collections.Generic.HashSet<string>(FormatContentTypes.Values, StringComparer.OrdinalIgnoreCase);

    /// <summary>
    ///     Sniffs the true image content type from <paramref name="bytes" /> using SkiaSharp's codec
    ///     detection. Only the image header is parsed to identify the format — pixels are never
    ///     decoded here, so a crafted decompression-bomb image cannot be triggered by this call.
    ///     Returns None for anything that is not one of the accepted raster formats (including
    ///     non-image payloads such as HTML/script uploaded under a spoofed content type).
    /// </summary>
    public static Option<string> DetectContentType(byte[] bytes)
    {
        if (bytes is null || bytes.Length == 0)
        {
            return Option<string>.None;
        }

        try
        {
            using SKData data = SKData.CreateCopy(bytes);
            using SKCodec codec = SKCodec.Create(data);
            if (codec is null)
            {
                return Option<string>.None;
            }

            return FormatContentTypes.TryGetValue(codec.EncodedFormat, out string contentType)
                ? contentType
                : Option<string>.None;
        }
        catch (Exception)
        {
            return Option<string>.None;
        }
    }

    /// <summary>
    ///     True when <paramref name="contentType" /> is one of the accepted image content types.
    /// </summary>
    public static bool IsAccepted(string contentType) =>
        !string.IsNullOrWhiteSpace(contentType) && Accepted.Contains(contentType.Trim());
}
