using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Globalization;
using System.IO;
using System.Windows.Forms;
using AutoIngest.Core;
using AutoIngest.Engine;

namespace AutoIngest.App
{
    /// <summary>
    /// Tray-resident photo importer. The window is a small borderless toast that stays
    /// hidden by default and only pops into the bottom-right corner of the screen while
    /// an SD/USB import is running (and for a few seconds after it finishes).
    ///
    /// Static UI lives in MainForm.Designer.cs; this file holds the toast show/hide
    /// logic, tray icon, monitor wiring, and config.
    /// </summary>
    public partial class MainForm : Form
    {
        #region theme
        static readonly Color CLR_BG       = Color.FromArgb(30, 30, 30);
        static readonly Color CLR_BG2      = Color.FromArgb(42, 42, 42);
        static readonly Color CLR_TEXT     = Color.FromArgb(224, 224, 224);
        static readonly Color CLR_TEXT_DIM = Color.FromArgb(153, 153, 153);
        static readonly Color CLR_ACCENT   = Color.FromArgb(88, 166, 255);
        static readonly Color CLR_GREEN    = Color.FromArgb(63, 185, 80);
        static readonly Color CLR_RED      = Color.FromArgb(248, 81, 73);
        #endregion

        #region fields
        SDCardMonitor? _monitor;
        readonly AutoIngest.Engine.ImageConverter _converter = null!;
        AppConfig _config = null!;

        // Runtime-only UI (not Designer-serialized).
        NotifyIcon trayIcon = null!;
        ContextMenuStrip trayMenu = null!;
        Bitmap? _trayBitmap;

        /// <summary>Auto-hides the toast a few seconds after the last activity.</summary>
        System.Windows.Forms.Timer? _hideTimer;

        /// <summary>True while an import is actively running — keeps the toast pinned open.</summary>
        bool _importing;

        const int HIDE_DELAY_MS = 4000;
        const string APP_NAME = "AutoIngest";
        #endregion

        public MainForm()
        {
            InitializeComponent();

            // Let the WinForms Designer instantiate the form without running
            // runtime-only initialization (config load, monitor, file I/O, etc.).
            if (LicenseManager.UsageMode == LicenseUsageMode.Designtime)
                return;

            _converter = new AutoIngest.Engine.ImageConverter();
            _config = ConfigManager.LoadConfig();

            _hideTimer = new System.Windows.Forms.Timer { Interval = HIDE_DELAY_MS };
            _hideTimer.Tick += (_, _) =>
            {
                _hideTimer.Stop();
                if (!_importing) Hide();
            };

            BuildTrayIcon();
            WireMonitorEvents();

            Shown += (_, _) =>
            {
                // The very first Shown fires after Application.Run creates the form.
                // We want to start hidden, so immediately hide (the form never had to
                // be visible in the first place — but WinForms shows it once on Run).
                Hide();
                StartMonitor();
            };

            FormClosing += MainForm_FormClosing;

            // Start hidden — tray only.
            Visible = false;
            WindowState = FormWindowState.Minimized;
        }

        #region startup / monitor
        void StartMonitor()
        {
            _monitor = new SDCardMonitor(_converter, _config.JpgQuality);
            WireRuntimeMonitorEvents();
            if (_config.AutoImport)
                _monitor.Start();

            Log("AutoIngest ready.", CLR_ACCENT);
            Log("Insert an SD card or USB drive to begin.", CLR_TEXT_DIM);
        }
        #endregion

        #region toast show / hide
        const int TOAST_MARGIN = 12;

        /// <summary>
        /// Positions the toast at the bottom-right of the working area (just above the
        /// taskbar, clear of the screen edges on every monitor / DPI scale) and shows it.
        /// Uses SetBounds so x/y and the rendered size are applied atomically, then clamps
        /// inside the working area so the form is always fully on-screen.
        /// </summary>
        void ShowToast()
        {
            // Make sure the form has a real size before we measure it. On the first show
            // the layout may not have run yet, so force it.
            if (Width == 0 || Height == 0)
            {
                PerformLayout();
                var pref = PreferredSize;
                if (Width == 0) Width = pref.Width;
                if (Height == 0) Height = pref.Height;
            }

            // GetWorkingArea(this) resolves the actual screen the form is on and accounts
            // for the taskbar; fall back to the primary screen's working area.
            var area = Screen.GetWorkingArea(this);
            if (area.IsEmpty) area = Screen.PrimaryScreen?.WorkingArea ?? SystemInformation.WorkingArea;

            int w = Width;
            int h = Height;

            // Target the bottom-right corner with a margin, then clamp into the working
            // area so rounding / DPI scaling can never push part of it off-screen.
            int x = area.Right - w - TOAST_MARGIN;
            int y = area.Bottom - h - TOAST_MARGIN;
            if (x < area.Left + TOAST_MARGIN) x = area.Left + TOAST_MARGIN;
            if (y < area.Top + TOAST_MARGIN) y = area.Top + TOAST_MARGIN;
            if (x + w > area.Right - TOAST_MARGIN) x = area.Right - w - TOAST_MARGIN;
            if (y + h > area.Bottom - TOAST_MARGIN) y = area.Bottom - h - TOAST_MARGIN;

            _hideTimer?.Stop();
            if (!Visible)
            {
                // SetBounds applies position + size atomically before the window is shown,
                // so it appears exactly where we want it with no flicker or repositioning.
                SetBounds(x, y, w, h);
                Show();
                WindowState = FormWindowState.Normal;
            }
            else
            {
                SetBounds(x, y, w, h);
            }
        }

        /// <summary>
        /// Arm the auto-hide timer. Called after any activity; if no further activity
        /// arrives within HIDE_DELAY_MS the toast hides itself (unless an import is
        /// actively running).
        /// </summary>
        void ArmAutoHide()
        {
            _hideTimer?.Stop();
            _hideTimer?.Start();
        }
        #endregion

        #region monitor events
        // Wired at design time (cheap — just lambda closures).
        void WireMonitorEvents() { }

        // Wired once the monitor exists (after StartMonitor creates it).
        void WireRuntimeMonitorEvents()
        {
            var monitor = _monitor!;

            monitor.DriveFound += (driveId, label) =>
            {
                _importing = true;
                this.Invoke(new Action(() =>
                {
                    lblStatus.Text = $"\u25CF  Reading {label}";
                    lblStatus.ForeColor = CLR_ACCENT;
                    ShowToast();
                }));
                Log($"Drive found: {label}", CLR_ACCENT);
            };

            monitor.ImportProgress += (current, total, filename) =>
            {
                this.Invoke(new Action(() =>
                {
                    lblStatus.Text = $"\u25CF  {current}/{total}: {filename}";
                    ShowToast();
                }));
            };

            monitor.ImportDone += (moved, converted, sizeStr, destFolder) =>
            {
                _importing = false;
                this.Invoke(new Action(() =>
                {
                    lblStatus.Text = $"\u2713  Done \u2014 {moved} moved, {converted} converted ({sizeStr})";
                    lblStatus.ForeColor = CLR_GREEN;
                    ShowToast();
                    ArmAutoHide();
                }));
                Log($"Import complete: {moved} moved, {converted} converted, {sizeStr} total -> {destFolder}", CLR_GREEN);
            };

            monitor.ImportError += msg =>
            {
                this.Invoke(new Action(() =>
                {
                    ShowToast();
                    ArmAutoHide();
                }));
                Log($"Error: {msg}", CLR_RED);
            };

            monitor.LogMessage += (msg, color) =>
            {
                var c = ParseHexColor(color) ?? CLR_TEXT;
                Log(msg, c);
            };
        }
        #endregion

        #region log
        void Log(string msg, Color color)
        {
            string line = $"[{DateTime.Now:HH:mm:ss}] {msg}\n";
            if (txtLog.InvokeRequired)
                txtLog.Invoke(new Action(() => AppendLog(line, color)));
            else
                AppendLog(line, color);
        }

        void AppendLog(string text, Color color)
        {
            txtLog.SelectionStart = txtLog.TextLength;
            txtLog.SelectionColor = color;
            txtLog.AppendText(text);
            txtLog.SelectionColor = CLR_TEXT;
            txtLog.ScrollToCaret();
        }

        static Color? ParseHexColor(string hex)
        {
            if (hex.Length < 7 || hex[0] != '#') return null;
            try
            {
                int r = int.Parse(hex[1..3], NumberStyles.HexNumber);
                int g = int.Parse(hex[3..5], NumberStyles.HexNumber);
                int b = int.Parse(hex[5..7], NumberStyles.HexNumber);
                return Color.FromArgb(r, g, b);
            }
            catch (FormatException) { return null; }
            catch (OverflowException) { return null; }
        }
        #endregion

        #region tray icon
        void BuildTrayIcon()
        {
            trayMenu = new ContextMenuStrip
            {
                BackColor = CLR_BG2,
                ForeColor = CLR_TEXT,
                Renderer = new DarkMenuRenderer()
            };

            var showItem = new ToolStripMenuItem("Show status");
            showItem.Click += (_, _) => { ShowToast(); };
            var folderItem = new ToolStripMenuItem("Open import folder");
            folderItem.Click += (_, _) => OpenImportFolder();
            var resetItem = new ToolStripMenuItem("Reset drive memory");
            resetItem.Click += (_, _) =>
            {
                _monitor?.ResetMemory();
                Log("Drive memory cleared. All drives will be re-scanned.", CLR_ACCENT);
            };
            var aboutItem = new ToolStripMenuItem("About");
            aboutItem.Click += (_, _) =>
                MessageBox.Show(
                    APP_NAME + "\n" +
                    "Camera photo importer for ecommerce workflows\n\n" +
                    "Author: Jeremy Anderson\n" +
                    "Email:  info@dcos.net\n" +
                    "Web:    https://dcos.net\n\n" +
                    "C# / .NET 8 / Magick.NET\n\n" +
                    "Licensed under the Microsoft Public License (Ms-PL).\n" +
                    "See https://opensource.org/license/ms-pl-html/",
                    "About " + APP_NAME, MessageBoxButtons.OK, MessageBoxIcon.Information);
            var exitItem = new ToolStripMenuItem("Exit");
            exitItem.Click += (_, _) => ExitApplication();

            trayMenu.Items.AddRange(new ToolStripItem[]
            {
                showItem, folderItem, resetItem,
                new ToolStripSeparator(), aboutItem,
                new ToolStripSeparator(), exitItem
            });

            var bmp = new Bitmap(16, 16);
            _trayBitmap = bmp;
            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);
            }

            trayIcon = new NotifyIcon
            {
                Icon = Icon.FromHandle(bmp.GetHicon()),
                Text = APP_NAME,
                Visible = true,
                ContextMenuStrip = trayMenu
            };
            // Left-click toggles the toast so the user can peek at status on demand.
            trayIcon.MouseClick += (_, e) =>
            {
                if (e.Button == MouseButtons.Left)
                {
                    if (Visible) Hide();
                    else { ShowToast(); ArmAutoHide(); }
                }
            };
            trayIcon.DoubleClick += (_, _) => ShowToast();
        }
        #endregion

        #region helpers
        static void OpenImportFolder()
        {
            string today = DateTime.Now.ToString("yyyy-MM-dd");
            string picturesFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
            string todayFolder = Path.Combine(picturesFolder, today);

            string target = Directory.Exists(todayFolder) ? todayFolder
                       : Directory.Exists(picturesFolder) ? picturesFolder
                       : "";

            if (target != "")
                Process.Start("explorer.exe", target);
            else
                MessageBox.Show("No Pictures folder found.", APP_NAME,
                    MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        void ExitApplication()
        {
            trayIcon.Visible = false;
            _monitor?.Stop();
            Application.Exit();
        }
        #endregion

        #region form events
        // Closing the toast (alt-f4 / clicking it off) should never quit the app —
        // it just hides. The app only exits via the tray's Exit item.
        void MainForm_FormClosing(object? sender, FormClosingEventArgs e)
        {
            if (e.CloseReason == CloseReason.UserClosing ||
                e.CloseReason == CloseReason.FormOwnerClosing)
            {
                e.Cancel = true;
                Hide();
            }
            // Any other close reason (taskmgr, shutdown, Application.Exit) is allowed.
        }
        #endregion
    }

    #region dark menu renderer
    public class DarkMenuRenderer : ToolStripProfessionalRenderer
    {
        public DarkMenuRenderer() : base(new DarkColorTable()) { }
    }

    public class DarkColorTable : ProfessionalColorTable
    {
        public override Color MenuStripGradientBegin => Color.FromArgb(42, 42, 42);
        public override Color MenuStripGradientEnd => Color.FromArgb(42, 42, 42);
        public override Color MenuItemSelected => Color.FromArgb(88, 166, 255);
        public override Color MenuItemBorder => Color.FromArgb(68, 68, 68);
        public override Color MenuBorder => Color.FromArgb(68, 68, 68);
        public override Color SeparatorDark => Color.FromArgb(68, 68, 68);
        public override Color SeparatorLight => Color.FromArgb(68, 68, 68);
        public override Color ImageMarginGradientBegin => Color.FromArgb(42, 42, 42);
        public override Color ImageMarginGradientMiddle => Color.FromArgb(42, 42, 42);
        public override Color ImageMarginGradientEnd => Color.FromArgb(42, 42, 42);
        public override Color ToolStripDropDownBackground => Color.FromArgb(35, 35, 35);
        public override Color ButtonSelectedHighlight => Color.FromArgb(88, 166, 255);
        public override Color ButtonSelectedBorder => Color.FromArgb(88, 166, 255);
        public override Color CheckBackground => Color.FromArgb(42, 42, 42);
        public override Color CheckSelectedBackground => Color.FromArgb(88, 166, 255);
    }
    #endregion
}
