import Testing
import GRDB
@testable import SyncEngine
@testable import IMAPClient
@testable import MailStore
@testable import Models

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

	func makeMock() -> MockIMAPClient {
		let mock = MockIMAPClient()
		mock.mailboxes = [
			IMAPMailboxInfo(name: "INBOX"),
			IMAPMailboxInfo(name: "Sent"),
		]
		mock.mailboxStatuses = [
			"INBOX": IMAPMailboxStatus(name: "INBOX", uidValidity: 1, uidNext: 3, messageCount: 2, recentCount: 0),
			"Sent": IMAPMailboxStatus(name: "Sent", uidValidity: 1, uidNext: 1, messageCount: 0, recentCount: 0),
		]
		mock.mailboxEnvelopes = [
			"INBOX": [
				FetchedEnvelope(
					uid: 1, messageId: "msg001@example.com", inReplyTo: nil, references: nil,
					subject: "Hello", from: EmailAddress(name: "Alice", address: "alice@example.com"),
					to: [EmailAddress(address: "me@example.com")], cc: [],
					date: "2024-03-08T10:00:00Z", snippet: "Hi there",
					bodyText: nil, bodyHtml: nil, isRead: false, isFlagged: false, size: 1024
				),
				FetchedEnvelope(
					uid: 2, messageId: "msg002@example.com", inReplyTo: "msg001@example.com",
					references: "msg001@example.com",
					subject: "Re: Hello", from: EmailAddress(name: "Bob", address: "bob@example.com"),
					to: [EmailAddress(address: "alice@example.com")], cc: [],
					date: "2024-03-08T11:00:00Z", snippet: "Hey!",
					bodyText: nil, bodyHtml: nil, isRead: true, isFlagged: false, size: 512
				),
			],
			"Sent": [],
		]
		return mock
	}

	@Test("full sync creates account, mailboxes, messages, and threads")
	func fullSync() async throws {
		let store = try makeStore()
		let mock = makeMock()
		let coordinator = SyncCoordinator(
			accountConfig: AccountConfig(
				id: "acc1", name: "Personal", email: "me@example.com",
				imapHost: "imap.example.com", imapPort: 993
			),
			imapClient: mock,
			store: store
		)

		try await coordinator.syncNow()

		// Account created
		let accounts = try store.accounts()
		#expect(accounts.count == 1)

		// Mailboxes created
		let mailboxes = try store.mailboxes(accountId: "acc1")
		#expect(mailboxes.count == 2)

		// Messages stored
		let inboxMb = mailboxes.first { $0.name == "INBOX" }!
		let messages = try store.messages(mailboxId: inboxMb.id)
		#expect(messages.count == 2)

		// Threads created (msg002 replies to msg001, so 1 thread)
		let threads = try store.threads(accountId: "acc1")
		#expect(threads.count == 1)
		#expect(threads[0].messageCount == 2)

		// uidNext updated
		let updatedMb = try store.mailbox(id: inboxMb.id)
		#expect(updatedMb?.uidNext == 3)

		// IMAP client was connected and disconnected
		#expect(mock.connectCalled)
		#expect(mock.disconnectCalled)
	}

	@Test("delta sync only fetches new messages")
	func deltaSync() async throws {
		let store = try makeStore()
		let mock = makeMock()
		let config = AccountConfig(
			id: "acc1", name: "Personal", email: "me@example.com",
			imapHost: "imap.example.com", imapPort: 993
		)
		let coordinator = SyncCoordinator(accountConfig: config, imapClient: mock, store: store)

		// First sync
		try await coordinator.syncNow()

		// Add a new message for delta sync
		mock.mailboxEnvelopes["INBOX"]!.append(FetchedEnvelope(
			uid: 3, messageId: "msg003@example.com", inReplyTo: nil, references: nil,
			subject: "New message", from: EmailAddress(name: "Charlie", address: "charlie@example.com"),
			to: [EmailAddress(address: "me@example.com")], cc: [],
			date: "2024-03-09T10:00:00Z", snippet: "Something new",
			bodyText: nil, bodyHtml: nil, isRead: false, isFlagged: false, size: 256
		))
		mock.mailboxStatuses["INBOX"] = IMAPMailboxStatus(
			name: "INBOX", uidValidity: 1, uidNext: 4, messageCount: 3, recentCount: 1
		)

		// Second sync — should only fetch uid > 2
		try await coordinator.syncNow()

		let inboxMb = try store.mailboxes(accountId: "acc1").first { $0.name == "INBOX" }!
		let messages = try store.messages(mailboxId: inboxMb.id)
		#expect(messages.count == 3)
	}

	@Test("sync state transitions through syncing to idle")
	func syncStateTransitions() async throws {
		let store = try makeStore()
		let mock = makeMock()
		let coordinator = SyncCoordinator(
			accountConfig: AccountConfig(
				id: "acc1", name: "Personal", email: "me@example.com",
				imapHost: "imap.example.com", imapPort: 993
			),
			imapClient: mock,
			store: store
		)

		#expect(coordinator.syncState == .idle)
		try await coordinator.syncNow()
		#expect(coordinator.syncState == .idle)
	}
}
