using InABox.Core;
using MailKit;
using MailKit.Net.Imap;
using MailKit.Net.Smtp;
using MimeKit;

namespace InABox.Mail
{
    public class IMAPMailer : CoreMailer<IMAPMailFolder, IMAPMailSummary, IMAPMailMessage>
    {
        private static ProtocolLogger imaplog;
        private static ProtocolLogger smtplog;
        private ImapClient _client;

        protected override void Initialize()
        {
            if (imaplog == null)
                imaplog = new ProtocolLogger(Path.Combine(CoreUtils.GetPath(), "imap.log"));
            _client = new ImapClient(imaplog);
            _client.ServerCertificateValidationCallback = (s, c, h, e) => true;
        }

        public override void Dispose()
        {
            if (_client.IsConnected)
                _client.Disconnect(true);
            _client.Dispose();
            _client = null;
        }


        protected override bool DoConnect()
        {
            try
            {
                if (_client.IsConnected)
                    return true;
                _client.Connect(MailboxHost, MailboxPort == 0 ? 993 : MailboxPort, MailboxPort != 143);
                _client.Authenticate(MailboxUserName, MailboxPassword);
                return true;
            }
            catch (Exception e)
            {
                return false;
            }
        }

        protected override bool GetIsConnected()
        {
            return _client.IsConnected;
        }

        protected override IMAPMailMessage DoCreateMessage()
        {
            return new IMAPMailMessage(null, UniqueId.MinValue, new MimeMessage());
        }

        protected override IMAPMailFolder DoFindFolder(IMAPMailFolder parent, string name)
        {
            if (string.IsNullOrEmpty(name))
                return parent;
            return new IMAPMailFolder(parent.Folder.GetSubfolder(name));
        }

        protected override IMAPMailFolder GetInbox()
        {
            return new IMAPMailFolder(_client.Inbox);
        }

        protected override IMAPMailFolder GetSentItems()
        {
            if ((_client.Capabilities & (ImapCapabilities.SpecialUse | ImapCapabilities.XList)) != 0)
                return new IMAPMailFolder(_client.GetFolder(SpecialFolder.Sent));

            string[] CommonSentFolderNames = { "Sent", "Sent Items", "Sent Mail", "Sent Messages" /* maybe add some translated names */ };
            var personal = _client.GetFolder(_client.PersonalNamespaces[0]);
            var folder = personal.GetSubfolders().FirstOrDefault(x => CommonSentFolderNames.Contains(x.Name));
            if (folder != null)
                return new IMAPMailFolder(folder);
            throw new Exception("Cannot find Sent Items Folder!");
        }

        protected override IMAPMailMessage DoGetMessage(IMAPMailFolder folder, string id)
        {
            var fld = folder != null ? folder.Folder : _client.Inbox;
            if (!fld.IsOpen)
                fld.Open(FolderAccess.ReadWrite);
            var uid = UniqueId.Parse(id);
            var msg = fld.GetMessage(uid);
            return new IMAPMailMessage(fld, uid, msg);
        }

        protected override IEnumerable<IMAPMailMessage> DoGetMessages(IMAPMailFolder folder)
        {
            var results = new List<IMAPMailMessage>();
            var fld = folder != null ? folder.Folder : _client.Inbox;
            if (!fld.IsOpen)
                fld.Open(FolderAccess.ReadWrite);
            var summaries = fld.Fetch(0, -1, MessageSummaryItems.UniqueId | MessageSummaryItems.Flags);
            foreach (var summary in summaries)
                if (!summary.Flags.HasValue || !summary.Flags.Value.HasFlag(MessageFlags.Deleted))
                {
                    var msg = fld.GetMessage(summary.UniqueId);
                    results.Add(new IMAPMailMessage(fld, summary.UniqueId, msg));
                }

            return results;
        }

        protected override IEnumerable<IMAPMailSummary> DoListMessages(IMAPMailFolder folder, int min = 0, int max = int.MaxValue)
        {
            var fld = folder != null ? folder.Folder : _client.Inbox;
            if (!fld.IsOpen)
                fld.Open(FolderAccess.ReadOnly);
            var summaries = fld.Fetch(min, max, MessageSummaryItems.UniqueId | MessageSummaryItems.Flags | MessageSummaryItems.Envelope);
            return summaries.Where(x => !x.Flags.Value.HasFlag(MessageFlags.Deleted)).Select(x => new IMAPMailSummary(fld, x));
        }

        protected override bool DoMoveMessage(IMAPMailMessage message, IMAPMailFolder to)
        {
            message.Move(to != null ? to.Folder : _client.Inbox);
            return true;
        }

        private bool UsesSSL(int port)
        {
            return port != 25;
        }


        protected override bool DoSendMessage(IMAPMailMessage message)
        {
            if (smtplog == null)
                smtplog = new ProtocolLogger(Path.Combine(CoreUtils.GetPath(), "smtp.log"));
            using (var smtp = new SmtpClient(smtplog))
            {
                smtp.ServerCertificateValidationCallback = (s, c, h, e) => true;
                smtp.Connect(SMTPHost, SMTPPort == 0 ? 587 : SMTPPort, SMTPPort == 465);
                smtp.Authenticate(SMTPUserName, SMTPPassword);
                smtp.Send(message.Message);

                if ((_client.Capabilities & (ImapCapabilities.SpecialUse | ImapCapabilities.XList)) != 0)
                {
                    var sent = _client.GetFolder(SpecialFolder.Sent);
                    sent.Open(FolderAccess.ReadWrite);
                    var id = sent.Append(message.Message).Value;
                    sent.SetFlags(id, MessageFlags.Seen, true);
                }

                smtp.Disconnect(true);
            }

            return true;
        }

        protected override bool DoSaveDraft(IMAPMailMessage message)
        {
            var draftFolder = _client.GetFolder(SpecialFolder.Drafts);
            if (draftFolder == null)
            {
                var toplevel = _client.GetFolder(_client.PersonalNamespaces[0]);
                draftFolder = toplevel.Create(SpecialFolder.Drafts.ToString(), true);
            }

            message.Move(draftFolder);
            draftFolder.SetFlags(message.UniqueID, MessageFlags.Draft, true);
            draftFolder.Expunge();
            return true;
        }
    }
}