using InABox.Clients;
using InABox.Core;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Mail;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Forms;
using System.Drawing;
using InABox.WPF;
using MessageBox = System.Windows.Forms.MessageBox;
using TextBox = System.Windows.Controls.TextBox;
using InABox.Wpf.Reports;
using System.Diagnostics.CodeAnalysis;

namespace InABox.Wpf;

public static class EmailUtils
{
    /// <summary>
     /// Creates and opens an email with the default email app - selected by the user.
     /// This method is for emails with a PDF attachment. Provide the file name and data.
     /// Optionally provide from, subject and body.
     /// If from is not provided, an attempt will be made to find the User's email address - if empty it will throw an error (cannot be empty)
     /// </summary>
     /// <param name="attachmentname"></param>
     /// <param name="attachmentdata"></param>
     /// <param name="from"></param>
     /// <param name="subject"></param>
     /// <param name="body"></param>
    public static void CreateEMLFile(string attachmentname, byte[] attachmentdata, string from = "", string subject = "", string body = "", string to = "")
    {
        var message = CreateMessage(from, subject, body, to);

        message = AddAttachment(message, attachmentname, attachmentdata);

        OpenEmail(message, attachmentname);
    }
    
    /// <summary>
    /// Creates and opens an email with the default email app - selected by the user.
    /// This method is for emails with multiple PDF attachments. Provide the a Dictionary of names and byte arrays
    /// Optionally provide from, subject and body.
    /// If from is not provided, an attempt will be made to find the User's email address - if empty it will throw an error (cannot be empty)
    /// </summary>
    /// <param name="attachmentname"></param>
    /// <param name="attachmentdata"></param>
    /// <param name="from"></param>
    /// <param name="subject"></param>
    /// <param name="body"></param>
    public static void CreateEMLFile(Dictionary<string,byte[]> attachments, string from = "", string subject = "", string body = "", string to = "")
    {
        var message = CreateMessage(from, subject, body, to);

        foreach (var key in attachments.Keys)
            AddAttachment(message, key, attachments[key]);

        OpenEmail(message);
    }

    /// <summary>
    /// Creates and opens an email with the default email app - selected by the user.
    /// This method is for emails with no attachments.
    /// Optionally provide from, subject and body.
    /// If from is not provided, an attempt will be made to find the User's email address - if empty it will throw an error (cannot be empty)
    /// </summary>
    /// <param name="from"></param>
    /// <param name="subject"></param>
    /// <param name="body"></param>

    public static void CreateEMLFile(string? from, string? subject, string? body)
    {
        var message = CreateMessage(from, subject, body);

        OpenEmail(message, "Message from " + message.From);
    }

    public static void OpenEmail(MailMessage message, string? name = null)
    {
        var filename = Path.Combine(
            Path.GetTempPath(), 
            Path.ChangeExtension(String.IsNullOrWhiteSpace(name) ? Guid.NewGuid().ToString() : name, ".eml")
        );

        using (var filestream = File.Open(filename, FileMode.Create))
        {
            var binaryWriter = new BinaryWriter(filestream);
            //Write the Unsent header to the file so the mail client knows this mail must be presented in "New message" mode
            binaryWriter.Write(Encoding.UTF8.GetBytes("X-Unsent: 1" + Environment.NewLine));

            var assembly = typeof(SmtpClient).Assembly;
            var mailWriterType = assembly.GetType("System.Net.Mail.MailWriter")!;

            // Get reflection info for MailWriter contructor
            var mailWriterConstructor =
                mailWriterType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(Stream), typeof(bool) }, null)!;

            // Construct MailWriter object with our FileStream
            var mailWriter = mailWriterConstructor.Invoke(new object[] { filestream, true });

            // Get reflection info for Send() method on MailMessage
            var sendMethod = typeof(MailMessage).GetMethod("Send", BindingFlags.Instance | BindingFlags.NonPublic)!;

            sendMethod.Invoke(message, BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { mailWriter, true, true }, null);

            // Finally get reflection info for Close() method on our MailWriter
            var closeMethod = mailWriter.GetType().GetMethod("Close", BindingFlags.Instance | BindingFlags.NonPublic)!;

            // Call close method
            closeMethod.Invoke(mailWriter, BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { }, null);
        }

        // Open the file with the default associated application registered on the local machine
        Process.Start(new ProcessStartInfo(filename) { UseShellExecute = true });
    }

    public static MailMessage CreateMessage(string? from = null, string? subject = null, string? body = null, string? to = null)
    {
        return new MailMessage(
            from.NotWhiteSpaceOr(GetAddressFromUser()).NotWhiteSpaceOr("example@outlook.com.au"),
            to.NotWhiteSpaceOr("example@outlook.com.au"),
            subject.NotWhiteSpaceOr("Enter subject"),
            body.NotWhiteSpaceOr("Enter message"))
        {
            IsBodyHtml = false
        };
    }

    public static string GetAddressFromUser()
    {
        if(TryGetAddressFromUser(out var address))
        {
            return address;
        }
        else
        {
            MessageWindow.ShowMessage("Current User Email Address is blank - please fill in (Human Resources -> User Accounts -> Choose your User -> Email Settings -> Email Address", "Error");
            return "";
        }
    }
    public static string? GetAddressFromUserOrNull()
    {
        TryGetAddressFromUser(out var address);
        return address;
    }
    public static bool TryGetAddressFromUser([NotNullWhen(true)] out string? address)
    {
        var table = Client.Query(new Filter<User>(x => x.ID).IsEqualTo(ClientFactory.UserGuid), Columns.None<User>().Add(x => x.EmailAddress));
        var user = table.ToObjects<User>().FirstOrDefault();

        address = user?.EmailAddress.NotWhiteSpaceOr(null);
        return address is not null;
    }

    public static MailMessage AddAttachment(this MailMessage message, string attachmentname, byte[] attachmentdata)
    {
        var attachment = Path.Combine(
            Path.GetTempPath(), 
            String.IsNullOrWhiteSpace(Path.GetExtension(attachmentname)) 
                ? Path.ChangeExtension(attachmentname, ".pdf") 
                : attachmentname 
        );
        File.WriteAllBytes(attachment, attachmentdata);

        message.Attachments.Add(new Attachment(attachment));

        return message;
    }
}
