using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using MarcusLaw.OutlookAddin.Core.Models;
using MarcusLaw.OutlookAddin.Core.Services;
using Serilog;
using Outlook = Microsoft.Office.Interop.Outlook;

namespace OutlookAddin.Services
{
    /// <summary>
    /// Drives the "Compose from Case" flow:
    /// 1. Caller opens a MailItem (either via Inspector ribbon or by us
    ///    creating a brand new MailItem via the URL protocol).
    /// 2. Resolve full Case details (name, number, contacts) and the primary
    ///    contact's email address.
    /// 3. Populate the MailItem (To/Subject/footer) and stamp two
    ///    UserProperties: <c>EspoCaseId</c> and <c>OutlookAddinGuid</c>.
    /// 4. Display the MailItem so the user can edit/send.
    /// </summary>
    public sealed class ComposeService
    {
        private const string EspoCaseIdProperty = "EspoCaseId";
        private const string EspoCaseNameProperty = "EspoCaseName";
        private const string AddInGuidProperty = "OutlookAddinGuid";

        private readonly IEspoCrmClient _client;
        private readonly ILogger _logger;

        public ComposeService(IEspoCrmClient client, ILogger logger)
        {
            _client = client ?? throw new ArgumentNullException(nameof(client));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        public async Task<ComposeResult> ApplyCaseToMailAsync(
            Outlook.MailItem mail,
            string caseId,
            string baseCrmUrl)
        {
            if (mail == null) throw new ArgumentNullException(nameof(mail));
            if (string.IsNullOrWhiteSpace(caseId)) throw new ArgumentException("caseId required", nameof(caseId));

            var caseEntity = await _client.GetCaseAsync(caseId, "id,name,number,status,contactsIds,accountId").ConfigureAwait(true);
            ContactEntity? primaryContact = null;
            if (caseEntity.ContactsIds != null && caseEntity.ContactsIds.Count > 0)
            {
                try
                {
                    primaryContact = await _client.GetContactAsync(
                        caseEntity.ContactsIds[0],
                        "id,name,emailAddress",
                        default).ConfigureAwait(true);
                }
                catch (Exception ex)
                {
                    _logger.Warning(ex, "ComposeService: failed to load primary contact for case {CaseId}", caseId);
                }
            }

            var guid = Guid.NewGuid().ToString("N");
            StampUserProperty(mail, EspoCaseIdProperty, caseEntity.Id);
            StampUserProperty(mail, EspoCaseNameProperty, caseEntity.Name ?? caseEntity.Number ?? caseEntity.Id);
            StampUserProperty(mail, AddInGuidProperty, guid);

            try
            {
                if (!string.IsNullOrWhiteSpace(primaryContact?.EmailAddress))
                {
                    var existingTo = mail.To ?? string.Empty;
                    if (!existingTo.Contains(primaryContact!.EmailAddress!))
                    {
                        mail.To = string.IsNullOrEmpty(existingTo)
                            ? primaryContact.EmailAddress
                            : existingTo + "; " + primaryContact.EmailAddress;
                    }
                }

                var caseLabel = caseEntity.Number ?? caseEntity.Name ?? caseEntity.Id;
                var existingSubject = mail.Subject ?? string.Empty;
                if (!existingSubject.Contains("[" + caseLabel + "]"))
                {
                    mail.Subject = "[" + caseLabel + "] " + existingSubject;
                }

                var crmCaseUrl = baseCrmUrl?.TrimEnd('/') + "/#Case/view/" + caseEntity.Id;
                var footer =
                    "<br/><hr/><p style=\"direction:rtl;font-size:11px;color:#666;\">" +
                    "תיק EspoCRM: " +
                    "<a href=\"" + crmCaseUrl + "\">" + System.Net.WebUtility.HtmlEncode(caseLabel) + "</a>" +
                    "</p>";
                var html = mail.HTMLBody ?? string.Empty;
                if (!html.Contains("#Case/view/" + caseEntity.Id))
                {
                    mail.HTMLBody = html + footer;
                }
            }
            catch (Exception ex)
            {
                _logger.Warning(ex, "ComposeService: failed to populate MailItem for case {CaseId}", caseId);
            }

            try { mail.Save(); } catch { /* ignore */ }

            return new ComposeResult(
                guid,
                new FilingTarget("Case", caseEntity.Id, caseEntity.Name ?? caseEntity.Id));
        }

        public static string? ReadEspoCaseId(Outlook.MailItem mail) => TryReadUserProperty(mail, EspoCaseIdProperty);
        public static string? ReadEspoCaseName(Outlook.MailItem mail) => TryReadUserProperty(mail, EspoCaseNameProperty);
        public static string? ReadAddInGuid(Outlook.MailItem mail) => TryReadUserProperty(mail, AddInGuidProperty);

        private static void StampUserProperty(Outlook.MailItem mail, string name, string value)
        {
            Outlook.UserProperties? props = null;
            Outlook.UserProperty? prop = null;
            try
            {
                props = mail.UserProperties;
                prop = props.Find(name) ?? props.Add(name, Outlook.OlUserPropertyType.olText, false);
                prop.Value = value;
            }
            catch { /* ignore */ }
            finally
            {
                if (prop != null && Marshal.IsComObject(prop)) Marshal.ReleaseComObject(prop);
                if (props != null && Marshal.IsComObject(props)) Marshal.ReleaseComObject(props);
            }
        }

        private static string? TryReadUserProperty(Outlook.MailItem mail, string name)
        {
            Outlook.UserProperties? props = null;
            Outlook.UserProperty? prop = null;
            try
            {
                props = mail.UserProperties;
                prop = props?.Find(name);
                return prop?.Value as string;
            }
            catch { return null; }
            finally
            {
                if (prop != null && Marshal.IsComObject(prop)) Marshal.ReleaseComObject(prop);
                if (props != null && Marshal.IsComObject(props)) Marshal.ReleaseComObject(props);
            }
        }
    }

    public sealed class ComposeResult
    {
        public string Guid { get; }
        public FilingTarget Target { get; }

        public ComposeResult(string guid, FilingTarget target)
        {
            Guid = guid;
            Target = target;
        }
    }
}
