using System.Runtime.InteropServices;
using System.Text;
using NexusShell.Models;

namespace NexusShell.Services;

public sealed class NexusPrinterInfoService
{
    private const int PrinterEnumLocal = 0x00000002;
    private const int Level2 = 2;

    public IReadOnlyList<NexusPrinterInfo> GetInstalledPrinters()
    {
        var printers = new List<NexusPrinterInfo>();
        uint needed = 0;
        uint returned = 0;
        EnumPrinters(PrinterEnumLocal, null, Level2, IntPtr.Zero, 0, ref needed, ref returned);
        if (needed == 0)
        {
            return printers;
        }

        var buffer = Marshal.AllocHGlobal((int)needed);
        try
        {
            if (!EnumPrinters(PrinterEnumLocal, null, Level2, buffer, needed, ref needed, ref returned))
            {
                return printers;
            }

            var defaultName = GetDefaultPrinterName();
            var stride = Marshal.SizeOf<PrinterInfo2>();
            for (var i = 0; i < returned; i++)
            {
                var info = Marshal.PtrToStructure<PrinterInfo2>(buffer + (i * stride));
                var name = info.pPrinterName ?? "Ukendt printer";
                printers.Add(new NexusPrinterInfo
                {
                    Name = name,
                    Status = DescribeStatus(info.Status),
                    IsDefault = string.Equals(name, defaultName, StringComparison.OrdinalIgnoreCase),
                });
            }
        }
        finally
        {
            Marshal.FreeHGlobal(buffer);
        }

        return printers
            .OrderByDescending(static p => p.IsDefault)
            .ThenBy(static p => p.Name, StringComparer.OrdinalIgnoreCase)
            .ToList();
    }

    private static string? GetDefaultPrinterName()
    {
        var size = 0;
        GetDefaultPrinter(null!, ref size);
        if (size <= 0)
        {
            return null;
        }

        var buffer = new StringBuilder(size);
        return GetDefaultPrinter(buffer, ref size) ? buffer.ToString().TrimEnd('\0') : null;
    }

    private static string DescribeStatus(uint status)
    {
        if (status == 0)
        {
            return "Klar";
        }

        var parts = new List<string>();
        if ((status & 0x00000001) != 0) parts.Add("Pauset");
        if ((status & 0x00000002) != 0) parts.Add("Fejl");
        if ((status & 0x00000008) != 0) parts.Add("Offline");
        if ((status & 0x00000080) != 0) parts.Add("Ikke tilgængelig");
        return parts.Count == 0 ? "Status ukendt" : string.Join(", ", parts);
    }

    [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool EnumPrinters(
        int flags,
        string? name,
        int level,
        IntPtr pPrinterEnum,
        uint cbBuf,
        ref uint pcbNeeded,
        ref uint pcReturned);

    [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool GetDefaultPrinter(StringBuilder? pszBuffer, ref int pcchBuffer);

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    private struct PrinterInfo2
    {
        public string? pServerName;
        public string? pPrinterName;
        public string? pShareName;
        public string? pPortName;
        public string? pDriverName;
        public string? pComment;
        public string? pLocation;
        public IntPtr pDevMode;
        public string? pSepFile;
        public string? pPrintProcessor;
        public string? pDatatype;
        public string? pParameters;
        public IntPtr pSecurityDescriptor;
        public uint Attributes;
        public uint Priority;
        public uint DefaultPriority;
        public uint StartTime;
        public uint UntilTime;
        public uint Status;
        public uint cJobs;
        public uint AveragePPM;
    }
}
