using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;

namespace AutoIngest.Engine
{
    /// <summary>
    /// Mass-storage device source: enumerates SD cards and USB sticks that appear as drive
    /// letters. Implements <see cref="IDeviceSource"/> so the import coordinator can poll it
    /// alongside the MTP source. Each connected drive is surfaced as a <see cref="DeviceIdentity"/>
    /// whose <see cref="DeviceIdentity.Id"/> is built from the volume serial number returned by
    /// GetVolumeInformationW (stable across reboots and drive-letter reassignment) so the device
    /// registry recognizes the same physical card no matter which letter it gets next time.
    ///
    /// The photo-discovery and import logic that used to live here has moved to
    /// <see cref="DeviceImporter"/>; this class is now only responsible for detection and for
    /// producing an <see cref="IPhotoProvider"/> per drive.
    /// </summary>
    public class SDCardMonitor : IDeviceSource
    {
        static readonly HashSet<char> IGNORE_DRIVES = new() { 'C' };

        static readonly string[] SD_KEYWORDS = {
            "SD", "CARD", "CAMERA", "NO NAME", "UNTITLED",
            "CANON", "NIKON", "SONY", "FUJI", "GOPRO",
            "EOS", "LUMIX", "CYBERSHOT", "ALPHA", "PENTAX",
            "OLYMPUS", "LEICA", "HUAWEI", "SAMSUNG", "PIXEL",
            "BLACKMAGIC", "DJI", "INSTA360", "MEMORY", "FLASH"
        };

        static readonly string[] DCIM_PROBES = { "DCIM", "Pictures", "photos", "PICTURES" };

        public string SourceName => "SD/USB";

        public IEnumerable<DeviceIdentity> GetConnectedDevices()
        {
            foreach (var ch in GetCandidateLetters())
            {
                var identity = TryIdentify(ch);
                if (identity != null) yield return identity;
            }
        }

        static IEnumerable<char> GetCandidateLetters()
        {
            return Enumerable.Range(0, 26)
                .Select(i => (char)('A' + i))
                .Where(ch => !IGNORE_DRIVES.Contains(ch))
                .Where(ch =>
                {
                    string root = $"{ch}:\\";
                    if (!Directory.Exists(root)) return false;
                    try
                    {
                        var dt = Win32.GetDriveType(root);
                        if (dt == DriveType.DRIVE_REMOVABLE) return true;
                        if (dt == DriveType.DRIVE_FIXED) return LooksLikeSDCard(ch, out _, out _, out _);
                    }
                    catch (UnauthorizedAccessException) { }
                    catch (IOException) { }
                    return false;
                });
        }

        static DeviceIdentity? TryIdentify(char ch)
        {
            string root = $"{ch}:\\";
            string label = "";
            uint serial = 0;
            bool isRemovable;

            try
            {
                var dt = Win32.GetDriveType(root);
                isRemovable = dt == DriveType.DRIVE_REMOVABLE;

                // LooksLikeSDCard reads label + serial in one shot for fixed drives; for
                // removable drives we still want the label/serial for the registry id, so do
                // the read here too.
                bool looksLikeCamera = false;
                if (!isRemovable)
                    looksLikeCamera = LooksLikeSDCard(ch, out label, out serial, out _);
                else
                    ReadVolumeInfo(root, out label, out serial, out _);

                if (!isRemovable && !looksLikeCamera) return null;
            }
            catch (UnauthorizedAccessException) { return null; }
            catch (IOException) { return null; }

            string kind = isRemovable ? "USB" : "SD";
            string id;
            if (serial != 0)
            {
                id = $"MS:{serial:X8}:{(label ?? "").ToUpperInvariant()}";
            }
            else
            {
                // No serial — rare, but some virtual/removable drives don't expose one. Fall
                // back to a weak id (label + volume size) and flag it so the UI can warn.
                long sizeBytes = TryGetDriveSize(root);
                id = $"WEAK:{(label ?? "").ToUpperInvariant()}:{sizeBytes}";
                kind = "USB-weak";
            }

            string display = string.IsNullOrWhiteSpace(label) ? $"{ch}:\\ ({kind})" : $"{label} ({ch}:)";

            return new DeviceIdentity
            {
                Id = id,
                DisplayName = display,
                Kind = kind,
                Photos = new MassStoragePhotoProvider(root)
            };
        }

        static bool LooksLikeSDCard(char ch, out string label, out uint serial, out string fsName)
        {
            label = ""; serial = 0; fsName = "";
            string root = $"{ch}:\\";

            // Volume-label keyword heuristic, but also surface label/serial for the caller.
            ReadVolumeInfo(root, out label, out serial, out fsName);

            if (DCIM_PROBES.Any(p => Directory.Exists(Path.Combine(root, p))))
                return true;

            string upper = (label ?? "").ToUpperInvariant();
            return SD_KEYWORDS.Any(kw => upper.Contains(kw));
        }

        static void ReadVolumeInfo(string root, out string label, out uint serial, out string fsName)
        {
            label = ""; serial = 0; fsName = "";
            var volName = new System.Text.StringBuilder(1024);
            var fsBuf = new System.Text.StringBuilder(1024);
            if (Win32.GetVolumeInformationW(root, volName, 1024, out serial, out _, out _, fsBuf, 1024))
            {
                label = volName.ToString();
                fsName = fsBuf.ToString();
            }
        }

        static long TryGetDriveSize(string root)
        {
            try
            {
                var drive = new DriveInfo(root);
                return drive.TotalSize;
            }
            catch (UnauthorizedAccessException) { return 0; }
            catch (IOException) { return 0; }
            catch (ArgumentException) { return 0; }
        }

        /// <summary>
        /// Photo provider for a drive-letter root. Enumerates via the shared mass-storage
        /// discovery logic in <see cref="DeviceImporter"/>; removal is File.Delete (the
        /// importer has already copied the file to its destination before calling this).
        /// </summary>
        sealed class MassStoragePhotoProvider : IPhotoProvider
        {
            readonly string _root;
            public MassStoragePhotoProvider(string root) { _root = root; }

            public List<string> EnumeratePhotos() => DeviceImporter.FindPhotosOnRoot(_root);

            public bool RemoveSource(string path)
            {
                try { if (File.Exists(path)) { File.Delete(path); return true; } }
                catch (UnauthorizedAccessException) { }
                catch (IOException) { }
                return false;
            }
        }
    }

    public enum DriveType : uint
    {
        DRIVE_UNKNOWN = 0,
        DRIVE_NO_ROOT_DIR = 1,
        DRIVE_REMOVABLE = 2,
        DRIVE_FIXED = 3,
        DRIVE_REMOTE = 4,
        DRIVE_CDROM = 5,
        DRIVE_RAMDISK = 6
    }

    public static class Win32
    {
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern DriveType GetDriveType(string lpRootPathName);

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool GetVolumeInformationW(
            string rootPathName,
            System.Text.StringBuilder volumeNameBuffer,
            int volumeNameSize,
            out uint volumeSerialNumber,
            out uint maximumComponentLength,
            out uint fileSystemFlags,
            System.Text.StringBuilder fileSystemNameBuffer,
            int fileSystemNameSize);
    }
}
