using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using AutoIngest.Core;
using AutoIngest.Engine;

namespace AutoIngest.App
{
    /// <summary>
    /// The branding editor: store identity fields, text/logo watermark toggles + settings, and
    /// EXIF copyright fields, with a live preview rendered via Magick.NET. Shared by the first-run
    /// WelcomeForm and the tray-opened BrandingForm so the two never duplicate layout. Edits flow
    /// straight into the bound <see cref="BrandingConfig"/>; the host form calls <see cref="Save"/>
    /// to persist (or the user can cancel).
    /// </summary>
    public class BrandingEditorPanel : UserControl
    {
        readonly BrandingConfig _b;
        readonly AppConfig _config;

        TextBox _storeName = null!, _storeUrl = null!, _handle = null!;
        CheckBox _textOn = null!, _logoOn = null!, _exifOn = null!;
        ComboBox _textPos = null!, _logoPos = null!;
        TrackBar _textOpacity = null!, _logoOpacity = null!, _logoScale = null!;
        TextBox _logoPath = null!;
        Button _pickLogo = null!;
        TextBox _exifArtist = null!, _exifCopyright = null!, _exifDesc = null!;
        PictureBox _preview = null!;
        Button _refreshPreview = null!;
        Label _previewHint = null!;

        // The MemoryStream backing the current _preview.Image. GDI+ Image.FromStream keeps a
        // reference to the source stream, so it must stay alive for as long as the image is on
        // screen — we hold it here and dispose it when the next preview replaces it (or when the
        // panel is disposed). Previously this stream was disposed (via "using var") the moment
        // RenderPreview returned, which made every preview fail with "Cannot access a closed Stream."
        MemoryStream? _previewStream;

        public BrandingEditorPanel(AppConfig config)
        {
            _config = config;
            _b = config.Branding;
            Dock = DockStyle.Fill;
            BackColor = Theme.BG;
            ForeColor = Theme.TEXT;
            BuildUi();
            LoadFromConfig();
            UpdateEnabledStates();
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                // Drop the live preview image and its backing stream together — GDI+ tied the
                // image's lifetime to the stream, so both go.
                _preview.Image?.Dispose();
                _preview.Image = null;
                _previewStream?.Dispose();
                _previewStream = null;
            }
            base.Dispose(disposing);
        }

        void BuildUi()
        {
            // Two columns: left = fields, right = preview. Use a top-level TableLayoutPanel.
            var cols = new TableLayoutPanel
            {
                Dock = DockStyle.Fill,
                ColumnCount = 2,
                RowCount = 1,
                BackColor = Theme.BG,
                Padding = new Padding(12)
            };
            cols.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 360));
            cols.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
            Controls.Add(cols);

            var leftScroll = new Panel { Dock = DockStyle.Fill, AutoScroll = true, BackColor = Theme.BG };
            var right = new Panel { Dock = DockStyle.Fill, BackColor = Theme.BG, Padding = new Padding(12, 0, 0, 0) };
            cols.Controls.Add(leftScroll, 0, 0);
            cols.Controls.Add(right, 1, 0);

            BuildLeftColumn(leftScroll);
            BuildRightColumn(right);
        }

        void BuildLeftColumn(Panel host)
        {
            int y = 0;
            const int RowH = 28;
            const int LabelW = 130;
            const int FieldW = 210;

            void AddSection(string title)
            {
                var lbl = new Label
                {
                    Text = title,
                    Location = new Point(0, y),
                    Size = new Size(340, 22),
                    ForeColor = Theme.ACCENT,
                    BackColor = Theme.BG
                };
                host.Controls.Add(lbl);
                y += 26;
            }

            Label FieldLabel(string text) => new()
            {
                Text = text,
                Location = new Point(0, y + 3),
                Size = new Size(LabelW, 20),
                ForeColor = Theme.TEXT_DIM,
                BackColor = Theme.BG
            };

            TextBox Field(string placeholder = "")
            {
                var tb = new TextBox
                {
                    Location = new Point(LabelW, y),
                    Size = new Size(FieldW, RowH),
                    BackColor = Theme.BG_DARK,
                    ForeColor = Theme.TEXT,
                    BorderStyle = BorderStyle.FixedSingle,
                    PlaceholderText = placeholder
                };
                return tb;
            }

            // --- Store identity -------------------------------------------------------
            AddSection("Store identity (drives the text watermark)");
            _storeName = Field("e.g. ReelDeals"); host.Controls.Add(FieldLabel("Store name")); host.Controls.Add(_storeName); y += RowH + 4;
            _storeUrl = Field("e.g. ebay.com/usr/reeldeals"); host.Controls.Add(FieldLabel("Store URL")); host.Controls.Add(_storeUrl); y += RowH + 4;
            _handle = Field("e.g. reeldeals"); host.Controls.Add(FieldLabel("Seller handle")); host.Controls.Add(_handle); y += RowH + 10;

            // --- Text watermark -------------------------------------------------------
            AddSection("Text watermark");
            _textOn = new CheckBox
            {
                Text = "Add text watermark",
                Location = new Point(0, y),
                Size = new Size(340, 22),
                ForeColor = Theme.TEXT,
                BackColor = Theme.BG
            };
            _textOn.CheckedChanged += (_, _) => UpdateEnabledStates();
            host.Controls.Add(_textOn); y += RowH;

            host.Controls.Add(FieldLabel("Position"));
            _textPos = PositionCombo();
            _textPos.Location = new Point(LabelW, y);
            host.Controls.Add(_textPos); y += RowH + 4;

            host.Controls.Add(FieldLabel($"Opacity ({_b.TextOpacity}%)"));
            _textOpacity = new TrackBar
            {
                Minimum = 5, Maximum = 100,
                Value = Math.Clamp(_b.TextOpacity, 5, 100),
                Location = new Point(LabelW, y - 2),
                Size = new Size(FieldW, 36),
                TickFrequency = 10
            };
            // The label was the last control added; tag the slider with it so the slider can keep
            // its "(NN%)" text in sync as the user drags.
            _textOpacity.Tag = host.Controls[host.Controls.Count - 1];
            _textOpacity.ValueChanged += (_, _) =>
            {
                if (_textOpacity.Tag is Label tl)
                    tl.Text = $"Opacity ({_textOpacity.Value}%)";
            };
            host.Controls.Add(_textOpacity); y += RowH + 10;

            // --- Logo watermark -------------------------------------------------------
            AddSection("Logo watermark");
            _logoOn = new CheckBox
            {
                Text = "Add logo watermark",
                Location = new Point(0, y),
                Size = new Size(340, 22),
                ForeColor = Theme.TEXT,
                BackColor = Theme.BG
            };
            _logoOn.CheckedChanged += (_, _) => UpdateEnabledStates();
            host.Controls.Add(_logoOn); y += RowH;

            host.Controls.Add(FieldLabel("Logo file"));
            _logoPath = Field("path to a PNG…");
            _logoPath.ReadOnly = true;
            _logoPath.Size = new Size(FieldW - 80, RowH);
            host.Controls.Add(_logoPath);
            _pickLogo = new Button
            {
                Text = "Browse…",
                FlatStyle = FlatStyle.Flat,
                BackColor = Theme.BG2,
                ForeColor = Theme.TEXT,
                Location = new Point(LabelW + FieldW - 75, y),
                Size = new Size(75, RowH - 2)
            };
            _pickLogo.Click += (_, _) => PickLogo();
            host.Controls.Add(_pickLogo); y += RowH + 4;

            host.Controls.Add(FieldLabel("Position"));
            _logoPos = PositionCombo();
            _logoPos.Location = new Point(LabelW, y);
            host.Controls.Add(_logoPos); y += RowH + 4;

            host.Controls.Add(FieldLabel($"Scale ({_b.LogoScalePercent}% of width)"));
            _logoScale = new TrackBar
            {
                Minimum = 0, Maximum = 60, Value = Math.Min(60, _b.LogoScalePercent),
                Location = new Point(LabelW, y - 2),
                Size = new Size(FieldW, 36),
                TickFrequency = 10
            };
            _logoScale.Tag = host.Controls[host.Controls.Count - 2];
            _logoScale.ValueChanged += (_, _) =>
            {
                if (_logoScale.Tag is Label sl)
                    sl.Text = $"Scale ({_logoScale.Value}% of width)";
            };
            host.Controls.Add(_logoScale); y += 34;

            host.Controls.Add(FieldLabel($"Opacity ({_b.LogoOpacity}%)"));
            _logoOpacity = new TrackBar
            {
                Minimum = 5, Maximum = 100, Value = _b.LogoOpacity,
                Location = new Point(LabelW, y - 2),
                Size = new Size(FieldW, 36),
                TickFrequency = 10
            };
            _logoOpacity.Tag = host.Controls[host.Controls.Count - 2];
            _logoOpacity.ValueChanged += (_, _) =>
            {
                if (_logoOpacity.Tag is Label ll)
                    ll.Text = $"Opacity ({_logoOpacity.Value}%)";
            };
            host.Controls.Add(_logoOpacity); y += 40;

            // --- EXIF copyright -------------------------------------------------------
            AddSection("EXIF copyright (written after strip)");
            _exifOn = new CheckBox
            {
                Text = "Embed my copyright fields",
                Location = new Point(0, y),
                Size = new Size(340, 22),
                ForeColor = Theme.TEXT,
                BackColor = Theme.BG
            };
            _exifOn.CheckedChanged += (_, _) => UpdateEnabledStates();
            host.Controls.Add(_exifOn); y += RowH;

            _exifArtist = Field("your name / business"); host.Controls.Add(FieldLabel("Artist")); host.Controls.Add(_exifArtist); y += RowH + 4;
            _exifCopyright = Field("© 2026 Your Name"); host.Controls.Add(FieldLabel("Copyright")); host.Controls.Add(_exifCopyright); y += RowH + 4;
            _exifDesc = Field("optional short description"); host.Controls.Add(FieldLabel("Description")); host.Controls.Add(_exifDesc); y += RowH + 10;

            var note = new Label
            {
                Location = new Point(0, y),
                Size = new Size(340, 44),
                ForeColor = Theme.TEXT_DIM,
                BackColor = Theme.BG,
                Text = "Watermarks and EXIF apply only to converted files (RAW/HEIC/etc).\r\nJPGs moved directly pass through untouched."
            };
            host.Controls.Add(note);
        }

        void BuildRightColumn(Panel host)
        {
            var lbl = new Label
            {
                Text = "Preview",
                Dock = DockStyle.Top,
                Height = 24,
                ForeColor = Theme.ACCENT,
                BackColor = Theme.BG
            };
            host.Controls.Add(lbl);

            _preview = new PictureBox
            {
                Dock = DockStyle.Fill,
                BackColor = Theme.BG_DARK,
                BorderStyle = BorderStyle.FixedSingle,
                SizeMode = PictureBoxSizeMode.Zoom
            };
            host.Controls.Add(_preview);
            _preview.BringToFront();

            var btnRow = new Panel { Dock = DockStyle.Bottom, Height = 32, BackColor = Theme.BG };
            _refreshPreview = new Button
            {
                Text = "Refresh preview",
                FlatStyle = FlatStyle.Flat,
                BackColor = Theme.BG2,
                ForeColor = Theme.TEXT,
                Dock = DockStyle.Fill
            };
            _refreshPreview.Click += (_, _) => RenderPreview();
            btnRow.Controls.Add(_refreshPreview);
            host.Controls.Add(btnRow);

            _previewHint = new Label
            {
                Dock = DockStyle.Bottom,
                Height = 18,
                ForeColor = Theme.TEXT_DIM,
                BackColor = Theme.BG,
                Text = "Preview renders on a sample image.",
                TextAlign = ContentAlignment.MiddleCenter
            };
            host.Controls.Add(_previewHint);
        }

        ComboBox PositionCombo()
        {
            var cb = new ComboBox
            {
                DropDownStyle = ComboBoxStyle.DropDownList,
                FlatStyle = FlatStyle.Flat,
                BackColor = Theme.BG2,
                ForeColor = Theme.TEXT,
                Size = new Size(130, 22)
            };
            foreach (var name in Enum.GetNames<WatermarkPosition>())
                cb.Items.Add(name);
            return cb;
        }


        void UpdateEnabledStates()
        {
            _textPos.Enabled = _textOn.Checked;
            _textOpacity.Enabled = _textOn.Checked;
            _logoPath.Enabled = _logoOn.Checked;
            _pickLogo.Enabled = _logoOn.Checked;
            _logoPos.Enabled = _logoOn.Checked;
            _logoScale.Enabled = _logoOn.Checked;
            _logoOpacity.Enabled = _logoOn.Checked;
            _exifArtist.Enabled = _exifOn.Checked;
            _exifCopyright.Enabled = _exifOn.Checked;
            _exifDesc.Enabled = _exifOn.Checked;
        }

        void PickLogo()
        {
            using var dlg = new OpenFileDialog
            {
                Title = "Choose a logo (PNG with transparency recommended)",
                Filter = "Images|*.png;*.jpg;*.jpeg;*.bmp|All files|*.*"
            };
            if (dlg.ShowDialog(FindForm()) == DialogResult.OK)
                _logoPath.Text = dlg.FileName;
        }

        void LoadFromConfig()
        {
            _storeName.Text = _b.StoreName;
            _storeUrl.Text = _b.StoreUrl;
            _handle.Text = _b.SellerHandle;

            _textOn.Checked = _b.TextWatermarkEnabled;
            SelectByName(_textPos, _b.TextPosition);
            _textOpacity.Value = ClampTrack(_textOpacity, _b.TextOpacity);

            _logoOn.Checked = _b.LogoWatermarkEnabled;
            _logoPath.Text = _b.LogoPath;
            SelectByName(_logoPos, _b.LogoPosition);
            _logoScale.Value = ClampTrack(_logoScale, _b.LogoScalePercent);
            _logoOpacity.Value = ClampTrack(_logoOpacity, _b.LogoOpacity);

            _exifOn.Checked = _b.ExifCopyrightEnabled;
            _exifArtist.Text = _b.ExifArtist;
            _exifCopyright.Text = _b.ExifCopyright;
            _exifDesc.Text = _b.ExifDescription;
        }

        /// <summary>Writes the current UI state back into the bound config (does not persist).</summary>
        public void ApplyToConfig()
        {
            _b.StoreName = _storeName.Text.Trim();
            _b.StoreUrl = _storeUrl.Text.Trim();
            _b.SellerHandle = _handle.Text.Trim();

            _b.TextWatermarkEnabled = _textOn.Checked;
            if (_textPos.SelectedItem is string tp && Enum.TryParse<WatermarkPosition>(tp, out var tpos))
                _b.TextPosition = tpos;
            _b.TextOpacity = _textOpacity.Value;

            _b.LogoWatermarkEnabled = _logoOn.Checked;
            _b.LogoPath = _logoPath.Text.Trim();
            if (_logoPos.SelectedItem is string lp && Enum.TryParse<WatermarkPosition>(lp, out var lpos))
                _b.LogoPosition = lpos;
            _b.LogoScalePercent = _logoScale.Value;
            _b.LogoOpacity = _logoOpacity.Value;

            _b.ExifCopyrightEnabled = _exifOn.Checked;
            _b.ExifArtist = _exifArtist.Text.Trim();
            _b.ExifCopyright = _exifCopyright.Text.Trim();
            _b.ExifDescription = _exifDesc.Text.Trim();

            // Master switch: branding is "enabled" if any of the three features are on. This keeps
            // the semantics simple — turning everything off disables branding entirely.
            _b.Enabled = _b.TextWatermarkEnabled || _b.LogoWatermarkEnabled || _b.ExifCopyrightEnabled;
        }

        /// <summary>Persists config to disk.</summary>
        public void Save()
        {
            ApplyToConfig();
            ConfigManager.SaveConfig(_config);
        }

        public void RenderPreview()
        {
            ApplyToConfig();
            // Render on a background thread so the UI doesn't freeze on large Magick.NET ops.
            var hint = _previewHint;
            hint.Text = "Rendering preview…";
            System.Threading.Tasks.Task.Run(() =>
            {
                // Ownership of the returned stream transfers to us. We must NOT "using" it here —
                // GDI+ keeps the stream open for the image's lifetime, so we hand it to
                // _previewStream on the UI thread and let it live until the next preview replaces
                // it (or Dispose runs).
                MemoryStream? newStream;
                Image? img;
                try
                {
                    newStream = RenderSamplePreview(_b);
                    img = Image.FromStream(newStream);
                }
                catch (Exception ex)
                {
                    // Render/decode failed — tell the UI if it's still around. UiInvokeIfAlive
                    // tolerates the panel being disposed (e.g. the user closed the form while we
                    // were rendering); a plain this.Invoke here would cascade the failure into an
                    // unhandled crash.
                    var msg = ex.Message;
                    UiInvokeIfAlive(() => hint.Text = $"Preview failed: {msg}");
                    return;
                }

                // Hand the live stream + image to the UI thread. Returns false if the panel is gone
                // (form closed mid-render) — in that case drop both locally rather than marshaling
                // into a disposed control and leaking the stream.
                if (!UiInvokeIfAlive(() =>
                {
                    _preview.Image?.Dispose();
                    _previewStream?.Dispose();
                    _previewStream = newStream;
                    _preview.Image = img;
                    hint.Text = "Preview rendered on a sample image.";
                }))
                {
                    img.Dispose();
                    newStream.Dispose();
                }
            });
        }

        /// <summary>Marshals an action onto the control's UI thread, tolerating the control being
        /// closed/disposed before the background render finishes. RenderPreview runs on a Task.Run;
        /// if the form closes (Save/Cancel) while Magick is still rendering, the window handle is
        /// gone by the time we marshal back. A bare Invoke throws InvalidOperationException there
        /// and — because it's called from the catch path too — would crash the app. This helper
        /// returns true if the action ran, false if the control is gone.</summary>
        bool UiInvokeIfAlive(Action action)
        {
            if (IsDisposed || !IsHandleCreated) return false;
            try { Invoke(action); return true; }
            // ObjectDisposedException derives from InvalidOperationException, so this covers both
            // the "handle destroyed between the check and Invoke" race and an outright disposal.
            catch (InvalidOperationException) { return false; }
        }

        /// <summary>Renders a small sample image with the current branding applied and returns it
        /// as PNG bytes in a fresh MemoryStream. Ownership of the stream transfers to the caller;
        /// it is NOT disposed here (a previous "using var ms" + "return ms" returned an already-
        /// disposed stream). The caller must keep the stream open for as long as any Image created
        /// from it is in use.</summary>
        static MemoryStream RenderSamplePreview(BrandingConfig b)
        {
            // Build a synthetic photo-like background so the preview is representative without
            // shipping a sample asset. A soft gradient with a mid-tone works well.
            using var image = new ImageMagick.MagickImage(new ImageMagick.MagickColor("#3a3f4b"), 640, 400);
            // Apply branding the same way ConvertToJpg does. BrandingRenderer.Apply handles
            // disabled/no-op cases internally.
            BrandingRenderer.Apply(image, b);
            // No "using" — the caller owns this stream and needs it alive for the Image's lifetime.
            var ms = new MemoryStream();
            image.Write(ms, ImageMagick.MagickFormat.Png);
            ms.Position = 0;
            return ms;
        }

        static void SelectByName(ComboBox cb, WatermarkPosition pos)
        {
            string name = pos.ToString();
            for (int i = 0; i < cb.Items.Count; i++)
                if (cb.Items[i] is string s && s == name) { cb.SelectedIndex = i; return; }
            if (cb.Items.Count > 0) cb.SelectedIndex = 0;
        }

        static int ClampTrack(TrackBar tb, int v) =>
            v < tb.Minimum ? tb.Minimum : v > tb.Maximum ? tb.Maximum : v;
    }
}
