using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using SharepointToolbox.Core.Models;
using SharepointToolbox.Infrastructure.Persistence;

namespace SharepointToolbox.Services;

public class BrandingService : IBrandingService
{
    private const int MaxSizeBytes = 512 * 1024; // 512 KB

    // PNG signature: first 4 bytes
    private static readonly byte[] PngMagic = { 0x89, 0x50, 0x4E, 0x47 };
    // JPEG signature: first 3 bytes
    private static readonly byte[] JpegMagic = { 0xFF, 0xD8, 0xFF };

    private readonly BrandingRepository _repository;

    public BrandingService(BrandingRepository repository)
    {
        _repository = repository;
    }

    /// <summary>
    /// Reads a file, validates that it is PNG or JPEG via magic bytes, auto-compresses if over 512 KB,
    /// and returns a LogoData record. Does NOT persist anything — the caller decides where to store it.
    /// </summary>
    public async Task<LogoData> ImportLogoAsync(string filePath)
    {
        var bytes = await File.ReadAllBytesAsync(filePath);
        return await ImportLogoFromBytesAsync(bytes);
    }

    /// <summary>
    /// Validates raw bytes as PNG or JPEG via magic bytes, auto-compresses if over 512 KB,
    /// and returns a LogoData record. Used when bytes are obtained from a stream (e.g. Entra branding API).
    /// </summary>
    public Task<LogoData> ImportLogoFromBytesAsync(byte[] bytes)
    {
        var mimeType = DetectMimeType(bytes);

        if (bytes.Length > MaxSizeBytes)
        {
            bytes = CompressToLimit(bytes, mimeType, MaxSizeBytes);
        }

        return Task.FromResult(new LogoData
        {
            Base64 = Convert.ToBase64String(bytes),
            MimeType = mimeType
        });
    }

    public async Task SaveMspLogoAsync(LogoData logo)
    {
        var settings = await _repository.LoadAsync();
        settings.MspLogo = logo;
        await _repository.SaveAsync(settings);
    }

    public async Task ClearMspLogoAsync()
    {
        var settings = await _repository.LoadAsync();
        settings.MspLogo = null;
        await _repository.SaveAsync(settings);
    }

    public async Task<LogoData?> GetMspLogoAsync()
    {
        var settings = await _repository.LoadAsync();
        return settings.MspLogo;
    }

    // -------------------------------------------------------------------------
    // Private helpers
    // -------------------------------------------------------------------------

    private static string DetectMimeType(byte[] bytes)
    {
        if (bytes.Length == 0)
            throw new InvalidDataException("File is empty. Only PNG and JPG files are accepted.");

        if (bytes.Length >= 4 && bytes[0] == PngMagic[0] && bytes[1] == PngMagic[1]
                               && bytes[2] == PngMagic[2] && bytes[3] == PngMagic[3])
            return "image/png";

        if (bytes.Length >= 3 && bytes[0] == JpegMagic[0] && bytes[1] == JpegMagic[1]
                               && bytes[2] == JpegMagic[2])
            return "image/jpeg";

        throw new InvalidDataException(
            "File format is not PNG or JPG. Only PNG and JPG are accepted.");
    }

    /// <summary>
    /// Compresses image bytes using WPF imaging (PresentationCore) to fit within <paramref name="maxBytes"/>.
    /// Resizes proportionally to max 300x300 at quality 75 first pass; if still too large, 200x200 at quality 50.
    /// </summary>
    private static byte[] CompressToLimit(byte[] bytes, string mimeType, int maxBytes)
    {
        // First pass: resize to 300x300 max, quality 75
        var compressed = ResizeAndEncode(bytes, mimeType, 300, 75);
        if (compressed.Length <= maxBytes)
            return compressed;

        // Second pass: resize to 200x200 max, quality 50
        compressed = ResizeAndEncode(bytes, mimeType, 200, 50);
        return compressed;
    }

    private static byte[] ResizeAndEncode(byte[] originalBytes, string mimeType, int maxDimension, int quality)
    {
        // Decode source image using WPF BitmapDecoder
        using var inputStream = new MemoryStream(originalBytes);
        var decoder = BitmapDecoder.Create(
            inputStream,
            BitmapCreateOptions.PreservePixelFormat,
            BitmapCacheOption.OnLoad);

        var frame = decoder.Frames[0];

        // Calculate target dimensions (proportional scaling)
        double srcWidth = frame.PixelWidth;
        double srcHeight = frame.PixelHeight;
        double scale = Math.Min((double)maxDimension / srcWidth, (double)maxDimension / srcHeight);

        // Only scale down, never up
        if (scale >= 1.0)
            scale = 1.0;

        int targetWidth = Math.Max(1, (int)(srcWidth * scale));
        int targetHeight = Math.Max(1, (int)(srcHeight * scale));

        // Scale the bitmap using TransformedBitmap
        var scaledBitmap = new TransformedBitmap(
            frame,
            new ScaleTransform(scale, scale));

        // Encode to target format
        using var outputStream = new MemoryStream();
        BitmapEncoder encoder = mimeType == "image/png"
            ? new PngBitmapEncoder()
            : CreateJpegEncoder(quality);

        encoder.Frames.Add(BitmapFrame.Create(scaledBitmap));
        encoder.Save(outputStream);

        return outputStream.ToArray();
    }

    private static BitmapEncoder CreateJpegEncoder(int quality)
    {
        return new JpegBitmapEncoder
        {
            QualityLevel = quality
        };
    }
}
