#nullable enable
using IPA.AntiMalware.ComAPI;
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 WindowsCOMAntiMalware : IAntiMalware
    {
        internal static WindowsCOMAntiMalware? TryInitialize()
        {
            // Mono's COM interop *fundamentally doesn't work.*
            // End of story.
#if false
            try
            {
                return new();
            }
            catch (Exception e)
            {
                Logger.AntiMalware.Warn("Could not initialize COM-based antimalware engine:");
                Logger.AntiMalware.Warn(e);
            }
#endif
            return null;
        }

        private readonly IAntimalware amInterface;

        private WindowsCOMAntiMalware()
        {
            var hr = CoCreateInstanceAM(AmsiConstants.CAntimalwareGuid,
                null,
                0x1 | 0x4 /* inproc server, local server */,
                AmsiConstants.IAntimalwareGuid,
                out var antimalware);
            Marshal.ThrowExceptionForHR(hr);

            amInterface = antimalware;
        }

        [DllImport("ole32",
            CallingConvention = CallingConvention.Winapi,
            ExactSpelling = true,
            PreserveSig = false,
            EntryPoint = "CoCreateInstance")]
        [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
        private static extern int CoCreateInstanceAM(
            [In] in Guid clsid,
            [In, MarshalAs(UnmanagedType.Interface)] object? unkOuter,
            [In] int dwClsContext,
            [In] in Guid iid,
            [Out, MarshalAs(UnmanagedType.Interface)] out IAntimalware @interface);


        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)
        {
            using var stream = new AmsiFileStream(file, IntPtr.Zero);
            amInterface.Scan(stream, out var result, out var provider);
            Logger.AntiMalware.Trace($"Scanned file '{file}' with {provider.DisplayName()}, and got '{result}'");
            return ScanResultFromAmsiResult(result);
        }

        public ScanResult ScanData(byte[] data, string? contentName = null)
        {
            contentName ??= $"unknown_data_{Guid.NewGuid()}";
            using var stream = new AmsiMemoryStream(contentName, data, IntPtr.Zero);
            amInterface.Scan(stream, out var result, out var provider);
            Logger.AntiMalware.Trace($"Scanned data named '{contentName}' with {provider.DisplayName()}, and got '{result}'");
            return ScanResultFromAmsiResult(result);
        }
    }
}
