using Avalonia;
using System;
using System.IO;
using Minint.Core.Models;
using Minint.Core.Services.Impl;
using Minint.Infrastructure.Serialization;

namespace Minint;

sealed class Program
{
    [STAThread]
    public static void Main(string[] args)
    {
        // TODO: remove --test branch after verification
        if (args.Length > 0 && args[0] == "--test")
        {
            RunRoundTripTest();
            RunCompositorTest();
            RunPaletteServiceTest();
            return;
        }

        BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
    }

    public static AppBuilder BuildAvaloniaApp()
        => AppBuilder.Configure<App>()
            .UsePlatformDetect()
            .WithInterFont()
            .LogToTrace()
            .With(new X11PlatformOptions { OverlayPopups = true });

    // TODO: temporary tests — remove after verification stages.

    private static void RunRoundTripTest()
    {
        Console.WriteLine("=== Minint Round-Trip Test ===\n");

        var container = new MinintContainer(8, 4);

        var doc1 = container.AddNewDocument("Frame 1");
        doc1.FrameDelayMs = 200;
        doc1.Palette.Add(new RgbaColor(255, 0, 0, 255));   // idx 1 = red
        doc1.Palette.Add(new RgbaColor(0, 255, 0, 255));   // idx 2 = green
        doc1.Palette.Add(new RgbaColor(0, 0, 255, 128));   // idx 3 = semi-transparent blue
        var layer1 = doc1.Layers[0];
        for (int i = 0; i < layer1.Pixels.Length; i++)
            layer1.Pixels[i] = i % 4; // cycle 0,1,2,3

        doc1.Layers.Add(new MinintLayer("Overlay", container.PixelCount));
        var layer2 = doc1.Layers[1];
        layer2.Opacity = 128;
        layer2.Pixels[0] = 3;
        layer2.Pixels[5] = 2;

        var doc2 = container.AddNewDocument("Frame 2");
        doc2.FrameDelayMs = 150;
        doc2.Palette.Add(new RgbaColor(255, 255, 0, 255)); // idx 1 = yellow
        var layer3 = doc2.Layers[0];
        for (int i = 0; i < layer3.Pixels.Length; i++)
            layer3.Pixels[i] = i % 2;

        Console.WriteLine($"Original: {container.Width}x{container.Height}, {container.Documents.Count} docs");
        Console.WriteLine($"  Doc1: palette={doc1.Palette.Count} colors, layers={doc1.Layers.Count}, indexWidth={doc1.IndexByteWidth}");
        Console.WriteLine($"  Doc2: palette={doc2.Palette.Count} colors, layers={doc2.Layers.Count}, indexWidth={doc2.IndexByteWidth}");

        var serializer = new MinintSerializer();

        using var ms = new MemoryStream();
        serializer.Write(ms, container);
        byte[] data = ms.ToArray();
        Console.WriteLine($"\nSerialized: {data.Length} bytes");
        Console.WriteLine($"  Signature: {System.Text.Encoding.ASCII.GetString(data, 0, 6)}");

        ms.Position = 0;
        var loaded = serializer.Read(ms);

        Assert(loaded.Width == container.Width, "Width mismatch");
        Assert(loaded.Height == container.Height, "Height mismatch");
        Assert(loaded.Documents.Count == container.Documents.Count, "Document count mismatch");

        for (int d = 0; d < container.Documents.Count; d++)
        {
            var orig = container.Documents[d];
            var copy = loaded.Documents[d];
            Assert(copy.Name == orig.Name, $"Doc[{d}] name mismatch");
            Assert(copy.FrameDelayMs == orig.FrameDelayMs, $"Doc[{d}] frameDelay mismatch");
            Assert(copy.Palette.Count == orig.Palette.Count, $"Doc[{d}] palette count mismatch");

            for (int c = 0; c < orig.Palette.Count; c++)
                Assert(copy.Palette[c] == orig.Palette[c], $"Doc[{d}] palette[{c}] mismatch");

            Assert(copy.Layers.Count == orig.Layers.Count, $"Doc[{d}] layer count mismatch");

            for (int l = 0; l < orig.Layers.Count; l++)
            {
                var oLayer = orig.Layers[l];
                var cLayer = copy.Layers[l];
                Assert(cLayer.Name == oLayer.Name, $"Doc[{d}].Layer[{l}] name mismatch");
                Assert(cLayer.IsVisible == oLayer.IsVisible, $"Doc[{d}].Layer[{l}] visibility mismatch");
                Assert(cLayer.Opacity == oLayer.Opacity, $"Doc[{d}].Layer[{l}] opacity mismatch");
                Assert(cLayer.Pixels.Length == oLayer.Pixels.Length, $"Doc[{d}].Layer[{l}] pixel count mismatch");

                for (int p = 0; p < oLayer.Pixels.Length; p++)
                    Assert(cLayer.Pixels[p] == oLayer.Pixels[p],
                        $"Doc[{d}].Layer[{l}].Pixels[{p}] mismatch: expected {oLayer.Pixels[p]}, got {cLayer.Pixels[p]}");
            }
        }

        Console.WriteLine("\n✓ All assertions passed — round-trip is correct!");

        // Test invalid signature
        Console.Write("\nTest: invalid signature... ");
        data[0] = (byte)'X';
        try
        {
            serializer.Read(new MemoryStream(data));
            Console.WriteLine("FAIL (no exception)");
        }
        catch (InvalidDataException)
        {
            Console.WriteLine("OK (InvalidDataException)");
        }
        data[0] = (byte)'M'; // restore

        // Test truncated stream
        Console.Write("Test: truncated stream... ");
        try
        {
            serializer.Read(new MemoryStream(data, 0, 10));
            Console.WriteLine("FAIL (no exception)");
        }
        catch (Exception ex) when (ex is InvalidDataException or EndOfStreamException)
        {
            Console.WriteLine($"OK ({ex.GetType().Name})");
        }

        Console.WriteLine("\n=== All tests passed ===");
    }

    private static void RunCompositorTest()
    {
        Console.WriteLine("\n=== Compositor Test ===\n");

        var compositor = new Compositor();
        const int W = 2, H = 2;

        // Document: 2 layers on a 2x2 canvas
        var doc = new MinintDocument("test");
        doc.Palette.Add(new RgbaColor(255, 0, 0, 255));  // idx 1 = opaque red
        doc.Palette.Add(new RgbaColor(0, 0, 255, 128));  // idx 2 = semi-transparent blue

        // Bottom layer: all red
        var bottom = new MinintLayer("bottom", W * H);
        for (int i = 0; i < bottom.Pixels.Length; i++)
            bottom.Pixels[i] = 1;
        doc.Layers.Add(bottom);

        // Top layer: pixel[0] = semi-blue, rest transparent
        var top = new MinintLayer("top", W * H);
        top.Pixels[0] = 2;
        doc.Layers.Add(top);

        uint[] result = compositor.Composite(doc, W, H);

        // Pixel [1],[2],[3]: only bottom visible → opaque red → 0xFFFF0000 (ARGB)
        uint opaqueRed = 0xFF_FF_00_00;
        Assert(result[1] == opaqueRed, $"Pixel[1]: expected {opaqueRed:X8}, got {result[1]:X8}");
        Assert(result[2] == opaqueRed, $"Pixel[2]: expected {opaqueRed:X8}, got {result[2]:X8}");
        Assert(result[3] == opaqueRed, $"Pixel[3]: expected {opaqueRed:X8}, got {result[3]:X8}");

        // Pixel [0]: red(255,0,0,255) under blue(0,0,255,128)
        // srcA=128, dstA=255 → outA = 128 + 255*(255-128)/255 = 128+127 = 255
        // outR = (0*128 + 255*255*(127)/255) / 255 = (0 + 255*127)/255 = 127
        // outG = 0
        // outB = (255*128 + 0) / 255 = 128
        uint blended = result[0];
        byte bA = (byte)(blended >> 24);
        byte bR = (byte)((blended >> 16) & 0xFF);
        byte bG = (byte)((blended >> 8) & 0xFF);
        byte bB = (byte)(blended & 0xFF);

        Console.WriteLine($"  Blended pixel[0]: A={bA} R={bR} G={bG} B={bB}");
        Assert(bA == 255, $"Pixel[0] A: expected 255, got {bA}");
        Assert(bR >= 125 && bR <= 129, $"Pixel[0] R: expected ~127, got {bR}");
        Assert(bG == 0, $"Pixel[0] G: expected 0, got {bG}");
        Assert(bB >= 126 && bB <= 130, $"Pixel[0] B: expected ~128, got {bB}");

        // Test hidden layer: hide top, result should be all red
        top.IsVisible = false;
        uint[] result2 = compositor.Composite(doc, W, H);
        Assert(result2[0] == opaqueRed, $"Hidden top: Pixel[0] should be red, got {result2[0]:X8}");

        // Test layer opacity=0: make top visible but opacity=0
        top.IsVisible = true;
        top.Opacity = 0;
        uint[] result3 = compositor.Composite(doc, W, H);
        Assert(result3[0] == opaqueRed, $"Opacity 0: Pixel[0] should be red, got {result3[0]:X8}");

        // Test single transparent layer
        var emptyDoc = new MinintDocument("empty");
        emptyDoc.Layers.Add(new MinintLayer("bg", W * H));
        uint[] result4 = compositor.Composite(emptyDoc, W, H);
        Assert(result4[0] == 0, $"Empty layer: Pixel[0] should be 0x00000000, got {result4[0]:X8}");

        Console.WriteLine("✓ Compositor tests passed!");
    }

    private static void RunPaletteServiceTest()
    {
        Console.WriteLine("\n=== PaletteService Test ===\n");

        var svc = new PaletteService();
        var doc = new MinintDocument("test");

        // Palette starts with [Transparent]
        Assert(doc.Palette.Count == 1, "Initial palette should have 1 entry");

        // EnsureColor: new color
        var red = new RgbaColor(255, 0, 0, 255);
        int redIdx = svc.EnsureColor(doc, red);
        Assert(redIdx == 1, $"Red index: expected 1, got {redIdx}");
        Assert(doc.Palette.Count == 2, $"Palette count after red: expected 2, got {doc.Palette.Count}");

        // EnsureColor: same color → same index
        int redIdx2 = svc.EnsureColor(doc, red);
        Assert(redIdx2 == 1, $"Red re-ensure: expected 1, got {redIdx2}");
        Assert(doc.Palette.Count == 2, "Palette should not grow on duplicate");

        // FindColor
        Assert(svc.FindColor(doc, red) == 1, "FindColor red");
        Assert(svc.FindColor(doc, new RgbaColor(0, 0, 0, 255)) == -1, "FindColor missing");

        // Compact: add unused color, then compact
        var green = new RgbaColor(0, 255, 0, 255);
        int greenIdx = svc.EnsureColor(doc, green); // idx 2
        doc.Layers.Add(new MinintLayer("L", 4));
        doc.Layers[0].Pixels[0] = 1; // red used
        doc.Layers[0].Pixels[1] = 0; // transparent used
        // green (idx 2) is NOT used by any pixel

        Console.WriteLine($"  Before compact: palette has {doc.Palette.Count} colors (green at idx {greenIdx})");
        svc.CompactPalette(doc);
        Console.WriteLine($"  After compact: palette has {doc.Palette.Count} colors");

        Assert(doc.Palette.Count == 2, $"After compact: expected 2 colors, got {doc.Palette.Count}");
        Assert(doc.Palette[0] == RgbaColor.Transparent, "Palette[0] should be transparent");
        Assert(doc.Palette[1] == red, $"Palette[1] should be red, got {doc.Palette[1]}");
        Assert(doc.Layers[0].Pixels[0] == 1, "Pixel[0] should still map to red (idx 1)");

        Console.WriteLine("✓ PaletteService tests passed!");
    }

    private static void Assert(bool condition, string message)
    {
        if (!condition)
            throw new Exception($"Assertion failed: {message}");
    }
}
