using System.Text;
using System.Xml.Linq;

namespace ZB.MOM.WW.ScadaBridge.NotificationService.Ews;

/// <summary>
/// Builds the EWS <c>CreateItem</c> SOAP envelope used to send a notification email
/// through Exchange Web Services.
/// <para>
/// Pure and I/O-free: the HTTP send lives in the sender that consumes this. The envelope is
/// composed with LINQ-to-XML rather than string concatenation so operator-authored subject and
/// body content is XML-escaped by construction.
/// </para>
/// </summary>
public static class EwsSoapEnvelope
{
    private static readonly XNamespace Soap = "http://schemas.xmlsoap.org/soap/envelope/";
    private static readonly XNamespace Types = "http://schemas.microsoft.com/exchange/services/2006/types";
    private static readonly XNamespace Messages = "http://schemas.microsoft.com/exchange/services/2006/messages";

    /// <summary>
    /// Builds a <c>CreateItem</c> request that sends one plain-text message.
    /// <para>
    /// <c>MessageDisposition="SendOnly"</c> sends without keeping a Sent Items copy, and every
    /// recipient is addressed via <c>BccRecipients</c> so recipients cannot see one another —
    /// both mirror the SMTP delivery path's semantics.
    /// </para>
    /// </summary>
    /// <param name="fromAddress">The sending mailbox address stamped into <c>t:From</c>.</param>
    /// <param name="bccRecipients">
    /// The recipient addresses; each becomes one <c>t:Mailbox</c> under <c>t:BccRecipients</c>.
    /// Callers are expected to supply at least one — an empty list yields an envelope Exchange
    /// rejects as schema-invalid.
    /// </param>
    /// <param name="subject">The message subject; escaped, not interpreted.</param>
    /// <param name="body">The plain-text message body (<c>BodyType="Text"</c>); escaped, not interpreted.</param>
    /// <returns>The complete SOAP envelope as XML text, prefixed with a UTF-8 declaration.</returns>
    public static string BuildCreateItem(
        string fromAddress,
        IReadOnlyList<string> bccRecipients,
        string subject,
        string body)
    {
        ArgumentNullException.ThrowIfNull(fromAddress);
        ArgumentNullException.ThrowIfNull(bccRecipients);
        ArgumentNullException.ThrowIfNull(subject);
        ArgumentNullException.ThrowIfNull(body);

        // EWS MessageType is an xs:sequence: Subject, Body, BccRecipients, From must appear in
        // this order or the server answers ErrorSchemaValidation.
        var message = new XElement(
            Types + "Message",
            new XElement(Types + "Subject", subject),
            new XElement(Types + "Body", new XAttribute("BodyType", "Text"), body),
            new XElement(Types + "BccRecipients", bccRecipients.Select(Mailbox)),
            new XElement(Types + "From", Mailbox(fromAddress)));

        var document = new XDocument(
            new XDeclaration("1.0", "utf-8", null),
            new XElement(
                Soap + "Envelope",
                new XAttribute(XNamespace.Xmlns + "soap", Soap.NamespaceName),
                new XAttribute(XNamespace.Xmlns + "t", Types.NamespaceName),
                new XAttribute(XNamespace.Xmlns + "m", Messages.NamespaceName),
                new XElement(
                    Soap + "Header",
                    new XElement(Types + "RequestServerVersion", new XAttribute("Version", "Exchange2013"))),
                new XElement(
                    Soap + "Body",
                    new XElement(
                        Messages + "CreateItem",
                        new XAttribute("MessageDisposition", "SendOnly"),
                        new XElement(Messages + "Items", message)))));

        // XDocument.ToString() drops the declaration; saving through a writer that reports UTF-8
        // keeps it, and keeps the declared encoding honest.
        using var writer = new Utf8StringWriter();
        document.Save(writer);
        return writer.ToString();
    }

    private static XElement Mailbox(string address)
        => new(Types + "Mailbox", new XElement(Types + "EmailAddress", address));

    /// <summary>
    /// A <see cref="StringWriter"/> that reports UTF-8 so <see cref="XDocument.Save(TextWriter)"/>
    /// emits <c>encoding="utf-8"</c> instead of the default UTF-16.
    /// </summary>
    private sealed class Utf8StringWriter : StringWriter
    {
        public override Encoding Encoding => Encoding.UTF8;
    }
}
