using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using AutoIngest.Core;
using ImageMagick;

namespace AutoIngest.App
{
    /// <summary>
    /// Builds the tray icon as a micro version of the user's branding — a rounded-square tile
    /// with the mark (logo or store initial) on an auto-contrasting background. The "intelligent
    /// color matching": we sample the mark's perceived luminance and pick the opposite background
    /// (light mark → dark bg, dark mark → light bg) so the icon reads on any taskbar color.
    ///
    /// Pipeline: render the mark at 64×64 (super-sampled for crispness), composite onto the tile
    /// background, then downsample to 16×16 with high-quality interpolation. Logo scaling uses
    /// Magick.NET (already a dependency, handles PNG alpha); tile/initial/downsample use GDI+
    /// (already in use for the tray, simpler for rounded-rect + hicon).
    ///
    /// Fallbacks: corrupt/missing logo → initial → lime-green circle (current default). Branding
    /// off entirely → green circle. Never throws; never leaves the tray empty.
    /// </summary>
    public static class TrayIconFactory
    {
        // Strong backgrounds (not mid-tones) so contrast is guaranteed on either taskbar theme.
        static readonly Color DarkBg = Color.FromArgb(31, 31, 31);   // ~#1f1f1f
        static readonly Color LightBg = Color.FromArgb(240, 240, 240); // ~#f0f0f0
        // The initial is drawn dark by default; the contrast step pairs it with a light bg.
        static readonly Color DarkMark = Color.FromArgb(30, 30, 30);

        const int HiRes = 64;  // super-sample at 4x for crisp downscale to 16
        const int FinalSize = 16;

        /// <summary>
        /// Builds the tray icon bitmap. Returns the green-circle fallback when branding is off or
        /// nothing usable is configured, so there's never an empty/garbled icon.
        /// </summary>
        public static Bitmap Build(BrandingConfig? branding, AppConfig config)
        {
            try
            {
                // Branding disabled or nothing to show → current default.
                if (branding == null || !branding.Enabled) return GreenCircle();

                // Try a logo first, then the store initial, then the green fallback.
                Bitmap? mark = null;
                if (branding.LogoWatermarkEnabled && File.Exists(branding.LogoPath))
                    mark = RenderLogo(branding.LogoPath);

                if (mark == null && HasInitial(branding, out string initial))
                    mark = RenderInitial(initial);

                if (mark == null) return GreenCircle();

                using (mark)
                {
                    double luminance = MedianLuminance(mark);
                    Color bg = luminance >= 0.5 ? DarkBg : LightBg;
                    return ComposeTile(mark, bg);
                }
            }
            catch
            {
                // Any surprise (GDI+ failure, OOM, etc.) → safe default. Never throw out of Build.
                return GreenCircle();
            }
        }

        /// <summary>Renders the logo onto a 64×32 transparent canvas, fit-contained with padding.</summary>
        static Bitmap? RenderLogo(string logoPath)
        {
            try
            {
                using var logo = new MagickImage(logoPath);

                int pad = HiRes / 10; // ~10% padding so the mark doesn't kiss the tile edge
                int maxW = HiRes - pad * 2;
                int maxH = HiRes - pad * 2;

                // Scale to fit while preserving aspect ratio.
                double scale = Math.Min((double)maxW / logo.Width, (double)maxH / logo.Height);
                if (scale > 1.0) scale = 1.0; // don't upscale tiny logos — they'd blur
                int newW = Math.Max(1, (int)(logo.Width * scale));
                int newH = Math.Max(1, (int)(logo.Height * scale));
                logo.Scale((uint)newW, (uint)newH);

                // Composite centered onto a transparent HiRes canvas.
                using var canvas = new MagickImage(MagickColors.None, (uint)HiRes, (uint)HiRes);
                int x = (HiRes - newW) / 2;
                int y = (HiRes - newH) / 2;
                canvas.Composite(logo, x, y, CompositeOperator.Over);

                // Export to a GDI+ bitmap with alpha intact.
                using var ms = new MemoryStream();
                canvas.Write(ms, MagickFormat.Png);
                ms.Position = 0;
                return new Bitmap(ms);
            }
            catch (MagickException)
            {
                return null; // corrupt/unreadable logo → caller falls back to initial
            }
            catch (IOException)
            {
                return null;
            }
        }

        /// <summary>Draws a single capital letter, bold, sized to ~70% of the 64px canvas.</summary>
        static Bitmap RenderInitial(string initial)
        {
            var bmp = new Bitmap(HiRes, HiRes, PixelFormat.Format32bppArgb);
            using (var g = Graphics.FromImage(bmp))
            {
                g.SmoothingMode = SmoothingMode.AntiAlias;
                g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
                g.Clear(Color.Transparent);

                // Size the font so the glyph fills ~70% of the canvas height. Step down until it fits.
                float fontSize = HiRes;
                Font font;
                SizeF measured;
                do
                {
                    fontSize -= 2;
                    font = new Font("Segoe UI", fontSize, FontStyle.Bold, GraphicsUnit.Pixel);
                    measured = g.MeasureString(initial, font);
                } while ((measured.Height > HiRes * 0.78 || measured.Width > HiRes * 0.85) && fontSize > 8);

                using (font)
                {
                    // Drawn dark; the contrast step will pair it with a light bg. Centered.
                    var fmt = new StringFormat
                    {
                        Alignment = StringAlignment.Center,
                        LineAlignment = StringAlignment.Center
                    };
                    var rect = new RectangleF(0, 0, HiRes, HiRes);
                    using var brush = new SolidBrush(DarkMark);
                    g.DrawString(initial, font, brush, rect, fmt);
                }
            }
            return bmp;
        }

        /// <summary>
        /// Composites the mark onto a rounded-square tile of the chosen background, then downsamples
        /// to the final 16×16 with high-quality interpolation.
        /// </summary>
        static Bitmap ComposeTile(Bitmap mark, Color bg)
        {
            var hi = new Bitmap(HiRes, HiRes, PixelFormat.Format32bppArgb);
            using (var g = Graphics.FromImage(hi))
            {
                g.SmoothingMode = SmoothingMode.AntiAlias;
                g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                g.PixelOffsetMode = PixelOffsetMode.HighQuality;

                // Rounded-square background. Radius ~22% gives the Win11 "squircle-ish" badge feel.
                using var path = RoundedRect(0, 0, HiRes, HiRes, HiRes * 0.22f);
                using var bgBrush = new SolidBrush(bg);
                g.FillPath(bgBrush, path);

                // Draw the mark on top. It has its own alpha (logo) or is opaque (initial), and
                // already sits centered in its own 64px canvas, so a 1:1 copy lands it correctly.
                g.DrawImage(mark, new Rectangle(0, 0, HiRes, HiRes));
            }

            // Downsample to 16×16. This is where the super-sampling pays off.
            var final = new Bitmap(FinalSize, FinalSize, PixelFormat.Format32bppArgb);
            using (var g = Graphics.FromImage(final))
            {
                g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                g.PixelOffsetMode = PixelOffsetMode.HighQuality;
                g.SmoothingMode = SmoothingMode.AntiAlias;
                g.DrawImage(hi, new Rectangle(0, 0, FinalSize, FinalSize));
            }
            hi.Dispose();
            return final;
        }

        /// <summary>
        /// Median luminance of the mark's non-transparent pixels. Median (not average) so a
        /// black-and-white logo doesn't average to mid-gray and produce a wrong contrast choice.
        /// Returns 0.0 (dark) for a fully-transparent mark.
        /// </summary>
        static double MedianLuminance(Bitmap bmp)
        {
            var lums = new List<double>();
            var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
            var data = bmp.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
            try
            {
                int bytes = data.Stride * data.Height;
                var buf = new byte[bytes];
                System.Runtime.InteropServices.Marshal.Copy(data.Scan0, buf, 0, bytes);
                for (int y = 0; y < data.Height; y++)
                {
                    int row = y * data.Stride;
                    for (int x = 0; x < data.Width; x++)
                    {
                        int i = row + x * 4;
                        byte a = buf[i + 3];
                        if (a < 16) continue; // treat near-transparent as no mark
                        byte b = buf[i + 0], g = buf[i + 1], r = buf[i + 2]; // 32bppArgb is BGRA
                        double lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255.0;
                        lums.Add(lum);
                    }
                }
            }
            finally
            {
                bmp.UnlockBits(data);
            }
            if (lums.Count == 0) return 0.0;
            lums.Sort();
            return lums[lums.Count / 2];
        }

        static System.Drawing.Drawing2D.GraphicsPath RoundedRect(float x, float y, float w, float h, float r)
        {
            var path = new System.Drawing.Drawing2D.GraphicsPath();
            float d = r * 2;
            path.AddArc(x, y, d, d, 180, 90);
            path.AddArc(x + w - d, y, d, d, 270, 90);
            path.AddArc(x + w - d, y + h - d, d, d, 0, 90);
            path.AddArc(x, y + h - d, d, d, 90, 90);
            path.CloseFigure();
            return path;
        }

        static bool HasInitial(BrandingConfig b, out string initial)
        {
            // Prefer store name, then handle; first non-space character.
            foreach (var source in new[] { b.StoreName, b.SellerHandle })
            {
                if (!string.IsNullOrWhiteSpace(source))
                {
                    char c = source.Trim()[0];
                    if (char.IsLetterOrDigit(c))
                    {
                        initial = char.ToUpperInvariant(c).ToString();
                        return true;
                    }
                }
            }
            initial = "";
            return false;
        }

        /// <summary>The original lime-green circle — the no-branding fallback.</summary>
        static Bitmap GreenCircle()
        {
            var bmp = new Bitmap(FinalSize, FinalSize, PixelFormat.Format32bppArgb);
            using (var g = Graphics.FromImage(bmp))
            {
                g.SmoothingMode = SmoothingMode.AntiAlias;
                g.FillEllipse(Brushes.LimeGreen, 1, 1, 14, 14);
                g.DrawEllipse(Pens.DarkGreen, 1, 1, 14, 14);
            }
            return bmp;
        }
    }
}
