import Testing
import GRDB
@testable import MailStore

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

	func makeAccount(_ store: MailStore) throws {
		try store.insertAccount(AccountRecord(
			id: "acc1", name: "Test", email: "test@example.com",
			imapHost: "imap.example.com", imapPort: 993
		))
	}

	// MARK: - Schema verification

	@Test("v2_smtp migration adds SMTP columns to account table")
	func smtpColumnsExist() throws {
		let store = try makeStore()
		try store.insertAccount(AccountRecord(
			id: "acc1", name: "Test", email: "test@example.com",
			imapHost: "imap.example.com", imapPort: 993,
			smtpHost: "smtp.example.com", smtpPort: 465, smtpSecurity: "ssl"
		))
		let accounts = try store.accounts()
		#expect(accounts[0].smtpHost == "smtp.example.com")
		#expect(accounts[0].smtpPort == 465)
		#expect(accounts[0].smtpSecurity == "ssl")
	}

	@Test("v2_smtp columns default to nil for backwards compatibility")
	func smtpColumnsDefaultNil() throws {
		let store = try makeStore()
		try store.insertAccount(AccountRecord(
			id: "acc1", name: "Test", email: "test@example.com",
			imapHost: "imap.example.com", imapPort: 993
		))
		let accounts = try store.accounts()
		#expect(accounts[0].smtpHost == nil)
		#expect(accounts[0].smtpPort == nil)
		#expect(accounts[0].smtpSecurity == nil)
	}

	@Test("v2_mailboxRole migration adds role column to mailbox table")
	func mailboxRoleColumnExists() throws {
		let store = try makeStore()
		try makeAccount(store)
		try store.upsertMailbox(MailboxRecord(
			id: "mb1", accountId: "acc1", name: "Sent",
			uidValidity: 1, uidNext: 1, role: "sent"
		))
		let mailboxes = try store.mailboxes(accountId: "acc1")
		#expect(mailboxes[0].role == "sent")
	}

	@Test("mailbox role defaults to nil")
	func mailboxRoleDefaultsNil() throws {
		let store = try makeStore()
		try makeAccount(store)
		try store.upsertMailbox(MailboxRecord(
			id: "mb1", accountId: "acc1", name: "INBOX",
			uidValidity: 1, uidNext: 1
		))
		let mailboxes = try store.mailboxes(accountId: "acc1")
		#expect(mailboxes[0].role == nil)
	}

	// MARK: - DraftRecord round-trip

	@Test("DraftRecord insert and fetch round-trip")
	func draftRoundTrip() throws {
		let store = try makeStore()
		try makeAccount(store)

		let draft = DraftRecord(
			id: "d1", accountId: "acc1",
			inReplyTo: "msg123@example.com",
			toAddresses: "[{\"address\":\"bob@example.com\"}]",
			subject: "Re: Hello",
			bodyText: "Thanks for the email",
			createdAt: "2026-03-14T10:00:00Z",
			updatedAt: "2026-03-14T10:00:00Z"
		)
		try store.insertDraft(draft)

		let fetched = try store.draft(id: "d1")
		#expect(fetched != nil)
		#expect(fetched?.accountId == "acc1")
		#expect(fetched?.inReplyTo == "msg123@example.com")
		#expect(fetched?.subject == "Re: Hello")
		#expect(fetched?.bodyText == "Thanks for the email")
	}

	@Test("DraftRecord update persists changes")
	func draftUpdate() throws {
		let store = try makeStore()
		try makeAccount(store)

		var draft = DraftRecord(
			id: "d1", accountId: "acc1",
			subject: "Draft v1",
			bodyText: "Initial",
			createdAt: "2026-03-14T10:00:00Z",
			updatedAt: "2026-03-14T10:00:00Z"
		)
		try store.insertDraft(draft)

		draft.subject = "Draft v2"
		draft.bodyText = "Updated body"
		draft.updatedAt = "2026-03-14T10:05:00Z"
		try store.updateDraft(draft)

		let fetched = try store.draft(id: "d1")
		#expect(fetched?.subject == "Draft v2")
		#expect(fetched?.bodyText == "Updated body")
	}

	@Test("DraftRecord delete removes record")
	func draftDelete() throws {
		let store = try makeStore()
		try makeAccount(store)

		try store.insertDraft(DraftRecord(
			id: "d1", accountId: "acc1",
			createdAt: "2026-03-14T10:00:00Z",
			updatedAt: "2026-03-14T10:00:00Z"
		))
		try store.deleteDraft(id: "d1")

		let fetched = try store.draft(id: "d1")
		#expect(fetched == nil)
	}

	@Test("drafts(accountId:) returns drafts for account")
	func draftsByAccount() throws {
		let store = try makeStore()
		try makeAccount(store)

		try store.insertDraft(DraftRecord(
			id: "d1", accountId: "acc1", subject: "First",
			createdAt: "2026-03-14T10:00:00Z",
			updatedAt: "2026-03-14T10:00:00Z"
		))
		try store.insertDraft(DraftRecord(
			id: "d2", accountId: "acc1", subject: "Second",
			createdAt: "2026-03-14T10:01:00Z",
			updatedAt: "2026-03-14T10:05:00Z"
		))

		let drafts = try store.drafts(accountId: "acc1")
		#expect(drafts.count == 2)
		// ordered by updatedAt desc
		#expect(drafts[0].id == "d2")
		#expect(drafts[1].id == "d1")
	}

	// MARK: - PendingActionRecord round-trip

	@Test("PendingActionRecord insert and fetch round-trip")
	func pendingActionRoundTrip() throws {
		let store = try makeStore()
		try makeAccount(store)

		let action = PendingActionRecord(
			id: "pa1", accountId: "acc1",
			actionType: "move",
			payload: "{\"messageId\":\"m1\",\"toMailbox\":\"trash\"}",
			createdAt: "2026-03-14T10:00:00Z"
		)
		try store.insertPendingAction(action)

		let actions = try store.pendingActions(accountId: "acc1")
		#expect(actions.count == 1)
		#expect(actions[0].actionType == "move")
		#expect(actions[0].retryCount == 0)
		#expect(actions[0].lastError == nil)
	}

	@Test("PendingActionRecord update persists retryCount and lastError")
	func pendingActionUpdate() throws {
		let store = try makeStore()
		try makeAccount(store)

		var action = PendingActionRecord(
			id: "pa1", accountId: "acc1",
			actionType: "send",
			payload: "{}",
			createdAt: "2026-03-14T10:00:00Z"
		)
		try store.insertPendingAction(action)

		action.retryCount = 3
		action.lastError = "Connection timeout"
		try store.updatePendingAction(action)

		let actions = try store.pendingActions(accountId: "acc1")
		#expect(actions[0].retryCount == 3)
		#expect(actions[0].lastError == "Connection timeout")
	}

	@Test("PendingActionRecord delete removes record")
	func pendingActionDelete() throws {
		let store = try makeStore()
		try makeAccount(store)

		try store.insertPendingAction(PendingActionRecord(
			id: "pa1", accountId: "acc1",
			actionType: "move", payload: "{}",
			createdAt: "2026-03-14T10:00:00Z"
		))
		try store.deletePendingAction(id: "pa1")

		let count = try store.pendingActionCount(accountId: "acc1")
		#expect(count == 0)
	}

	@Test("pendingActions ordered by createdAt ASC")
	func pendingActionsOrdering() throws {
		let store = try makeStore()
		try makeAccount(store)

		try store.insertPendingActions([
			PendingActionRecord(
				id: "pa2", accountId: "acc1",
				actionType: "move", payload: "{}",
				createdAt: "2026-03-14T10:05:00Z"
			),
			PendingActionRecord(
				id: "pa1", accountId: "acc1",
				actionType: "send", payload: "{}",
				createdAt: "2026-03-14T10:00:00Z"
			),
		])

		let actions = try store.pendingActions(accountId: "acc1")
		#expect(actions[0].id == "pa1")
		#expect(actions[1].id == "pa2")
	}

	@Test("pendingActionCount returns correct count")
	func pendingActionCount() throws {
		let store = try makeStore()
		try makeAccount(store)

		try store.insertPendingActions([
			PendingActionRecord(
				id: "pa1", accountId: "acc1",
				actionType: "send", payload: "{}",
				createdAt: "2026-03-14T10:00:00Z"
			),
			PendingActionRecord(
				id: "pa2", accountId: "acc1",
				actionType: "move", payload: "{}",
				createdAt: "2026-03-14T10:01:00Z"
			),
		])

		let count = try store.pendingActionCount(accountId: "acc1")
		#expect(count == 2)
	}

	// MARK: - Mailbox role queries

	@Test("mailboxWithRole finds mailbox by role")
	func mailboxWithRole() throws {
		let store = try makeStore()
		try makeAccount(store)
		try store.upsertMailbox(MailboxRecord(
			id: "mb1", accountId: "acc1", name: "Trash",
			uidValidity: 1, uidNext: 1, role: "trash"
		))
		try store.upsertMailbox(MailboxRecord(
			id: "mb2", accountId: "acc1", name: "INBOX",
			uidValidity: 1, uidNext: 1
		))

		let trash = try store.mailboxWithRole("trash", accountId: "acc1")
		#expect(trash?.id == "mb1")

		let archive = try store.mailboxWithRole("archive", accountId: "acc1")
		#expect(archive == nil)
	}

	@Test("updateMailboxRole sets role on mailbox")
	func updateMailboxRole() throws {
		let store = try makeStore()
		try makeAccount(store)
		try store.upsertMailbox(MailboxRecord(
			id: "mb1", accountId: "acc1", name: "Archive",
			uidValidity: 1, uidNext: 1
		))

		try store.updateMailboxRole(id: "mb1", role: "archive")
		let mailbox = try store.mailbox(id: "mb1")
		#expect(mailbox?.role == "archive")

		try store.updateMailboxRole(id: "mb1", role: nil)
		let cleared = try store.mailbox(id: "mb1")
		#expect(cleared?.role == nil)
	}

	// MARK: - Message mutations

	@Test("updateMessageMailbox moves message to new mailbox")
	func updateMessageMailbox() throws {
		let store = try makeStore()
		try makeAccount(store)
		try store.upsertMailbox(MailboxRecord(
			id: "mb1", accountId: "acc1", name: "INBOX",
			uidValidity: 1, uidNext: 100
		))
		try store.upsertMailbox(MailboxRecord(
			id: "mb2", accountId: "acc1", name: "Trash",
			uidValidity: 1, uidNext: 1, role: "trash"
		))
		try store.insertMessages([
			MessageRecord(
				id: "m1", accountId: "acc1", mailboxId: "mb1", uid: 1,
				messageId: nil, inReplyTo: nil, refs: nil,
				subject: "Test", fromAddress: nil, fromName: nil,
				toAddresses: nil, ccAddresses: nil,
				date: "2026-03-14T10:00:00Z", snippet: nil,
				bodyText: nil, bodyHtml: nil,
				isRead: false, isFlagged: false, size: 0
			),
		])

		try store.updateMessageMailbox(messageId: "m1", newMailboxId: "mb2")
		let msg = try store.message(id: "m1")
		#expect(msg?.mailboxId == "mb2")
	}

	@Test("deleteMessage removes message")
	func deleteMessage() throws {
		let store = try makeStore()
		try makeAccount(store)
		try store.upsertMailbox(MailboxRecord(
			id: "mb1", accountId: "acc1", name: "INBOX",
			uidValidity: 1, uidNext: 100
		))
		try store.insertMessages([
			MessageRecord(
				id: "m1", accountId: "acc1", mailboxId: "mb1", uid: 1,
				messageId: nil, inReplyTo: nil, refs: nil,
				subject: "Test", fromAddress: nil, fromName: nil,
				toAddresses: nil, ccAddresses: nil,
				date: "2026-03-14T10:00:00Z", snippet: nil,
				bodyText: nil, bodyHtml: nil,
				isRead: false, isFlagged: false, size: 0
			),
		])

		try store.deleteMessage(id: "m1")
		let msg = try store.message(id: "m1")
		#expect(msg == nil)
	}

	@Test("messagesInThread filters by mailbox")
	func messagesInThreadByMailbox() throws {
		let store = try makeStore()
		try makeAccount(store)
		try store.upsertMailbox(MailboxRecord(
			id: "mb1", accountId: "acc1", name: "INBOX",
			uidValidity: 1, uidNext: 100
		))
		try store.upsertMailbox(MailboxRecord(
			id: "mb2", accountId: "acc1", name: "Sent",
			uidValidity: 1, uidNext: 1, role: "sent"
		))
		try store.insertMessages([
			MessageRecord(
				id: "m1", accountId: "acc1", mailboxId: "mb1", uid: 1,
				messageId: "msg1@example.com", inReplyTo: nil, refs: nil,
				subject: "Hello", fromAddress: "alice@example.com", fromName: "Alice",
				toAddresses: nil, ccAddresses: nil,
				date: "2026-03-14T10:00:00Z", snippet: nil,
				bodyText: nil, bodyHtml: nil,
				isRead: false, isFlagged: false, size: 0
			),
			MessageRecord(
				id: "m2", accountId: "acc1", mailboxId: "mb2", uid: 1,
				messageId: "msg2@example.com", inReplyTo: "msg1@example.com", refs: nil,
				subject: "Re: Hello", fromAddress: "user@example.com", fromName: "User",
				toAddresses: nil, ccAddresses: nil,
				date: "2026-03-14T10:01:00Z", snippet: nil,
				bodyText: nil, bodyHtml: nil,
				isRead: true, isFlagged: false, size: 0
			),
		])
		try store.insertThread(ThreadRecord(
			id: "t1", accountId: "acc1", subject: "Hello",
			lastDate: "2026-03-14T10:01:00Z", messageCount: 2
		))
		try store.linkMessageToThread(threadId: "t1", messageId: "m1")
		try store.linkMessageToThread(threadId: "t1", messageId: "m2")

		let inboxMsgs = try store.messagesInThread(threadId: "t1", mailboxId: "mb1")
		#expect(inboxMsgs.count == 1)
		#expect(inboxMsgs[0].id == "m1")

		let sentMsgs = try store.messagesInThread(threadId: "t1", mailboxId: "mb2")
		#expect(sentMsgs.count == 1)
		#expect(sentMsgs[0].id == "m2")
	}
}
