using System;
using System.IO;
using System.Runtime.InteropServices;
using MarcusLaw.OutlookAddin.Core.Models;
using Outlook = Microsoft.Office.Interop.Outlook;

namespace OutlookAddin.Services
{
    /// <summary>
    /// Converts an Outlook MailItem (live COM object) into a plain
    /// <see cref="MailEnvelope"/> on the STA thread. Must run on the UI thread
    /// — never pass the MailItem itself to background work.
    /// </summary>
    public static class MailItemExtractor
    {
        // PR_INTERNET_MESSAGE_ID
        private const string InternetMessageIdTag =
            "http://schemas.microsoft.com/mapi/proptag/0x1035001F";

        public static MailEnvelope Extract(Outlook.MailItem item)
        {
            if (item == null) throw new ArgumentNullException(nameof(item));

            var hasHtml = !string.IsNullOrEmpty(item.HTMLBody);
            var env = new MailEnvelope
            {
                Token = SafeGet(() => item.EntryID),
                Subject = SafeGet(() => item.Subject) ?? string.Empty,
                From = ResolveFromAddress(item),
                Body = hasHtml ? item.HTMLBody : (SafeGet(() => item.Body) ?? string.Empty),
                IsHtml = hasHtml,
                DateSent = ResolveDateSent(item),
                MessageId = ResolveMessageId(item)
            };

            ExtractRecipients(item, env);
            ExtractAttachments(item, env);

            return env;
        }

        private static void ExtractRecipients(Outlook.MailItem item, MailEnvelope env)
        {
            Outlook.Recipients? recipients = null;
            try
            {
                recipients = item.Recipients;
                if (recipients == null) return;
                for (int i = 1; i <= recipients.Count; i++)
                {
                    var r = recipients[i];
                    try
                    {
                        var address = r.Address ?? r.Name ?? string.Empty;
                        if (string.IsNullOrWhiteSpace(address)) continue;
                        switch ((Outlook.OlMailRecipientType)r.Type)
                        {
                            case Outlook.OlMailRecipientType.olTo:
                                env.To.Add(address);
                                break;
                            case Outlook.OlMailRecipientType.olCC:
                                env.Cc.Add(address);
                                break;
                            case Outlook.OlMailRecipientType.olBCC:
                                env.Bcc.Add(address);
                                break;
                        }
                    }
                    finally
                    {
                        Release(r);
                    }
                }
            }
            finally
            {
                Release(recipients);
            }
        }

        private static void ExtractAttachments(Outlook.MailItem item, MailEnvelope env)
        {
            Outlook.Attachments? attachments = null;
            try
            {
                attachments = item.Attachments;
                if (attachments == null) return;
                for (int i = 1; i <= attachments.Count; i++)
                {
                    var att = attachments[i];
                    try
                    {
                        if (att.Type != Outlook.OlAttachmentType.olByValue) continue;
                        var tempPath = Path.Combine(
                            Path.GetTempPath(),
                            Guid.NewGuid().ToString("N") + "_" + SanitizeFileName(att.FileName));
                        try
                        {
                            att.SaveAsFile(tempPath);
                            var bytes = File.ReadAllBytes(tempPath);
                            var name = att.FileName ?? "attachment.bin";
                            env.Attachments.Add(new EspoAttachment
                            {
                                Name = name,
                                ContentType = GuessMimeType(name),
                                ContentBase64 = Convert.ToBase64String(bytes)
                            });
                        }
                        finally
                        {
                            try { if (File.Exists(tempPath)) File.Delete(tempPath); } catch { /* ignore */ }
                        }
                    }
                    finally
                    {
                        Release(att);
                    }
                }
            }
            finally
            {
                Release(attachments);
            }
        }

        private static string ResolveFromAddress(Outlook.MailItem item)
        {
            var smtp = SafeGet(() => item.SenderEmailAddress);
            if (!string.IsNullOrWhiteSpace(smtp) && smtp.Contains("@"))
            {
                return smtp;
            }
            // Exchange addresses look like /O=ORG/OU=…/CN=… — fall back to the sender name.
            var name = SafeGet(() => item.SenderName);
            return string.IsNullOrWhiteSpace(name) ? (smtp ?? string.Empty) : name;
        }

        private static string? ResolveMessageId(Outlook.MailItem item)
        {
            try
            {
                var accessor = item.PropertyAccessor;
                if (accessor == null) return null;
                return accessor.GetProperty(InternetMessageIdTag) as string;
            }
            catch
            {
                return null;
            }
        }

        private static DateTimeOffset ResolveDateSent(Outlook.MailItem item)
        {
            try
            {
                var sent = item.SentOn;
                if (sent != DateTime.MinValue)
                {
                    return new DateTimeOffset(sent.ToUniversalTime(), TimeSpan.Zero);
                }
            }
            catch { /* ignore */ }
            return DateTimeOffset.UtcNow;
        }

        private static string SanitizeFileName(string? name)
        {
            if (string.IsNullOrWhiteSpace(name)) return "attachment.bin";
            foreach (var c in Path.GetInvalidFileNameChars())
            {
                name = name.Replace(c, '_');
            }
            return name;
        }

        /// <summary>
        /// Picks a sane MIME type from the file extension. EspoCRM's default
        /// attachment-upload config rejects "application/octet-stream" with
        /// "Not allowed file type", so we map common Office / image / archive
        /// extensions to canonical MIMEs before posting.
        /// </summary>
        private static string GuessMimeType(string fileName)
        {
            if (string.IsNullOrWhiteSpace(fileName)) return "application/octet-stream";
            var ext = Path.GetExtension(fileName).ToLowerInvariant();
            switch (ext)
            {
                // Office (legacy + OOXML)
                case ".pdf":  return "application/pdf";
                case ".doc":  return "application/msword";
                case ".docx": return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
                case ".xls":  return "application/vnd.ms-excel";
                case ".xlsx": return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
                case ".ppt":  return "application/vnd.ms-powerpoint";
                case ".pptx": return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
                case ".rtf":  return "application/rtf";
                case ".odt":  return "application/vnd.oasis.opendocument.text";
                case ".ods":  return "application/vnd.oasis.opendocument.spreadsheet";

                // Text
                case ".txt":  return "text/plain";
                case ".csv":  return "text/csv";
                case ".log":  return "text/plain";
                case ".htm":
                case ".html": return "text/html";
                case ".xml":  return "application/xml";
                case ".json": return "application/json";

                // Images
                case ".png":  return "image/png";
                case ".jpg":
                case ".jpeg": return "image/jpeg";
                case ".gif":  return "image/gif";
                case ".bmp":  return "image/bmp";
                case ".tif":
                case ".tiff": return "image/tiff";
                case ".webp": return "image/webp";
                case ".svg":  return "image/svg+xml";

                // Archives
                case ".zip":  return "application/zip";
                case ".rar":  return "application/x-rar-compressed";
                case ".7z":   return "application/x-7z-compressed";
                case ".gz":   return "application/gzip";
                case ".tar":  return "application/x-tar";

                // Email
                case ".eml":  return "message/rfc822";
                case ".msg":  return "application/vnd.ms-outlook";

                // Audio / video — rare in legal mail but cheap to handle
                case ".mp3":  return "audio/mpeg";
                case ".wav":  return "audio/wav";
                case ".mp4":  return "video/mp4";

                default:      return "application/octet-stream";
            }
        }

        private static T? SafeGet<T>(Func<T> getter) where T : class
        {
            try { return getter(); } catch { return null; }
        }

        private static void Release(object? com)
        {
            if (com != null && Marshal.IsComObject(com))
            {
                try { Marshal.ReleaseComObject(com); } catch { /* ignore */ }
            }
        }
    }
}
