import Testing
import Foundation
@testable import MIMEParser

@Suite("MIMEParser")
struct MIMEParserTests {

	// MARK: - Content Decoding

	@Test("decode base64 content")
	func decodeBase64() {
		let encoded = "SGVsbG8gV29ybGQ="
		let data = MIMEParser.decodeContent(encoded, encoding: .base64)
		#expect(String(data: data, encoding: .utf8) == "Hello World")
	}

	@Test("decode quoted-printable content")
	func decodeQuotedPrintable() {
		let encoded = "Gr=C3=BC=C3=9Fe"
		let data = MIMEParser.decodeContent(encoded, encoding: .quotedPrintable)
		#expect(String(data: data, encoding: .utf8) == "Grüße")
	}

	@Test("decode 7bit content passes through")
	func decode7bit() {
		let text = "Hello World"
		let data = MIMEParser.decodeContent(text, encoding: .sevenBit)
		#expect(String(data: data, encoding: .utf8) == "Hello World")
	}

	@Test("boundary generation produces unique strings with =_ prefix")
	func boundaryGeneration() {
		let b1 = MIMEParser.generateBoundary()
		let b2 = MIMEParser.generateBoundary()
		#expect(b1 != b2)
		#expect(b1.hasPrefix("=_MagnumOpus_"))
		#expect(b2.hasPrefix("=_MagnumOpus_"))
	}

	// MARK: - Single-part Parsing

	@Test("parse single-part text/plain message")
	func parseSinglePartText() {
		let raw = "Content-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\nHello, this is the body."
		let message = MIMEParser.parse(raw)
		#expect(message.textBody == "Hello, this is the body.")
		#expect(message.htmlBody == nil)
		#expect(message.attachments.isEmpty)
	}

	// MARK: - Multipart Parsing

	@Test("parse multipart/mixed with text and one attachment")
	func parseMultipartMixed() {
		let raw = "Content-Type: multipart/mixed; boundary=\"----boundary123\"\r\n\r\n------boundary123\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\nHello from the body.\r\n------boundary123\r\nContent-Type: application/pdf; name=\"report.pdf\"\r\nContent-Disposition: attachment; filename=\"report.pdf\"\r\nContent-Transfer-Encoding: base64\r\n\r\nSGVsbG8=\r\n------boundary123--"
		let message = MIMEParser.parse(raw)
		#expect(message.textBody == "Hello from the body.")
		#expect(message.attachments.count == 1)
		#expect(message.attachments.first?.filename == "report.pdf")
		#expect(message.attachments.first?.mimeType == "application/pdf")
		#expect(message.attachments.first?.sectionPath == "2")
		#expect(message.attachments.first?.isInline == false)
	}

	@Test("parse multipart/alternative extracts text and html bodies")
	func parseMultipartAlternative() {
		let raw = "Content-Type: multipart/alternative; boundary=\"alt-boundary\"\r\n\r\n--alt-boundary\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nPlain text body\r\n--alt-boundary\r\nContent-Type: text/html; charset=utf-8\r\n\r\n<p>HTML body</p>\r\n--alt-boundary--"
		let message = MIMEParser.parse(raw)
		#expect(message.textBody == "Plain text body")
		#expect(message.htmlBody == "<p>HTML body</p>")
		#expect(message.attachments.isEmpty)
	}

	@Test("parse multipart/related with inline image")
	func parseMultipartRelated() {
		let raw = "Content-Type: multipart/related; boundary=\"rel-boundary\"\r\n\r\n--rel-boundary\r\nContent-Type: text/html; charset=utf-8\r\n\r\n<p>Image: <img src=\"cid:img001\"></p>\r\n--rel-boundary\r\nContent-Type: image/png\r\nContent-ID: <img001>\r\nContent-Disposition: inline\r\nContent-Transfer-Encoding: base64\r\n\r\niVBORw0KGgo=\r\n--rel-boundary--"
		let message = MIMEParser.parse(raw)
		#expect(message.htmlBody == "<p>Image: <img src=\"cid:img001\"></p>")
		#expect(message.inlineImages.count == 1)
		#expect(message.inlineImages.first?.contentId == "img001")
		#expect(message.inlineImages.first?.isInline == true)
	}

	@Test("parse nested multipart/mixed containing multipart/alternative")
	func parseNestedMultipart() {
		let raw = "Content-Type: multipart/mixed; boundary=\"outer\"\r\n\r\n--outer\r\nContent-Type: multipart/alternative; boundary=\"inner\"\r\n\r\n--inner\r\nContent-Type: text/plain\r\n\r\nPlain text\r\n--inner\r\nContent-Type: text/html\r\n\r\n<p>HTML</p>\r\n--inner--\r\n--outer\r\nContent-Type: application/pdf; name=\"doc.pdf\"\r\nContent-Disposition: attachment; filename=\"doc.pdf\"\r\nContent-Transfer-Encoding: base64\r\n\r\nAAAA\r\n--outer--"
		let message = MIMEParser.parse(raw)
		#expect(message.textBody == "Plain text")
		#expect(message.htmlBody == "<p>HTML</p>")
		#expect(message.attachments.count == 1)
		#expect(message.attachments.first?.filename == "doc.pdf")
	}

	@Test("section paths assigned correctly for nested parts")
	func sectionPaths() {
		let raw = "Content-Type: multipart/mixed; boundary=\"outer\"\r\n\r\n--outer\r\nContent-Type: text/plain\r\n\r\nBody text\r\n--outer\r\nContent-Type: application/pdf; name=\"a.pdf\"\r\nContent-Disposition: attachment; filename=\"a.pdf\"\r\nContent-Transfer-Encoding: base64\r\n\r\nAAAA\r\n--outer\r\nContent-Type: image/jpeg; name=\"b.jpg\"\r\nContent-Disposition: attachment; filename=\"b.jpg\"\r\nContent-Transfer-Encoding: base64\r\n\r\nBBBB\r\n--outer--"
		let message = MIMEParser.parse(raw)
		#expect(message.attachments.count == 2)
		#expect(message.attachments[0].sectionPath == "2")
		#expect(message.attachments[1].sectionPath == "3")
	}

	@Test("extract filename from Content-Type name parameter when no Content-Disposition")
	func filenameFromContentType() {
		let raw = "Content-Type: multipart/mixed; boundary=\"bound\"\r\n\r\n--bound\r\nContent-Type: text/plain\r\n\r\nBody\r\n--bound\r\nContent-Type: application/octet-stream; name=\"data.bin\"\r\nContent-Transfer-Encoding: base64\r\n\r\nAAAA\r\n--bound--"
		let message = MIMEParser.parse(raw)
		#expect(message.attachments.count == 1)
		#expect(message.attachments.first?.filename == "data.bin")
	}

	@Test("estimate decoded size from base64 content")
	func base64SizeEstimate() {
		// 8 base64 chars = 6 decoded bytes
		let raw = "Content-Type: multipart/mixed; boundary=\"bound\"\r\n\r\n--bound\r\nContent-Type: text/plain\r\n\r\nBody\r\n--bound\r\nContent-Type: application/pdf; name=\"f.pdf\"\r\nContent-Disposition: attachment; filename=\"f.pdf\"\r\nContent-Transfer-Encoding: base64\r\n\r\nAAAAAAAA\r\n--bound--"
		let message = MIMEParser.parse(raw)
		#expect(message.attachments.first?.size == 6)
	}

	@Test("handle malformed MIME gracefully — missing boundary")
	func malformedMissingBoundary() {
		let raw = "Content-Type: multipart/mixed\r\n\r\nSome text without proper boundary markers."
		let message = MIMEParser.parse(raw)
		// Should not crash; treat as single-part
		#expect(message.attachments.isEmpty)
	}

	@Test("RFC 2047 encoded filename decoded")
	func rfc2047Filename() {
		let raw = "Content-Type: multipart/mixed; boundary=\"bound\"\r\n\r\n--bound\r\nContent-Type: text/plain\r\n\r\nBody\r\n--bound\r\nContent-Type: application/pdf; name=\"=?utf-8?B?QmVyaWNodC5wZGY=?=\"\r\nContent-Disposition: attachment; filename=\"=?utf-8?B?QmVyaWNodC5wZGY=?=\"\r\nContent-Transfer-Encoding: base64\r\n\r\nAAAA\r\n--bound--"
		let message = MIMEParser.parse(raw)
		#expect(message.attachments.first?.filename == "Bericht.pdf")
	}
}
