import Testing
import GRDB
@testable import MailStore

@Suite("V3 Labels, Deferrals & Tasks")
struct LabelDeferralTests {
	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
		))
	}

	func makeMailboxAndMessage(_ store: MailStore) throws {
		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: "msg1@example.com", inReplyTo: nil, refs: nil,
				subject: "Test", 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: 100
			),
		])
	}

	// MARK: - Schema verification

	@Test("v3 tables exist after migration")
	func v3TablesExist() throws {
		let store = try makeStore()
		let tables = try store.databaseWriter.read { db in
			try String.fetchAll(db, sql: """
				SELECT name FROM sqlite_master WHERE type='table'
				AND name IN ('task', 'label', 'itemLabel', 'deferral')
				ORDER BY name
				""")
		}
		#expect(tables == ["deferral", "itemLabel", "label", "task"])
	}

	@Test("task table has expected columns")
	func taskColumns() throws {
		let store = try makeStore()
		let columns = try store.databaseWriter.read { db in
			try db.columns(in: "task").map(\.name)
		}
		let expected = [
			"id", "accountId", "summary", "description", "status",
			"priority", "dueDate", "deferUntil", "createdAt", "updatedAt",
			"filePath", "linkedMessageId", "isSomeday",
		]
		for col in expected {
			#expect(columns.contains(col), "missing column: \(col)")
		}
	}

	// MARK: - LabelRecord round-trip

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

		let label = LabelRecord(
			id: "lbl1", accountId: "acc1", name: "urgent",
			isProject: false, color: "#ff0000"
		)
		try store.insertLabel(label)

		let labels = try store.labels(accountId: "acc1")
		#expect(labels.count == 1)
		#expect(labels[0].name == "urgent")
		#expect(labels[0].color == "#ff0000")
		#expect(labels[0].isProject == false)
	}

	@Test("labelByName finds label by account and name")
	func labelByName() throws {
		let store = try makeStore()
		try makeAccount(store)

		try store.insertLabel(LabelRecord(
			id: "lbl1", accountId: "acc1", name: "work"
		))

		let found = try store.labelByName("work", accountId: "acc1")
		#expect(found?.id == "lbl1")

		let notFound = try store.labelByName("personal", accountId: "acc1")
		#expect(notFound == nil)
	}

	@Test("projectLabels returns only project labels")
	func projectLabels() throws {
		let store = try makeStore()
		try makeAccount(store)

		try store.insertLabel(LabelRecord(
			id: "lbl1", accountId: "acc1", name: "context", isProject: false
		))
		try store.insertLabel(LabelRecord(
			id: "lbl2", accountId: "acc1", name: "my-project", isProject: true
		))

		let projects = try store.projectLabels(accountId: "acc1")
		#expect(projects.count == 1)
		#expect(projects[0].name == "my-project")
	}

	@Test("deleteLabel removes label and cascades to itemLabel")
	func deleteLabelCascade() throws {
		let store = try makeStore()
		try makeAccount(store)

		try store.insertLabel(LabelRecord(
			id: "lbl1", accountId: "acc1", name: "test"
		))
		try store.attachLabel(labelId: "lbl1", itemType: "email", itemId: "m1")
		try store.deleteLabel(id: "lbl1")

		let labels = try store.labels(accountId: "acc1")
		#expect(labels.isEmpty)

		let items = try store.itemsWithLabel(labelId: "lbl1")
		#expect(items.isEmpty)
	}

	// MARK: - ItemLabelRecord round-trip

	@Test("ItemLabelRecord attach and fetch round-trip")
	func itemLabelRoundTrip() throws {
		let store = try makeStore()
		try makeAccount(store)

		try store.insertLabel(LabelRecord(
			id: "lbl1", accountId: "acc1", name: "important"
		))
		try store.attachLabel(labelId: "lbl1", itemType: "email", itemId: "m1")

		let labels = try store.labelsForItem(itemType: "email", itemId: "m1")
		#expect(labels.count == 1)
		#expect(labels[0].id == "lbl1")

		let items = try store.itemsWithLabel(labelId: "lbl1")
		#expect(items.count == 1)
		#expect(items[0].itemId == "m1")
	}

	@Test("detachLabel removes item-label link")
	func detachLabel() throws {
		let store = try makeStore()
		try makeAccount(store)

		try store.insertLabel(LabelRecord(
			id: "lbl1", accountId: "acc1", name: "test"
		))
		try store.attachLabel(labelId: "lbl1", itemType: "email", itemId: "m1")
		try store.detachLabel(labelId: "lbl1", itemType: "email", itemId: "m1")

		let labels = try store.labelsForItem(itemType: "email", itemId: "m1")
		#expect(labels.isEmpty)
	}

	// MARK: - DeferralRecord round-trip

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

		let deferral = DeferralRecord(
			id: "def1", messageId: "m1",
			deferUntil: "2026-03-15T09:00:00Z",
			originalMailbox: "mb1",
			createdAt: "2026-03-14T10:00:00Z"
		)
		try store.insertDeferral(deferral)

		let fetched = try store.deferralForMessage(messageId: "m1")
		#expect(fetched != nil)
		#expect(fetched?.deferUntil == "2026-03-15T09:00:00Z")
		#expect(fetched?.originalMailbox == "mb1")
	}

	@Test("deferral cascade on message delete")
	func deferralCascadeOnMessageDelete() throws {
		let store = try makeStore()
		try makeAccount(store)
		try makeMailboxAndMessage(store)

		try store.insertDeferral(DeferralRecord(
			id: "def1", messageId: "m1",
			deferUntil: "2026-03-15T09:00:00Z",
			createdAt: "2026-03-14T10:00:00Z"
		))

		try store.deleteMessage(id: "m1")

		let fetched = try store.deferralForMessage(messageId: "m1")
		#expect(fetched == nil)
	}

	@Test("expiredDeferrals returns deferrals before given date")
	func expiredDeferrals() throws {
		let store = try makeStore()
		try makeAccount(store)
		try makeMailboxAndMessage(store)

		// Add second message for second deferral
		try store.insertMessages([
			MessageRecord(
				id: "m2", accountId: "acc1", mailboxId: "mb1", uid: 2,
				messageId: "msg2@example.com", inReplyTo: nil, refs: nil,
				subject: "Future", 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.insertDeferral(DeferralRecord(
			id: "def1", messageId: "m1",
			deferUntil: "2026-03-14T08:00:00Z",
			createdAt: "2026-03-13T10:00:00Z"
		))
		try store.insertDeferral(DeferralRecord(
			id: "def2", messageId: "m2",
			deferUntil: "2026-03-16T08:00:00Z",
			createdAt: "2026-03-13T10:00:00Z"
		))

		let expired = try store.expiredDeferrals(beforeDate: "2026-03-15T00:00:00Z")
		#expect(expired.count == 1)
		#expect(expired[0].id == "def1")
	}

	@Test("somedayDeferrals returns deferrals with nil deferUntil")
	func somedayDeferrals() throws {
		let store = try makeStore()
		try makeAccount(store)
		try makeMailboxAndMessage(store)

		try store.insertDeferral(DeferralRecord(
			id: "def1", messageId: "m1",
			deferUntil: nil,
			createdAt: "2026-03-14T10:00:00Z"
		))

		let someday = try store.somedayDeferrals()
		#expect(someday.count == 1)
		#expect(someday[0].id == "def1")
	}

	// MARK: - TaskRecord round-trip

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

		let task = TaskRecord(
			id: "t1", accountId: "acc1", summary: "Buy groceries",
			description: "Milk, eggs, bread",
			status: "NEEDS-ACTION", priority: 5,
			dueDate: "2026-03-15",
			createdAt: "2026-03-14T10:00:00Z",
			updatedAt: "2026-03-14T10:00:00Z",
			linkedMessageId: "msg1@example.com"
		)
		try store.insertTask(task)

		let fetched = try store.task(id: "t1")
		#expect(fetched != nil)
		#expect(fetched?.summary == "Buy groceries")
		#expect(fetched?.description == "Milk, eggs, bread")
		#expect(fetched?.priority == 5)
		#expect(fetched?.linkedMessageId == "msg1@example.com")
	}

	@Test("inboxTasks returns only active non-deferred non-someday tasks")
	func inboxTasks() throws {
		let store = try makeStore()
		try makeAccount(store)

		// Inbox task
		try store.insertTask(TaskRecord(
			id: "t1", accountId: "acc1", summary: "Inbox task",
			createdAt: "2026-03-14T10:00:00Z",
			updatedAt: "2026-03-14T10:00:00Z"
		))
		// Deferred task
		try store.insertTask(TaskRecord(
			id: "t2", accountId: "acc1", summary: "Deferred",
			deferUntil: "2026-03-20T10:00:00Z",
			createdAt: "2026-03-14T10:00:00Z",
			updatedAt: "2026-03-14T10:00:00Z"
		))
		// Someday task
		try store.insertTask(TaskRecord(
			id: "t3", accountId: "acc1", summary: "Someday",
			createdAt: "2026-03-14T10:00:00Z",
			updatedAt: "2026-03-14T10:00:00Z",
			isSomeday: true
		))
		// Completed task
		try store.insertTask(TaskRecord(
			id: "t4", accountId: "acc1", summary: "Done",
			status: "COMPLETED",
			createdAt: "2026-03-14T10:00:00Z",
			updatedAt: "2026-03-14T10:00:00Z"
		))

		let inbox = try store.inboxTasks(accountId: "acc1")
		#expect(inbox.count == 1)
		#expect(inbox[0].id == "t1")
	}

	@Test("deleteAllTasks removes all tasks")
	func deleteAllTasks() throws {
		let store = try makeStore()
		try makeAccount(store)

		try store.insertTask(TaskRecord(
			id: "t1", accountId: "acc1", summary: "Task 1",
			createdAt: "2026-03-14T10:00:00Z",
			updatedAt: "2026-03-14T10:00:00Z"
		))
		try store.insertTask(TaskRecord(
			id: "t2", accountId: "acc1", summary: "Task 2",
			createdAt: "2026-03-14T10:00:00Z",
			updatedAt: "2026-03-14T10:00:00Z"
		))

		try store.deleteAllTasks()
		let remaining = try store.task(id: "t1")
		#expect(remaining == nil)
	}
}
