namespace SharedKernel.Storage;

public static class ImageDimensionHelper
{
    /// <summary>
    /// 이미지 스트림에서 해상도(Width, Height)를 추출합니다.
    /// 지원 포맷: PNG, JPEG, GIF, BMP, WebP
    /// </summary>
    public static (short Width, short Height)? GetDimensions(Stream stream)
    {
        if (stream is null || !stream.CanRead)
        {
            return null;
        }

        var buffer = new byte[32];
        var bytesRead = stream.Read(buffer, 0, buffer.Length);
        if (bytesRead < 10)
        {
            return null;
        }

        // PNG: 89 50 4E 47 0D 0A 1A 0A
        if (bytesRead >= 24 && buffer[0] == 0x89 && buffer[1] == 0x50 && buffer[2] == 0x4E && buffer[3] == 0x47)
        {
            var w = ReadInt32BigEndian(buffer, 16);
            var h = ReadInt32BigEndian(buffer, 20);
            return ToShortPair(w, h);
        }

        // GIF: 47 49 46 38 (GIF8)
        if (bytesRead >= 10 && buffer[0] == 0x47 && buffer[1] == 0x49 && buffer[2] == 0x46 && buffer[3] == 0x38)
        {
            var w = ReadUInt16LittleEndian(buffer, 6);
            var h = ReadUInt16LittleEndian(buffer, 8);
            return ToShortPair(w, h);
        }

        // BMP: 42 4D (BM)
        if (bytesRead >= 26 && buffer[0] == 0x42 && buffer[1] == 0x4D)
        {
            var w = ReadInt32LittleEndian(buffer, 18);
            var h = Math.Abs(ReadInt32LittleEndian(buffer, 22));
            return ToShortPair(w, h);
        }

        // WebP: RIFF....WEBP
        if (bytesRead >= 30 && buffer[0] == 0x52 && buffer[1] == 0x49 && buffer[2] == 0x46 && buffer[3] == 0x46
            && buffer[8] == 0x57 && buffer[9] == 0x45 && buffer[10] == 0x42 && buffer[11] == 0x50)
        {
            return GetWebPDimensions(stream, buffer, bytesRead);
        }

        // JPEG: FF D8
        if (bytesRead >= 2 && buffer[0] == 0xFF && buffer[1] == 0xD8)
        {
            return GetJpegDimensions(stream);
        }

        return null;
    }

    /// <summary>
    /// 바이트 배열에서 이미지 해상도(Width, Height)를 추출합니다.
    /// </summary>
    public static (short Width, short Height)? GetDimensions(ReadOnlyMemory<byte> bytes)
    {
        if (bytes.Length < 10)
        {
            return null;
        }

        using var stream = new MemoryStream(bytes.ToArray(), writable: false);
        return GetDimensions(stream);
    }

    private static (short Width, short Height)? GetJpegDimensions(Stream stream)
    {
        // 스트림 위치를 2로 이동 (SOI 마커 이후)
        stream.Position = 2;

        var marker = new byte[2];
        var sizeBuffer = new byte[2];

        while (stream.Position < stream.Length - 1)
        {
            // 마커 읽기 (0xFF xx)
            if (stream.Read(marker, 0, 2) != 2)
            {
                break;
            }

            if (marker[0] != 0xFF)
            {
                break;
            }

            // 패딩 0xFF 건너뛰기
            while (marker[1] == 0xFF && stream.Position < stream.Length)
            {
                marker[1] = (byte)stream.ReadByte();
            }

            var markerType = marker[1];

            // SOF 마커 (0xC0 ~ 0xCF, 0xC4/0xC8/0xCC 제외)
            if (markerType is >= 0xC0 and <= 0xCF && markerType is not (0xC4 or 0xC8 or 0xCC))
            {
                var header = new byte[7];
                if (stream.Read(header, 0, 7) != 7)
                {
                    break;
                }

                // header[0..1] = segment length
                // header[2] = precision
                // header[3..4] = height (big-endian)
                // header[5..6] = width (big-endian)
                var h = (header[3] << 8) | header[4];
                var w = (header[5] << 8) | header[6];
                return ToShortPair(w, h);
            }

            // SOS(0xDA) 또는 EOI(0xD9) → 이미지 데이터 시작/끝
            if (markerType is 0xDA or 0xD9)
            {
                break;
            }

            // 나머지 세그먼트는 길이만큼 건너뛰기
            if (stream.Read(sizeBuffer, 0, 2) != 2)
            {
                break;
            }

            var segmentLength = (sizeBuffer[0] << 8) | sizeBuffer[1];
            if (segmentLength < 2)
            {
                break;
            }

            stream.Position += segmentLength - 2;
        }

        return null;
    }

    private static (short Width, short Height)? GetWebPDimensions(Stream stream, byte[] initialBuffer, int initialBytesRead)
    {
        // VP8 청크 시작: offset 12
        if (initialBytesRead < 16)
        {
            return null;
        }

        var chunkType = new string(new[] { (char)initialBuffer[12], (char)initialBuffer[13], (char)initialBuffer[14], (char)initialBuffer[15] });

        // VP8 (lossy)
        if (chunkType == "VP8 ")
        {
            // VP8 bitstream: offset 12 + 4(chunk header) + 4(chunk size) + 10(frame header)
            stream.Position = 26;
            var buf = new byte[4];
            if (stream.Read(buf, 0, 4) != 4)
            {
                return null;
            }

            var w = (buf[0] | (buf[1] << 8)) & 0x3FFF;
            var h = (buf[2] | (buf[3] << 8)) & 0x3FFF;
            return ToShortPair(w, h);
        }

        // VP8L (lossless)
        if (chunkType == "VP8L")
        {
            // VP8L: offset 12 + 4 + 4 + 1(signature) = 21
            stream.Position = 21;
            var buf = new byte[4];
            if (stream.Read(buf, 0, 4) != 4)
            {
                return null;
            }

            var bits = (uint)(buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24));
            var w = (int)((bits & 0x3FFF) + 1);
            var h = (int)(((bits >> 14) & 0x3FFF) + 1);
            return ToShortPair(w, h);
        }

        // VP8X (extended)
        if (chunkType == "VP8X")
        {
            // VP8X: offset 12 + 4 + 4 + 4(flags) = 24, width 3bytes + height 3bytes
            stream.Position = 24;
            var buf = new byte[6];
            if (stream.Read(buf, 0, 6) != 6)
            {
                return null;
            }

            var w = (buf[0] | (buf[1] << 8) | (buf[2] << 16)) + 1;
            var h = (buf[3] | (buf[4] << 8) | (buf[5] << 16)) + 1;
            return ToShortPair(w, h);
        }

        return null;
    }

    private static (short Width, short Height)? ToShortPair(int width, int height)
    {
        if (width <= 0 || height <= 0 || width > short.MaxValue || height > short.MaxValue)
        {
            return null;
        }

        return ((short)width, (short)height);
    }

    private static int ReadInt32BigEndian(byte[] buf, int offset)
    {
        return (buf[offset] << 24) | (buf[offset + 1] << 16) | (buf[offset + 2] << 8) | buf[offset + 3];
    }

    private static int ReadUInt16LittleEndian(byte[] buf, int offset)
    {
        return buf[offset] | (buf[offset + 1] << 8);
    }

    private static int ReadInt32LittleEndian(byte[] buf, int offset)
    {
        return buf[offset] | (buf[offset + 1] << 8) | (buf[offset + 2] << 16) | (buf[offset + 3] << 24);
    }
}