import Testing
import Foundation
import GRDB
@testable import TaskStore
@testable import MailStore

@Suite("TaskStore")
struct TaskStoreTests {

	private func makeStore() throws -> (TaskStore, URL) {
		let dbWriter = try DatabaseSetup.openInMemoryDatabase()
		// Insert account to satisfy foreign key constraint
		try dbWriter.write { db in
			let account = AccountRecord(
				id: "acct-1",
				name: "Test",
				email: "test@example.com",
				imapHost: "imap.example.com",
				imapPort: 993
			)
			try account.insert(db)
		}
		let tempDir = FileManager.default.temporaryDirectory
			.appendingPathComponent("TaskStoreTests-\(UUID().uuidString)")
		let store = TaskStore(taskDirectory: tempDir, dbWriter: dbWriter)
		return (store, tempDir)
	}

	private func cleanup(_ dir: URL) {
		try? FileManager.default.removeItem(at: dir)
	}

	@Test("writeTask creates file and cache record")
	func writeTaskCreatesFileAndCache() throws {
		let (store, dir) = try makeStore()
		defer { cleanup(dir) }

		try store.writeTask(
			id: "task-001",
			accountId: "acct-1",
			summary: "Test task",
			description: "A test description",
			status: "NEEDS-ACTION",
			priority: 3,
			categories: ["work"]
		)

		// File exists
		let filePath = dir.appendingPathComponent("task-001.ics")
		#expect(FileManager.default.fileExists(atPath: filePath.path))

		// Cache record exists
		let record = try store.dbWriter.read { db in
			try TaskRecord.fetchOne(db, key: "task-001")
		}
		#expect(record != nil)
		#expect(record?.summary == "Test task")
		#expect(record?.description == "A test description")
		#expect(record?.status == "NEEDS-ACTION")
		#expect(record?.priority == 3)
		#expect(record?.accountId == "acct-1")

		// File contains valid VTODO
		let content = try String(contentsOf: filePath, encoding: .utf8)
		let props = VTODOParser.parse(content)
		#expect(props?["UID"] == "task-001")
		#expect(props?["SUMMARY"] == "Test task")
	}

	@Test("updateStatus changes both cache and file")
	func updateStatusChangesBoth() throws {
		let (store, dir) = try makeStore()
		defer { cleanup(dir) }

		try store.writeTask(id: "task-002", accountId: "acct-1", summary: "Status test")
		try store.updateStatus(id: "task-002", status: "COMPLETED")

		// Cache updated
		let record = try store.dbWriter.read { db in
			try TaskRecord.fetchOne(db, key: "task-002")
		}
		#expect(record?.status == "COMPLETED")

		// File updated
		let filePath = dir.appendingPathComponent("task-002.ics")
		let content = try String(contentsOf: filePath, encoding: .utf8)
		let props = VTODOParser.parse(content)
		#expect(props?["STATUS"] == "COMPLETED")
	}

	@Test("deleteTask removes both file and cache")
	func deleteTaskRemovesBoth() throws {
		let (store, dir) = try makeStore()
		defer { cleanup(dir) }

		try store.writeTask(id: "task-003", accountId: "acct-1", summary: "Delete test")

		let filePath = dir.appendingPathComponent("task-003.ics")
		#expect(FileManager.default.fileExists(atPath: filePath.path))

		try store.deleteTask(id: "task-003")

		#expect(!FileManager.default.fileExists(atPath: filePath.path))

		let record = try store.dbWriter.read { db in
			try TaskRecord.fetchOne(db, key: "task-003")
		}
		#expect(record == nil)
	}

	@Test("rebuildCache restores tasks from files")
	func rebuildCacheRestoresFromFiles() throws {
		let (store, dir) = try makeStore()
		defer { cleanup(dir) }

		// Write tasks
		try store.writeTask(
			id: "task-004",
			accountId: "acct-1",
			summary: "Rebuild test 1",
			categories: ["home"]
		)
		try store.writeTask(
			id: "task-005",
			accountId: "acct-1",
			summary: "Rebuild test 2",
			linkedMessageId: "msg@example.com"
		)

		// Clear cache manually
		try store.dbWriter.write { db in
			_ = try TaskRecord.deleteAll(db)
			try db.execute(sql: "DELETE FROM itemLabel WHERE itemType = 'task'")
		}

		// Verify cache is empty
		let countBefore = try store.dbWriter.read { db in
			try TaskRecord.fetchCount(db)
		}
		#expect(countBefore == 0)

		// Rebuild
		try store.rebuildCache(accountId: "acct-1")

		// Verify records restored
		let countAfter = try store.dbWriter.read { db in
			try TaskRecord.fetchCount(db)
		}
		#expect(countAfter == 2)

		let record4 = try store.dbWriter.read { db in
			try TaskRecord.fetchOne(db, key: "task-004")
		}
		#expect(record4?.summary == "Rebuild test 1")

		let record5 = try store.dbWriter.read { db in
			try TaskRecord.fetchOne(db, key: "task-005")
		}
		#expect(record5?.linkedMessageId == "msg@example.com")

		// Verify labels restored
		let labels = try store.dbWriter.read { db in
			try ItemLabelRecord
				.filter(Column("itemType") == "task")
				.filter(Column("itemId") == "task-004")
				.fetchAll(db)
		}
		#expect(labels.count == 1)
	}

	@Test("updateDeferral changes both cache and file")
	func updateDeferralWorks() throws {
		let (store, dir) = try makeStore()
		defer { cleanup(dir) }

		try store.writeTask(id: "task-006", accountId: "acct-1", summary: "Defer test")

		let deferDate = VTODOParser.parseDate("20260320")!
		try store.updateDeferral(id: "task-006", deferUntil: deferDate, isSomeday: false)

		let record = try store.dbWriter.read { db in
			try TaskRecord.fetchOne(db, key: "task-006")
		}
		#expect(record?.deferUntil == "20260320")
		#expect(record?.isSomeday == false)

		// File updated
		let filePath = dir.appendingPathComponent("task-006.ics")
		let content = try String(contentsOf: filePath, encoding: .utf8)
		let props = VTODOParser.parse(content)
		#expect(props?["DTSTART"] == "20260320")
	}

	@Test("writeTask preserves createdAt on update")
	func preserveCreatedAtOnUpdate() throws {
		let (store, dir) = try makeStore()
		defer { cleanup(dir) }

		try store.writeTask(id: "task-007", accountId: "acct-1", summary: "Original")

		let originalRecord = try store.dbWriter.read { db in
			try TaskRecord.fetchOne(db, key: "task-007")
		}
		let originalCreatedAt = originalRecord?.createdAt

		// Update the task
		try store.writeTask(id: "task-007", accountId: "acct-1", summary: "Updated")

		let updatedRecord = try store.dbWriter.read { db in
			try TaskRecord.fetchOne(db, key: "task-007")
		}
		#expect(updatedRecord?.createdAt == originalCreatedAt)
		#expect(updatedRecord?.summary == "Updated")
	}
}
