#nullable enable
using IPA.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace IPA.AntiMalware
{
    internal class WindowsWin32AntiMalware : IAntiMalware, IDisposable
    {
        internal static WindowsWin32AntiMalware? TryInitialize()
        {
            try
            {
                return new();
            }
            catch (Exception e)
            {
                Logger.AntiMalware.Warn("Could not initialize Win32-based antimalware engine:");
                Logger.AntiMalware.Warn(e);
                return null;
            }
        }

        private readonly IntPtr handle;
        private bool disposedValue;

        private WindowsWin32AntiMalware()
        {
            AmsiInitialize(AmsiConstants.AppName, out handle);
        }

        private static ScanResult ScanResultFromAmsiResult(AmsiResult result)
            => result switch
            {
                AmsiResult.Clean => ScanResult.KnownSafe,
                AmsiResult.NotDetected => ScanResult.NotDetected,
                AmsiResult.Detected => ScanResult.Detected,
                _ => ScanResult.MaybeMalware
            };

        public ScanResult ScanFile(FileInfo file)
        {
            var data = File.ReadAllBytes(file.FullName);
            return ScanData(data, file.FullName);
        }

        public ScanResult ScanData(byte[] data, string? contentName = null)
        {
            contentName ??= $"unknown_data_{Guid.NewGuid()}";

            AmsiScanBuffer(handle, data, (uint)data.Length, contentName, IntPtr.Zero, out var result);

            Logger.AntiMalware.Trace($"Scanned data named '{contentName}' and got '{result}'");
            return ScanResultFromAmsiResult(result);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // we have no disposable managed state
                }

                AmsiUninitialize(handle);
                disposedValue = true;
            }
        }

        ~WindowsWin32AntiMalware()
        {
            // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
            Dispose(disposing: false);
        }

        public void Dispose()
        {
            // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }

        [DllImport("amsi", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true)]
#if !NET35
        [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
#endif
        private static extern void AmsiInitialize([MarshalAs(UnmanagedType.LPWStr)] string appName, [Out] out IntPtr handle);

        [DllImport("amsi", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true)]
#if !NET35
        [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
#endif
        private static extern void AmsiUninitialize(IntPtr handle);

        [DllImport("amsi", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true)]
#if !NET35
        [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
#endif
        private static extern void AmsiScanBuffer(IntPtr context,
            [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] byte[] buffer, uint length,
            [MarshalAs(UnmanagedType.LPWStr)] string contentName, 
            IntPtr session, 
            [Out] out AmsiResult result);
    }
}
