using System.Text;
using QRCoder;
using SkiaSharp;
using TrackQrApi.Features.QRCodes.Common;

namespace TrackQrApi.Features.QRCodes.Services;

public interface IQrCodeGeneratorService
{
    byte[] GeneratePng(string content, QRCodeStyle style, int size = 512, Stream? logoStream = null);
    string GenerateSvg(string content, QRCodeStyle style, int size = 512);
    string GenerateDataUrl(string content, QRCodeStyle style, int size = 256, Stream? logoStream = null);
}

public class QrCodeGeneratorService : IQrCodeGeneratorService
{
    public byte[] GeneratePng(string content, QRCodeStyle style, int size = 512, Stream? logoStream = null)
    {
        using var qrGenerator = new QRCodeGenerator();
        var eccLevel = ParseEccLevel(style.ErrorCorrectionLevel);
        using var qrCodeData = qrGenerator.CreateQrCode(content, eccLevel);

        var moduleMatrix = qrCodeData.ModuleMatrix;
        var moduleCount = moduleMatrix.Count;

        // Calculate pixels per module based on desired size (accounting for quiet zone)
        var totalModules = moduleCount + style.QuietZone * 2;
        var pixelsPerModule = Math.Max(4, size / totalModules);
        var actualSize = totalModules * pixelsPerModule;

        // Create bitmap with SkiaSharp for custom shapes
        var foregroundColor = ParseSkColor(style.ForegroundColor);
        var backgroundColor = ParseSkColor(style.BackgroundColor);

        using var surface = SKSurface.Create(new SKImageInfo(actualSize, actualSize));
        var canvas = surface.Canvas;

        // Draw background
        canvas.Clear(backgroundColor);

        // Draw QR modules with custom shapes
        var modulePaint = new SKPaint
        {
            Color = foregroundColor,
            IsAntialias = true,
            Style = SKPaintStyle.Fill
        };

        var quietZoneOffset = style.QuietZone * pixelsPerModule;

        for (var y = 0; y < moduleCount; y++)
        for (var x = 0; x < moduleCount; x++)
            if (moduleMatrix[y][x])
            {
                var px = quietZoneOffset + x * pixelsPerModule;
                var py = quietZoneOffset + y * pixelsPerModule;

                // Check if this is part of a finder pattern (eyes)
                var isEye = IsFinderPattern(x, y, moduleCount);

                if (isEye)
                    DrawModule(canvas, px, py, pixelsPerModule, modulePaint, style.EyeShape);
                else
                    DrawModule(canvas, px, py, pixelsPerModule, modulePaint, style.ModuleShape);
            }

        // Encode to PNG
        using var image = surface.Snapshot();
        using var data = image.Encode(SKEncodedImageFormat.Png, 100);
        var qrBytes = data.ToArray();

        // If no logo, return the QR code as-is
        if (logoStream == null) return qrBytes;

        // Overlay logo on QR code
        return OverlayLogo(qrBytes, logoStream, actualSize);
    }

    public string GenerateSvg(string content, QRCodeStyle style, int size = 512)
    {
        using var qrGenerator = new QRCodeGenerator();
        var eccLevel = ParseEccLevel(style.ErrorCorrectionLevel);
        using var qrCodeData = qrGenerator.CreateQrCode(content, eccLevel);

        var moduleMatrix = qrCodeData.ModuleMatrix;
        var moduleCount = moduleMatrix.Count;

        // Calculate pixels per module based on desired size (accounting for quiet zone)
        var totalModules = moduleCount + style.QuietZone * 2;
        var pixelsPerModule = (float)size / totalModules;
        var actualSize = size;

        var foreground = style.ForegroundColor;
        var background = style.BackgroundColor;

        var svg = new StringBuilder();
        svg.AppendLine(
            $"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 {actualSize} {actualSize}\" width=\"{actualSize}\" height=\"{actualSize}\">");
        svg.AppendLine($"  <rect width=\"100%\" height=\"100%\" fill=\"{background}\"/>");

        var quietZoneOffset = style.QuietZone * pixelsPerModule;

        for (var y = 0; y < moduleCount; y++)
        for (var x = 0; x < moduleCount; x++)
            if (moduleMatrix[y][x])
            {
                var px = quietZoneOffset + x * pixelsPerModule;
                var py = quietZoneOffset + y * pixelsPerModule;
                var isEye = IsFinderPattern(x, y, moduleCount);
                var shape = isEye ? style.EyeShape : style.ModuleShape;

                var padding = pixelsPerModule * 0.1f;
                var moduleSize = pixelsPerModule - padding;

                switch (shape.ToLowerInvariant())
                {
                    case "circle":
                    case "dots":
                        var radius = moduleSize / 2;
                        var cx = px + pixelsPerModule / 2;
                        var cy = py + pixelsPerModule / 2;
                        svg.AppendLine(
                            $"  <circle cx=\"{cx:F2}\" cy=\"{cy:F2}\" r=\"{radius:F2}\" fill=\"{foreground}\"/>");
                        break;

                    case "rounded":
                        var cornerRadius = moduleSize * 0.3f;
                        svg.AppendLine(
                            $"  <rect x=\"{px + padding / 2:F2}\" y=\"{py + padding / 2:F2}\" width=\"{moduleSize:F2}\" height=\"{moduleSize:F2}\" rx=\"{cornerRadius:F2}\" fill=\"{foreground}\"/>");
                        break;

                    case "square":
                    default:
                        svg.AppendLine(
                            $"  <rect x=\"{px + padding / 2:F2}\" y=\"{py + padding / 2:F2}\" width=\"{moduleSize:F2}\" height=\"{moduleSize:F2}\" fill=\"{foreground}\"/>");
                        break;
                }
            }

        svg.AppendLine("</svg>");
        return svg.ToString();
    }

    public string GenerateDataUrl(string content, QRCodeStyle style, int size = 256, Stream? logoStream = null)
    {
        var pngBytes = GeneratePng(content, style, size, logoStream);
        var base64 = Convert.ToBase64String(pngBytes);
        return $"data:image/png;base64,{base64}";
    }

    private static bool IsFinderPattern(int x, int y, int moduleCount)
    {
        // Top-left finder pattern: 0-6, 0-6
        if (x <= 6 && y <= 6) return true;
        // Top-right finder pattern: moduleCount-7 to moduleCount-1, 0-6
        if (x >= moduleCount - 7 && y <= 6) return true;
        // Bottom-left finder pattern: 0-6, moduleCount-7 to moduleCount-1
        if (x <= 6 && y >= moduleCount - 7) return true;
        return false;
    }

    private static void DrawModule(SKCanvas canvas, float x, float y, float size, SKPaint paint, string shape)
    {
        var padding = size * 0.1f; // 10% padding between modules
        var moduleSize = size - padding;

        switch (shape.ToLowerInvariant())
        {
            case "circle":
            case "dots":
                var radius = moduleSize / 2;
                canvas.DrawCircle(x + size / 2, y + size / 2, radius, paint);
                break;

            case "rounded":
                var cornerRadius = moduleSize * 0.3f;
                var rect = new SKRoundRect(
                    new SKRect(x + padding / 2, y + padding / 2, x + size - padding / 2, y + size - padding / 2),
                    cornerRadius
                );
                canvas.DrawRoundRect(rect, paint);
                break;

            case "square":
            default:
                canvas.DrawRect(x + padding / 2, y + padding / 2, moduleSize, moduleSize, paint);
                break;
        }
    }

    private static byte[] OverlayLogo(byte[] qrBytes, Stream logoStream, int qrSize)
    {
        using var qrBitmap = SKBitmap.Decode(qrBytes);
        using var logoBitmap = SKBitmap.Decode(logoStream);

        if (qrBitmap == null || logoBitmap == null) return qrBytes;

        // Logo should be about 20% of QR code size
        var logoSize = (int)(qrSize * 0.2);
        var logoX = (qrBitmap.Width - logoSize) / 2;
        var logoY = (qrBitmap.Height - logoSize) / 2;

        // Create a new surface to draw on
        using var surface = SKSurface.Create(new SKImageInfo(qrBitmap.Width, qrBitmap.Height));
        var canvas = surface.Canvas;

        // Draw QR code
        canvas.DrawBitmap(qrBitmap, 0, 0);

        // Draw white background circle for logo
        var circlePaint = new SKPaint
        {
            Color = SKColors.White,
            IsAntialias = true,
            Style = SKPaintStyle.Fill
        };
        var circleRadius = logoSize * 0.6f;
        canvas.DrawCircle(qrBitmap.Width / 2f, qrBitmap.Height / 2f, circleRadius, circlePaint);

        // Resize and draw logo
        using var resizedLogo = logoBitmap.Resize(
            new SKImageInfo(logoSize, logoSize),
            new SKSamplingOptions(SKCubicResampler.Mitchell));
        if (resizedLogo != null) canvas.DrawBitmap(resizedLogo, logoX, logoY);

        // Encode to PNG
        using var image = surface.Snapshot();
        using var data = image.Encode(SKEncodedImageFormat.Png, 100);

        return data.ToArray();
    }

    private static QRCodeGenerator.ECCLevel ParseEccLevel(string level)
    {
        return level.ToUpperInvariant() switch
        {
            "L" => QRCodeGenerator.ECCLevel.L,
            "M" => QRCodeGenerator.ECCLevel.M,
            "Q" => QRCodeGenerator.ECCLevel.Q,
            "H" => QRCodeGenerator.ECCLevel.H,
            _ => QRCodeGenerator.ECCLevel.M
        };
    }

    private static SKColor ParseSkColor(string hexColor)
    {
        // Remove # if present
        var hex = hexColor.TrimStart('#');

        if (hex.Length == 6)
        {
            var r = Convert.ToByte(hex[..2], 16);
            var g = Convert.ToByte(hex[2..4], 16);
            var b = Convert.ToByte(hex[4..6], 16);
            return new SKColor(r, g, b);
        }

        // Default to black
        return SKColors.Black;
    }
}