import Testing
import GRDB
@testable import MailStore

@Suite("ThreadReconstructor")
struct ThreadReconstructorTests {
	func makeStore() throws -> MailStore {
		try MailStore(dbWriter: DatabaseSetup.openInMemoryDatabase())
	}

	func seedAccount(_ store: MailStore) throws {
		try store.insertAccount(AccountRecord(
			id: "acc1", name: "Test", email: "me@example.com",
			imapHost: "imap.example.com", imapPort: 993
		))
		try store.upsertMailbox(MailboxRecord(
			id: "mb1", accountId: "acc1", name: "INBOX", uidValidity: 1, uidNext: 100
		))
	}

	func makeMessage(
		id: String, messageId: String?, inReplyTo: String? = nil,
		refs: String? = nil, subject: String = "Test", date: String = "2024-03-08T10:00:00Z"
	) -> MessageRecord {
		MessageRecord(
			id: id, accountId: "acc1", mailboxId: "mb1", uid: Int.random(in: 1...99999),
			messageId: messageId, inReplyTo: inReplyTo, refs: refs,
			subject: subject, fromAddress: "alice@example.com", fromName: "Alice",
			toAddresses: nil, ccAddresses: nil,
			date: date, snippet: nil, bodyText: nil, bodyHtml: nil,
			isRead: false, isFlagged: false, size: 100
		)
	}

	@Test("creates new thread for standalone message")
	func standaloneMessage() throws {
		let store = try makeStore()
		try seedAccount(store)
		let msg = makeMessage(id: "m1", messageId: "msg001@example.com")
		try store.insertMessages([msg])
		let reconstructor = ThreadReconstructor(store: store)
		try reconstructor.processMessages([msg])
		let threads = try store.threads(accountId: "acc1")
		#expect(threads.count == 1)
		#expect(threads[0].messageCount == 1)
	}

	@Test("groups reply into same thread via In-Reply-To")
	func replyByInReplyTo() throws {
		let store = try makeStore()
		try seedAccount(store)
		let msg1 = makeMessage(id: "m1", messageId: "msg001@example.com", date: "2024-03-08T10:00:00Z")
		let msg2 = makeMessage(
			id: "m2", messageId: "msg002@example.com",
			inReplyTo: "msg001@example.com",
			subject: "Re: Test", date: "2024-03-08T11:00:00Z"
		)
		try store.insertMessages([msg1, msg2])
		let reconstructor = ThreadReconstructor(store: store)
		try reconstructor.processMessages([msg1])
		try reconstructor.processMessages([msg2])
		let threads = try store.threads(accountId: "acc1")
		#expect(threads.count == 1)
		#expect(threads[0].messageCount == 2)
	}

	@Test("groups reply into same thread via References")
	func replyByReferences() throws {
		let store = try makeStore()
		try seedAccount(store)
		let msg1 = makeMessage(id: "m1", messageId: "msg001@example.com", date: "2024-03-08T10:00:00Z")
		let msg2 = makeMessage(
			id: "m2", messageId: "msg003@example.com",
			refs: "msg001@example.com msg002@example.com",
			date: "2024-03-08T12:00:00Z"
		)
		try store.insertMessages([msg1, msg2])
		let reconstructor = ThreadReconstructor(store: store)
		try reconstructor.processMessages([msg1])
		try reconstructor.processMessages([msg2])
		let threads = try store.threads(accountId: "acc1")
		#expect(threads.count == 1)
	}

	@Test("merges threads when new message connects them")
	func mergeThreads() throws {
		let store = try makeStore()
		try seedAccount(store)
		let msg1 = makeMessage(id: "m1", messageId: "msg001@example.com", date: "2024-03-08T10:00:00Z")
		let msg2 = makeMessage(id: "m2", messageId: "msg002@example.com", date: "2024-03-08T11:00:00Z")
		try store.insertMessages([msg1, msg2])
		let reconstructor = ThreadReconstructor(store: store)
		try reconstructor.processMessages([msg1])
		try reconstructor.processMessages([msg2])
		// two separate threads
		#expect(try store.threads(accountId: "acc1").count == 2)
		// msg3 references both, merging the threads
		let msg3 = makeMessage(
			id: "m3", messageId: "msg003@example.com",
			refs: "msg001@example.com msg002@example.com",
			date: "2024-03-08T12:00:00Z"
		)
		try store.insertMessages([msg3])
		try reconstructor.processMessages([msg3])
		#expect(try store.threads(accountId: "acc1").count == 1)
		#expect(try store.threads(accountId: "acc1")[0].messageCount == 3)
	}

	@Test("message without messageId gets its own thread")
	func noMessageId() throws {
		let store = try makeStore()
		try seedAccount(store)
		let msg = makeMessage(id: "m1", messageId: nil)
		try store.insertMessages([msg])
		let reconstructor = ThreadReconstructor(store: store)
		try reconstructor.processMessages([msg])
		let threads = try store.threads(accountId: "acc1")
		#expect(threads.count == 1)
		#expect(threads[0].messageCount == 1)
	}
}
