using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using SharepointToolbox.Core.Models;
using SharepointToolbox.Infrastructure.Persistence;
using SharepointToolbox.Services;

namespace SharepointToolbox.Tests.Services;

[Trait("Category", "Unit")]
public class BrandingServiceTests : IDisposable
{
    private readonly string _tempRepoFile;
    private readonly List<string> _tempFiles = new();

    public BrandingServiceTests()
    {
        _tempRepoFile = Path.GetTempFileName();
        File.Delete(_tempRepoFile);
    }

    public void Dispose()
    {
        if (File.Exists(_tempRepoFile)) File.Delete(_tempRepoFile);
        if (File.Exists(_tempRepoFile + ".tmp")) File.Delete(_tempRepoFile + ".tmp");
        foreach (var f in _tempFiles)
        {
            if (File.Exists(f)) File.Delete(f);
        }
    }

    private BrandingRepository CreateRepository() => new(_tempRepoFile);
    private BrandingService CreateService() => new(CreateRepository());

    private string WriteTempFile(byte[] bytes)
    {
        var path = Path.GetTempFileName();
        File.WriteAllBytes(path, bytes);
        _tempFiles.Add(path);
        return path;
    }

    // Minimal valid 1x1 PNG bytes
    private static byte[] MinimalPngBytes()
    {
        // Full 1x1 transparent PNG (67 bytes)
        return new byte[]
        {
            0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, // PNG signature
            0x00, 0x00, 0x00, 0x0D, // IHDR length
            0x49, 0x48, 0x44, 0x52, // IHDR
            0x00, 0x00, 0x00, 0x01, // width = 1
            0x00, 0x00, 0x00, 0x01, // height = 1
            0x08, 0x02,             // bit depth = 8, color type = RGB
            0x00, 0x00, 0x00,       // compression, filter, interlace
            0x90, 0x77, 0x53, 0xDE, // CRC
            0x00, 0x00, 0x00, 0x0C, // IDAT length
            0x49, 0x44, 0x41, 0x54, // IDAT
            0x08, 0xD7, 0x63, 0xF8, 0xCF, 0xC0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, // compressed data
            0xE2, 0x21, 0xBC, 0x33, // CRC
            0x00, 0x00, 0x00, 0x00, // IEND length
            0x49, 0x45, 0x4E, 0x44, // IEND
            0xAE, 0x42, 0x60, 0x82  // CRC
        };
    }

    // Minimal valid JPEG bytes (SOI + APP0 JFIF header + EOI)
    private static byte[] MinimalJpegBytes()
    {
        return new byte[]
        {
            0xFF, 0xD8,             // SOI
            0xFF, 0xE0,             // APP0 marker
            0x00, 0x10,             // length = 16
            0x4A, 0x46, 0x49, 0x46, 0x00, // "JFIF\0"
            0x01, 0x01,             // version 1.1
            0x00,                   // aspect ratio units = 0
            0x00, 0x01, 0x00, 0x01, // X/Y density = 1
            0x00, 0x00,             // thumbnail size
            0xFF, 0xD9              // EOI
        };
    }

    [Fact]
    public async Task ImportLogoAsync_ValidPng_ReturnsPngLogoData()
    {
        var service = CreateService();
        var pngBytes = MinimalPngBytes();
        var path = WriteTempFile(pngBytes);

        var result = await service.ImportLogoAsync(path);

        Assert.Equal("image/png", result.MimeType);
        Assert.Equal(Convert.ToBase64String(pngBytes), result.Base64);
    }

    [Fact]
    public async Task ImportLogoAsync_ValidJpeg_ReturnsJpegLogoData()
    {
        var service = CreateService();
        var jpegBytes = MinimalJpegBytes();
        var path = WriteTempFile(jpegBytes);

        var result = await service.ImportLogoAsync(path);

        Assert.Equal("image/jpeg", result.MimeType);
    }

    [Fact]
    public async Task ImportLogoAsync_BmpFile_ThrowsInvalidDataExceptionMentioningPngAndJpg()
    {
        var service = CreateService();
        // BMP magic bytes: 0x42 0x4D
        var bmpBytes = new byte[] { 0x42, 0x4D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
        var path = WriteTempFile(bmpBytes);

        var ex = await Assert.ThrowsAsync<InvalidDataException>(() => service.ImportLogoAsync(path));
        Assert.Contains("PNG", ex.Message, StringComparison.OrdinalIgnoreCase);
        Assert.Contains("JPG", ex.Message, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public async Task ImportLogoAsync_EmptyFile_ThrowsInvalidDataException()
    {
        var service = CreateService();
        var path = WriteTempFile(Array.Empty<byte>());

        await Assert.ThrowsAsync<InvalidDataException>(() => service.ImportLogoAsync(path));
    }

    [Fact]
    public async Task ImportLogoAsync_FileUnder512KB_ReturnOriginalBytesUnmodified()
    {
        var service = CreateService();
        var pngBytes = MinimalPngBytes();
        var path = WriteTempFile(pngBytes);

        var result = await service.ImportLogoAsync(path);

        Assert.Equal(Convert.ToBase64String(pngBytes), result.Base64);
    }

    [Fact]
    public async Task ImportLogoAsync_FileOver512KB_ReturnsCompressedUnder512KB()
    {
        var service = CreateService();

        // Create a large PNG image in memory (400x400 random pixels)
        var largePngPath = Path.GetTempFileName();
        _tempFiles.Add(largePngPath);

        using (var bmp = new Bitmap(400, 400))
        {
            var rng = new Random(42);
            for (int y = 0; y < 400; y++)
                for (int x = 0; x < 400; x++)
                    bmp.SetPixel(x, y, Color.FromArgb(255, rng.Next(256), rng.Next(256), rng.Next(256)));
            bmp.Save(largePngPath, ImageFormat.Png);
        }

        var fileSize = new FileInfo(largePngPath).Length;
        // PNG with random pixels should exceed 512 KB
        // If not, we'll pad it
        if (fileSize <= 512 * 1024)
        {
            // Generate a bigger image to be sure
            using var bmp = new Bitmap(800, 800);
            var rng = new Random(42);
            for (int y = 0; y < 800; y++)
                for (int x = 0; x < 800; x++)
                    bmp.SetPixel(x, y, Color.FromArgb(255, rng.Next(256), rng.Next(256), rng.Next(256)));
            bmp.Save(largePngPath, ImageFormat.Png);
        }

        fileSize = new FileInfo(largePngPath).Length;
        Assert.True(fileSize > 512 * 1024, $"Test setup: PNG file must be > 512 KB but was {fileSize} bytes");

        var result = await service.ImportLogoAsync(largePngPath);

        var decodedBytes = Convert.FromBase64String(result.Base64);
        Assert.True(decodedBytes.Length <= 512 * 1024,
            $"Compressed result must be <= 512 KB but was {decodedBytes.Length} bytes");
    }

    [Fact]
    public async Task SaveMspLogoAsync_PersistsLogoInRepository()
    {
        var repo = CreateRepository();
        var service = new BrandingService(repo);
        var logo = new LogoData { Base64 = "abc123==", MimeType = "image/png" };

        await service.SaveMspLogoAsync(logo);

        var loaded = await repo.LoadAsync();
        Assert.NotNull(loaded.MspLogo);
        Assert.Equal("abc123==", loaded.MspLogo.Base64);
        Assert.Equal("image/png", loaded.MspLogo.MimeType);
    }

    [Fact]
    public async Task ClearMspLogoAsync_SetsMspLogoToNull()
    {
        var repo = CreateRepository();
        var service = new BrandingService(repo);
        var logo = new LogoData { Base64 = "abc123==", MimeType = "image/png" };
        await service.SaveMspLogoAsync(logo);

        await service.ClearMspLogoAsync();

        var loaded = await repo.LoadAsync();
        Assert.Null(loaded.MspLogo);
    }

    [Fact]
    public async Task GetMspLogoAsync_WhenNoLogoConfigured_ReturnsNull()
    {
        var service = CreateService();

        var result = await service.GetMspLogoAsync();

        Assert.Null(result);
    }

    [Fact]
    public async Task ImportLogoFromBytesAsync_ValidPngBytes_ReturnsPngLogoData()
    {
        var service = CreateService();
        var pngBytes = MinimalPngBytes();

        var result = await service.ImportLogoFromBytesAsync(pngBytes);

        Assert.Equal("image/png", result.MimeType);
        Assert.Equal(Convert.ToBase64String(pngBytes), result.Base64);
    }

    [Fact]
    public async Task ImportLogoFromBytesAsync_InvalidBytes_ThrowsInvalidDataException()
    {
        var service = CreateService();
        var invalidBytes = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };

        await Assert.ThrowsAsync<InvalidDataException>(() => service.ImportLogoFromBytesAsync(invalidBytes));
    }
}
