use std::{io::Write, iter::repeat_with};

use mime::Mime;

use crate::message::{
    EmailFormat, IntoBody,
    header::{self, ContentTransferEncoding, ContentType, Header, Headers},
};

/// MIME part variants
#[derive(Debug, Clone)]
pub(super) enum Part {
    /// Single part with content
    Single(SinglePart),

    /// Multiple parts of content
    Multi(MultiPart),
}

impl Part {
    #[cfg(feature = "dkim")]
    pub(super) fn format_body(&self, out: &mut Vec<u8>) {
        match self {
            Part::Single(part) => part.format_body(out),
            Part::Multi(part) => part.format_body(out),
        }
    }
}

impl EmailFormat for Part {
    fn format(&self, out: &mut Vec<u8>) {
        match self {
            Part::Single(part) => part.format(out),
            Part::Multi(part) => part.format(out),
        }
    }
}

/// Creates builder for single part
#[derive(Debug, Clone)]
pub struct SinglePartBuilder {
    headers: Headers,
}

impl SinglePartBuilder {
    /// Creates a default singlepart builder
    pub fn new() -> Self {
        Self {
            headers: Headers::new(),
        }
    }

    /// Set the header to singlepart
    pub fn header<H: Header>(mut self, header: H) -> Self {
        self.headers.set(header);
        self
    }

    /// Set the Content-Type header of the singlepart
    pub fn content_type(mut self, content_type: ContentType) -> Self {
        self.headers.set(content_type);
        self
    }

    /// Build singlepart using body
    pub fn body<T: IntoBody>(mut self, body: T) -> SinglePart {
        let maybe_encoding = self.headers.get::<ContentTransferEncoding>();
        let body = body.into_body(maybe_encoding);

        self.headers.set(body.encoding());

        SinglePart {
            headers: self.headers,
            body: body.into_vec(),
        }
    }
}

impl Default for SinglePartBuilder {
    fn default() -> Self {
        Self::new()
    }
}

/// Single part
///
/// # Example
///
/// ```
/// use lettre::message::{SinglePart, header};
///
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let part = SinglePart::builder()
///     .header(header::ContentType::TEXT_PLAIN)
///     .body(String::from("Текст письма в уникоде"));
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone)]
pub struct SinglePart {
    headers: Headers,
    body: Vec<u8>,
}

impl SinglePart {
    /// Creates a builder for singlepart
    #[inline]
    pub fn builder() -> SinglePartBuilder {
        SinglePartBuilder::new()
    }

    /// Directly create a `SinglePart` from a plain UTF-8 content
    pub fn plain<T: IntoBody>(body: T) -> Self {
        Self::builder()
            .header(header::ContentType::TEXT_PLAIN)
            .body(body)
    }

    /// Directly create a `SinglePart` from a UTF-8 HTML content
    pub fn html<T: IntoBody>(body: T) -> Self {
        Self::builder()
            .header(header::ContentType::TEXT_HTML)
            .body(body)
    }

    /// Get the headers from singlepart
    #[inline]
    pub fn headers(&self) -> &Headers {
        &self.headers
    }

    /// Get the encoded body
    #[inline]
    pub fn raw_body(&self) -> &[u8] {
        &self.body
    }

    /// Get message content formatted for sending
    pub fn formatted(&self) -> Vec<u8> {
        let mut out = Vec::new();
        self.format(&mut out);
        out
    }

    /// Format only the signlepart body
    fn format_body(&self, out: &mut Vec<u8>) {
        out.extend_from_slice(&self.body);
        out.extend_from_slice(b"\r\n");
    }
}

impl EmailFormat for SinglePart {
    fn format(&self, out: &mut Vec<u8>) {
        write!(out, "{}", self.headers)
            .expect("A Write implementation panicked while formatting headers");
        out.extend_from_slice(b"\r\n");
        self.format_body(out);
    }
}

/// The kind of multipart
#[derive(Debug, Clone)]
pub enum MultiPartKind {
    /// Mixed kind to combine unrelated content parts
    ///
    /// For example, this kind can be used to mix an email message and attachments.
    Mixed,

    /// Alternative kind to join several variants of same email contents.
    ///
    /// That kind is recommended to use for joining plain (text) and rich (HTML) messages into a single email message.
    Alternative,

    /// Related kind to mix content and related resources.
    ///
    /// For example, you can include images in HTML content using that.
    Related,

    /// Encrypted kind for encrypted messages
    Encrypted { protocol: String },

    /// Signed kind for signed messages
    Signed { protocol: String, micalg: String },
}

/// Create a random MIME boundary.
/// (Not cryptographically random)
fn make_boundary() -> String {
    repeat_with(fastrand::alphanumeric).take(40).collect()
}

impl MultiPartKind {
    pub(crate) fn to_mime<S: Into<String>>(&self, boundary: Option<S>) -> Mime {
        let boundary = boundary.map_or_else(make_boundary, Into::into);

        format!(
            "multipart/{}; boundary=\"{}\"{}",
            match self {
                Self::Mixed => "mixed",
                Self::Alternative => "alternative",
                Self::Related => "related",
                Self::Encrypted { .. } => "encrypted",
                Self::Signed { .. } => "signed",
            },
            boundary,
            match self {
                Self::Encrypted { protocol } => format!("; protocol=\"{protocol}\""),
                Self::Signed { protocol, micalg } =>
                    format!("; protocol=\"{protocol}\"; micalg=\"{micalg}\""),
                _ => String::new(),
            }
        )
        .parse()
        .unwrap()
    }

    fn from_mime(m: &Mime) -> Option<Self> {
        match m.subtype().as_ref() {
            "mixed" => Some(Self::Mixed),
            "alternative" => Some(Self::Alternative),
            "related" => Some(Self::Related),
            "signed" => m.get_param("protocol").and_then(|p| {
                m.get_param("micalg").map(|micalg| Self::Signed {
                    protocol: p.as_str().to_owned(),
                    micalg: micalg.as_str().to_owned(),
                })
            }),
            "encrypted" => m.get_param("protocol").map(|p| Self::Encrypted {
                protocol: p.as_str().to_owned(),
            }),
            _ => None,
        }
    }
}

/// Multipart builder
#[derive(Debug, Clone)]
pub struct MultiPartBuilder {
    headers: Headers,
}

impl MultiPartBuilder {
    /// Creates default multipart builder
    pub fn new() -> Self {
        Self {
            headers: Headers::new(),
        }
    }

    /// Set a header
    pub fn header<H: Header>(mut self, header: H) -> Self {
        self.headers.set(header);
        self
    }

    /// Set `Content-Type` header using [`MultiPartKind`]
    pub fn kind(self, kind: MultiPartKind) -> Self {
        self.header(ContentType::from_mime(kind.to_mime::<String>(None)))
    }

    /// Set custom boundary
    pub fn boundary<S: Into<String>>(self, boundary: S) -> Self {
        let kind = {
            let content_type = self.headers.get::<ContentType>().unwrap();
            MultiPartKind::from_mime(content_type.as_ref()).unwrap()
        };
        let mime = kind.to_mime(Some(boundary));
        self.header(ContentType::from_mime(mime))
    }

    /// Creates multipart without parts
    pub fn build(self) -> MultiPart {
        MultiPart {
            headers: self.headers,
            parts: Vec::new(),
        }
    }

    /// Creates multipart using singlepart
    pub fn singlepart(self, part: SinglePart) -> MultiPart {
        self.build().singlepart(part)
    }

    /// Creates multipart using multipart
    pub fn multipart(self, part: MultiPart) -> MultiPart {
        self.build().multipart(part)
    }
}

impl Default for MultiPartBuilder {
    fn default() -> Self {
        Self::new()
    }
}

/// Multipart variant with parts
#[derive(Debug, Clone)]
pub struct MultiPart {
    headers: Headers,
    parts: Vec<Part>,
}

impl MultiPart {
    /// Creates multipart builder
    pub fn builder() -> MultiPartBuilder {
        MultiPartBuilder::new()
    }

    /// Creates mixed multipart builder
    ///
    /// Shortcut for `MultiPart::builder().kind(MultiPartKind::Mixed)`
    pub fn mixed() -> MultiPartBuilder {
        MultiPart::builder().kind(MultiPartKind::Mixed)
    }

    /// Creates alternative multipart builder
    ///
    /// Shortcut for `MultiPart::builder().kind(MultiPartKind::Alternative)`
    pub fn alternative() -> MultiPartBuilder {
        MultiPart::builder().kind(MultiPartKind::Alternative)
    }

    /// Creates related multipart builder
    ///
    /// Shortcut for `MultiPart::builder().kind(MultiPartKind::Related)`
    pub fn related() -> MultiPartBuilder {
        MultiPart::builder().kind(MultiPartKind::Related)
    }

    /// Creates encrypted multipart builder
    ///
    /// Shortcut for `MultiPart::builder().kind(MultiPartKind::Encrypted{ protocol })`
    pub fn encrypted(protocol: String) -> MultiPartBuilder {
        MultiPart::builder().kind(MultiPartKind::Encrypted { protocol })
    }

    /// Creates signed multipart builder
    ///
    /// Shortcut for `MultiPart::builder().kind(MultiPartKind::Signed{ protocol, micalg })`
    pub fn signed(protocol: String, micalg: String) -> MultiPartBuilder {
        MultiPart::builder().kind(MultiPartKind::Signed { protocol, micalg })
    }

    /// Alias for HTML and plain text versions of an email
    pub fn alternative_plain_html<T: IntoBody, V: IntoBody>(plain: T, html: V) -> Self {
        Self::alternative()
            .singlepart(SinglePart::plain(plain))
            .singlepart(SinglePart::html(html))
    }

    /// Add single part to multipart
    pub fn singlepart(mut self, part: SinglePart) -> Self {
        self.parts.push(Part::Single(part));
        self
    }

    /// Add multi part to multipart
    pub fn multipart(mut self, part: MultiPart) -> Self {
        self.parts.push(Part::Multi(part));
        self
    }

    /// Get the boundary of multipart contents
    pub fn boundary(&self) -> String {
        let content_type = self.headers.get::<ContentType>().unwrap();
        content_type
            .as_ref()
            .get_param("boundary")
            .unwrap()
            .as_str()
            .into()
    }

    /// Get the headers from the multipart
    pub fn headers(&self) -> &Headers {
        &self.headers
    }

    /// Get a mutable reference to the headers
    pub fn headers_mut(&mut self) -> &mut Headers {
        &mut self.headers
    }

    /// Get message content formatted for SMTP
    pub fn formatted(&self) -> Vec<u8> {
        let mut out = Vec::new();
        self.format(&mut out);
        out
    }

    /// Format only the multipart body
    fn format_body(&self, out: &mut Vec<u8>) {
        let boundary = self.boundary();

        for part in &self.parts {
            out.extend_from_slice(b"--");
            out.extend_from_slice(boundary.as_bytes());
            out.extend_from_slice(b"\r\n");
            part.format(out);
        }

        out.extend_from_slice(b"--");
        out.extend_from_slice(boundary.as_bytes());
        out.extend_from_slice(b"--\r\n");
    }
}

impl EmailFormat for MultiPart {
    fn format(&self, out: &mut Vec<u8>) {
        write!(out, "{}", self.headers)
            .expect("A Write implementation panicked while formatting headers");
        out.extend_from_slice(b"\r\n");
        self.format_body(out);
    }
}

#[cfg(test)]
mod test {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn single_part_binary() {
        let part = SinglePart::builder()
            .header(header::ContentType::TEXT_PLAIN)
            .header(header::ContentTransferEncoding::Binary)
            .body(String::from("Текст письма в уникоде"));

        assert_eq!(
            String::from_utf8(part.formatted()).unwrap(),
            concat!(
                "Content-Type: text/plain; charset=utf-8\r\n",
                "Content-Transfer-Encoding: binary\r\n",
                "\r\n",
                "Текст письма в уникоде\r\n"
            )
        );
    }

    #[test]
    fn single_part_quoted_printable() {
        let part = SinglePart::builder()
            .header(header::ContentType::TEXT_PLAIN)
            .header(header::ContentTransferEncoding::QuotedPrintable)
            .body(String::from("Текст письма в уникоде"));

        assert_eq!(
            String::from_utf8(part.formatted()).unwrap(),
            concat!(
                "Content-Type: text/plain; charset=utf-8\r\n",
                "Content-Transfer-Encoding: quoted-printable\r\n",
                "\r\n",
                "=D0=A2=D0=B5=D0=BA=D1=81=D1=82 =D0=BF=D0=B8=D1=81=D1=8C=D0=BC=D0=B0 =D0=B2 =\r\n",
                "=D1=83=D0=BD=D0=B8=D0=BA=D0=BE=D0=B4=D0=B5\r\n"
            )
        );
    }

    #[test]
    fn single_part_base64() {
        let part = SinglePart::builder()
            .header(header::ContentType::TEXT_PLAIN)
            .header(header::ContentTransferEncoding::Base64)
            .body(String::from("Текст письма в уникоде"));

        assert_eq!(
            String::from_utf8(part.formatted()).unwrap(),
            concat!(
                "Content-Type: text/plain; charset=utf-8\r\n",
                "Content-Transfer-Encoding: base64\r\n",
                "\r\n",
                "0KLQtdC60YHRgiDQv9C40YHRjNC80LAg0LIg0YPQvdC40LrQvtC00LU=\r\n"
            )
        );
    }

    #[test]
    fn multi_part_mixed() {
        let part = MultiPart::mixed()
            .boundary("0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1")
            .singlepart(
                SinglePart::builder()
                    .header(header::ContentType::TEXT_PLAIN)
                    .header(header::ContentTransferEncoding::Binary)
                    .body(String::from("Текст письма в уникоде")),
            )
            .singlepart(
                SinglePart::builder()
                    .header(header::ContentType::TEXT_PLAIN)
                    .header(header::ContentDisposition::attachment("example.c"))
                    .header(header::ContentTransferEncoding::Binary)
                    .body(String::from("int main() { return 0; }")),
            );

        assert_eq!(
            String::from_utf8(part.formatted()).unwrap(),
            concat!(
                "Content-Type: multipart/mixed;\r\n",
                " boundary=\"0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\"\r\n",
                "\r\n",
                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
                "Content-Type: text/plain; charset=utf-8\r\n",
                "Content-Transfer-Encoding: binary\r\n",
                "\r\n",
                "Текст письма в уникоде\r\n",
                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
                "Content-Type: text/plain; charset=utf-8\r\n",
                "Content-Disposition: attachment; filename=\"example.c\"\r\n",
                "Content-Transfer-Encoding: binary\r\n",
                "\r\n",
                "int main() { return 0; }\r\n",
                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1--\r\n"
            )
        );
    }
    #[test]
    fn multi_part_encrypted() {
        let part = MultiPart::encrypted("application/pgp-encrypted".to_owned())
            .boundary("0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1")
            .singlepart(
                SinglePart::builder()
                    .header(header::ContentType::parse("application/pgp-encrypted").unwrap())
                    .body(String::from("Version: 1")),
            )
            .singlepart(
                SinglePart::builder()
                    .header(
                        ContentType::parse("application/octet-stream; name=\"encrypted.asc\"")
                            .unwrap(),
                    )
                    .header(header::ContentDisposition::inline_with_name(
                        "encrypted.asc",
                    ))
                    .body(String::from(concat!(
                        "-----BEGIN PGP MESSAGE-----\r\n",
                        "wV4D0dz5vDXklO8SAQdA5lGX1UU/eVQqDxNYdHa7tukoingHzqUB6wQssbMfHl8w\r\n",
                        "...\r\n",
                        "-----END PGP MESSAGE-----\r\n"
                    ))),
            );

        assert_eq!(
            String::from_utf8(part.formatted()).unwrap(),
            concat!(
                "Content-Type: multipart/encrypted;\r\n",
                " boundary=\"0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\";\r\n",
                " protocol=\"application/pgp-encrypted\"\r\n",
                "\r\n",
                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
                "Content-Type: application/pgp-encrypted\r\n",
                "Content-Transfer-Encoding: 7bit\r\n",
                "\r\n",
                "Version: 1\r\n",
                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
                "Content-Type: application/octet-stream; name=\"encrypted.asc\"\r\n",
                "Content-Disposition: inline; filename=\"encrypted.asc\"\r\n",
                "Content-Transfer-Encoding: 7bit\r\n",
                "\r\n",
                "-----BEGIN PGP MESSAGE-----\r\n",
                "wV4D0dz5vDXklO8SAQdA5lGX1UU/eVQqDxNYdHa7tukoingHzqUB6wQssbMfHl8w\r\n",
                "...\r\n",
                "-----END PGP MESSAGE-----\r\n",
                "\r\n",
                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1--\r\n"
            )
        );
    }
    #[test]
    fn multi_part_signed() {
        let part = MultiPart::signed(
            "application/pgp-signature".to_owned(),
            "pgp-sha256".to_owned(),
        )
        .boundary("0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1")
        .singlepart(
            SinglePart::builder()
                .header(header::ContentType::TEXT_PLAIN)
                .body(String::from("Test email for signature")),
        )
        .singlepart(
            SinglePart::builder()
                .header(
                    ContentType::parse("application/pgp-signature; name=\"signature.asc\"")
                        .unwrap(),
                )
                .header(header::ContentDisposition::attachment("signature.asc"))
                .body(String::from(concat!(
                    "-----BEGIN PGP SIGNATURE-----\r\n",
                    "\r\n",
                    "iHUEARYIAB0WIQTNsp3S/GbdE0KoiQ+IGQOscREZuQUCXyOzDAAKCRCIGQOscREZ\r\n",
                    "udgDAQCv3FJ3QWW5bRaGZAa0Ug6vASFdkvDMKoRwcoFnHPthjQEAiQ8skkIyE2GE\r\n",
                    "PoLpAXiKpT+NU8S8+8dfvwutnb4dSwM=\r\n",
                    "=3FYZ\r\n",
                    "-----END PGP SIGNATURE-----\r\n",
                ))),
        );

        assert_eq!(
            String::from_utf8(part.formatted()).unwrap(),
            concat!(
                "Content-Type: multipart/signed;\r\n",
                " boundary=\"0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\";\r\n",
                " protocol=\"application/pgp-signature\";",
                " micalg=\"pgp-sha256\"\r\n",
                "\r\n",
                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
                "Content-Type: text/plain; charset=utf-8\r\n",
                "Content-Transfer-Encoding: 7bit\r\n",
                "\r\n",
                "Test email for signature\r\n",
                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
                "Content-Type: application/pgp-signature; name=\"signature.asc\"\r\n",
                "Content-Disposition: attachment; filename=\"signature.asc\"\r\n",
                "Content-Transfer-Encoding: 7bit\r\n",
                "\r\n",
                "-----BEGIN PGP SIGNATURE-----\r\n",
                "\r\n",
                "iHUEARYIAB0WIQTNsp3S/GbdE0KoiQ+IGQOscREZuQUCXyOzDAAKCRCIGQOscREZ\r\n",
                "udgDAQCv3FJ3QWW5bRaGZAa0Ug6vASFdkvDMKoRwcoFnHPthjQEAiQ8skkIyE2GE\r\n",
                "PoLpAXiKpT+NU8S8+8dfvwutnb4dSwM=\r\n",
                "=3FYZ\r\n",
                "-----END PGP SIGNATURE-----\r\n",
                "\r\n",
                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1--\r\n"
            )
        );
    }

    #[test]
    fn multi_part_alternative() {
        let part = MultiPart::alternative()
            .boundary("0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1")
            .singlepart(SinglePart::builder()
                             .header(header::ContentType::TEXT_PLAIN)
                             .header(header::ContentTransferEncoding::Binary)
                             .body(String::from("Текст письма в уникоде")))
            .singlepart(SinglePart::builder()
                             .header(header::ContentType::TEXT_HTML)
                             .header(header::ContentTransferEncoding::Binary)
                             .body(String::from("<p>Текст <em>письма</em> в <a href=\"https://ru.wikipedia.org/wiki/Юникод\">уникоде</a><p>")));

        assert_eq!(
            String::from_utf8(part.formatted()).unwrap(),
            concat!(
                "Content-Type: multipart/alternative;\r\n",
                " boundary=\"0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\"\r\n",
                "\r\n",
                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
                "Content-Type: text/plain; charset=utf-8\r\n",
                "Content-Transfer-Encoding: binary\r\n",
                "\r\n",
                "Текст письма в уникоде\r\n",
                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
                "Content-Type: text/html; charset=utf-8\r\n",
                "Content-Transfer-Encoding: binary\r\n",
                "\r\n",
                "<p>Текст <em>письма</em> в <a href=\"https://ru.wikipedia.org/wiki/Юникод\">уникоде</a><p>\r\n",
                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1--\r\n"
            )
        );
    }

    #[test]
    fn multi_part_mixed_related() {
        let part = MultiPart::mixed()
            .boundary("0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1")
            .multipart(MultiPart::related()
                            .boundary("0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1")
                            .singlepart(SinglePart::builder()
                                             .header(header::ContentType::TEXT_HTML)
                                             .header(header::ContentTransferEncoding::Binary)
                                             .body(String::from("<p>Текст <em>письма</em> в <a href=\"https://ru.wikipedia.org/wiki/Юникод\">уникоде</a><p>")))
                            .singlepart(SinglePart::builder()
                                             .header(header::ContentType::parse("image/png").unwrap())
                                             .header(header::ContentLocation::from(String::from("/image.png")))
                                             .header(header::ContentTransferEncoding::Base64)
                                             .body(String::from("1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"))))
            .singlepart(SinglePart::builder()
                             .header(header::ContentType::TEXT_PLAIN)
                             .header(header::ContentDisposition::attachment("example.c"))
                             .header(header::ContentTransferEncoding::Binary)
                             .body(String::from("int main() { return 0; }")));

        assert_eq!(
            String::from_utf8(part.formatted()).unwrap(),
            concat!(
                "Content-Type: multipart/mixed;\r\n",
                " boundary=\"0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\"\r\n",
                "\r\n",
                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
                "Content-Type: multipart/related;\r\n",
                " boundary=\"0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\"\r\n",
                "\r\n",
                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
                "Content-Type: text/html; charset=utf-8\r\n",
                "Content-Transfer-Encoding: binary\r\n",
                "\r\n",
                "<p>Текст <em>письма</em> в <a href=\"https://ru.wikipedia.org/wiki/Юникод\">уникоде</a><p>\r\n",
                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
                "Content-Type: image/png\r\n",
                "Content-Location: /image.png\r\n",
                "Content-Transfer-Encoding: base64\r\n",
                "\r\n",
                "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3\r\n",
                "ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0\r\n",
                "NTY3ODkwMTIzNDU2Nzg5MA==\r\n",
                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1--\r\n",
                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1\r\n",
                "Content-Type: text/plain; charset=utf-8\r\n",
                "Content-Disposition: attachment; filename=\"example.c\"\r\n",
                "Content-Transfer-Encoding: binary\r\n",
                "\r\n",
                "int main() { return 0; }\r\n",
                "--0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1--\r\n"
            )
        );
    }

    #[test]
    fn test_make_boundary() {
        let mut boundaries = std::collections::HashSet::with_capacity(10);
        for _ in 0..1000 {
            boundaries.insert(make_boundary());
        }

        // Ensure there are no duplicates
        assert_eq!(1000, boundaries.len());

        // Ensure correct length
        for boundary in boundaries {
            assert_eq!(40, boundary.len());
        }
    }
}
