using System;
using System.IO;
using System.Runtime.InteropServices;

namespace AutoIngest.App
{
    /// <summary>
    /// Self-install + autostart, the classic Windows way: copy the running exe to a stable
    /// per-user home and drop a <c>.lnk</c> in the user's Startup folder so Explorer launches it
    /// at login. No admin rights, no UAC, no registry — both
    /// <c>%LocalAppData%\Programs\AutoIngest\</c> and <c>shell:startup</c> are user-writable.
    ///
    /// The modern-correct target for the exe is <c>%LocalAppData%\Programs\</c> (this is where
    /// VS Code, Discord, and other per-user installers live). The old-school
    /// <c>%SystemRoot%\</c> (<c>C:\Windows\</c>) target is deliberately NOT used: it's admin-
    /// owned, write-protected, semantically "part of the OS," more heavily scrutinized by
    /// Defender, and can be wiped by a Windows reset. LocalAppData achieves everything the
    /// Win9x system-root copy did, without those costs.
    ///
    /// The shortcut itself is the persisted "enabled" state — <see cref="IsEnabled"/> is just
    /// File.Exists on the Startup link — so there's no config flag to drift out of sync with
    /// reality (e.g. if the user manually deletes the shortcut, the toggle reflects that).
    /// </summary>
    public static class AutostartManager
    {
        /// <summary>Stable per-user home for the installed exe.</summary>
        public static string StableHome =>
            Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                "Programs", "AutoIngest");

        /// <summary>Full path of the installed exe in the stable home.</summary>
        public static string StableExe => Path.Combine(StableHome, "AutoIngest.exe");

        /// <summary>The <c>.lnk</c> in the user's Startup folder that triggers login launch.</summary>
        public static string StartupLink
        {
            get
            {
                string startupFolder = Environment.GetFolderPath(Environment.SpecialFolder.Startup);
                return Path.Combine(startupFolder, "AutoIngest.lnk");
            }
        }

        /// <summary>True when autostart is active (the Startup shortcut exists).</summary>
        public static bool IsEnabled => File.Exists(StartupLink);

        /// <summary>True when the currently-running process IS the stable-home copy.</summary>
        public static bool IsRunningFromStableHome =>
            string.Equals(Environment.ProcessPath, StableExe,
                StringComparison.OrdinalIgnoreCase);

        /// <summary>
        /// Installs the exe to <see cref="StableHome"/> and creates the Startup shortcut. Safe to
        /// call repeatedly — skips the copy if already running from the stable home, and refreshes
        /// the copy if the running exe is newer/different. Returns true if the exe was (re)copied
        /// this call, false if the stable copy was already current. Throws on I/O failure so the UI
        /// can surface the error and revert the toggle.
        /// </summary>
        public static bool Enable()
        {
            Directory.CreateDirectory(StableHome);

            string? runningPath = Environment.ProcessPath;
            bool copied = false;

            if (!string.IsNullOrEmpty(runningPath) &&
                !IsRunningFromStableHome)
            {
                // Copy the running exe to the stable home, refreshing if it's stale. Copying the
                // running image *to a different path* is fine on Windows (the exe is opened with
                // read/share); we never copy *over* the running path.
                if (NeedsRefresh(runningPath, StableExe))
                {
                    File.Copy(runningPath, StableExe, overwrite: true);
                    copied = true;
                }
            }

            // (Re)create the Startup shortcut pointing at the stable exe.
            CreateShortcut(StartupLink, StableExe, StableHome, "AutoIngest photo importer");
            return copied;
        }

        /// <summary>Removes the Startup shortcut. Leaves the stable-home exe in place so the user
        /// can re-enable without a re-copy. No-op if already disabled.</summary>
        public static void Disable()
        {
            if (File.Exists(StartupLink))
                File.Delete(StartupLink);
        }

        /// <summary>
        /// True if the destination doesn't exist or differs from the source by size or write time.
        /// Used to decide whether to re-copy the exe on re-enable (e.g. after the user updates).
        /// </summary>
        static bool NeedsRefresh(string source, string dest)
        {
            if (!File.Exists(dest)) return true;
            try
            {
                var s = new FileInfo(source);
                var d = new FileInfo(dest);
                return s.Length != d.Length
                    || s.LastWriteTimeUtc > d.LastWriteTimeUtc.AddSeconds(1);
            }
            catch { return true; }
        }

        // ---- .lnk creation via the raw IShellLinkW COM interface (no IWshRuntimeLibrary) ----
        // This is the old-school way: CoCreateInstance the Shell.Link object, set path/working dir
        // through IShellLinkW, then persist via IPersistFile. Self-contained, no extra reference.

        static void CreateShortcut(string linkPath, string targetPath, string workingDir, string description)
        {
            // CLSID_ShellLink — the Shell Link object. CoCreateInstance gives us an object that
            // implements both IShellLinkW (for setting target/working dir) and IPersistFile (for
            // saving the .lnk). We QI for IUnknown and cast to each interface.
            var clsid = new Guid("00021401-0000-0000-C000-000000000046");
            var iidUnknown = new Guid("00000000-0000-0000-C000-000000000046"); // IID_IUnknown

            int hr = CoCreateInstance(ref clsid, IntPtr.Zero,
                1 /*CLSCTX_INPROC_SERVER*/, ref iidUnknown, out object obj);
            if (hr != 0)
                Marshal.ThrowExceptionForHR(hr);

            try
            {
                var shellLink = (IShellLinkW)obj;
                shellLink.SetPath(targetPath);
                shellLink.SetWorkingDirectory(workingDir);
                shellLink.SetDescription(description);

                // Save via IPersistFile. remember=1 lets the shell track the link (so it can
                // heal the target path if the exe moves later).
                var persistFile = (IPersistFile)obj;
                persistFile.Save(linkPath, remember: 1);
            }
            finally
            {
                Marshal.ReleaseComObject(obj);
            }
        }

        [DllImport("ole32.dll")]
        static extern int CoCreateInstance(
            [In] ref Guid rclsid,
            IntPtr pUnkOuter,
            int dwClsContext,
            [In] ref Guid riid,
            [MarshalAs(UnmanagedType.Interface)] out object ppv);

        // Minimal IShellLinkW — only the methods we call. vtable order matters; the preceding
        // HRESULT-returning methods are declared with PreserveSig so they stay in slot order
        // without us needing to handle their results.
        [ComImport]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        [Guid("000214F9-0000-0000-C000-000000000046")]
        interface IShellLinkW
        {
            void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] System.Text.StringBuilder pszFile,
                         int cch, IntPtr pfd, uint fFlags);
            void GetIDList(out IntPtr ppidl);
            void SetIDList(IntPtr pidl);
            void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] System.Text.StringBuilder pszName, int cch);
            void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
            void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] System.Text.StringBuilder pszDir, int cch);
            void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
            void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] System.Text.StringBuilder pszArgs, int cch);
            void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
            void GetHotkey(out ushort pwHotkey);
            void SetHotkey(ushort wHotkey);
            void GetShowCmd(out int piShowCmd);
            void SetShowCmd(int iShowCmd);
            void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] System.Text.StringBuilder pszIconPath,
                                 int cch, out int piIcon);
            void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
            void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, uint dwReserved);
            void Resolve(IntPtr hwnd, uint fFlags);
            void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
        }

        [ComImport]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        [Guid("0000010B-0000-0000-C000-000000000046")]
        interface IPersistFile
        {
            void GetClassID(out Guid pClassID);
            void IsDirty();
            void Load([MarshalAs(UnmanagedType.LPWStr)] string pszFileName, uint dwMode);
            void Save([MarshalAs(UnmanagedType.LPWStr)] string pszFileName, int remember);
            void SaveCompleted([MarshalAs(UnmanagedType.LPWStr)] string pszFileName);
            void GetCurFile([MarshalAs(UnmanagedType.LPWStr)] out string ppszFileName);
        }
    }
}
