import GRDB

public enum DatabaseSetup {
	public static func migrator() -> DatabaseMigrator {
		var migrator = DatabaseMigrator()

		migrator.registerMigration("v1_initial") { db in
			try db.create(table: "account") { t in
				t.primaryKey("id", .text)
				t.column("name", .text).notNull()
				t.column("email", .text).notNull()
				t.column("imapHost", .text).notNull()
				t.column("imapPort", .integer).notNull()
			}

			try db.create(table: "mailbox") { t in
				t.primaryKey("id", .text)
				t.belongsTo("account", onDelete: .cascade).notNull()
				t.column("name", .text).notNull()
				t.column("uidValidity", .integer).notNull()
				t.column("uidNext", .integer).notNull()
			}

			try db.create(table: "message") { t in
				t.primaryKey("id", .text)
				t.belongsTo("account", onDelete: .cascade).notNull()
				t.belongsTo("mailbox", onDelete: .cascade).notNull()
				t.column("uid", .integer).notNull()
				t.column("messageId", .text)
				t.column("inReplyTo", .text)
				t.column("refs", .text)
				t.column("subject", .text)
				t.column("fromAddress", .text)
				t.column("fromName", .text)
				t.column("toAddresses", .text)
				t.column("ccAddresses", .text)
				t.column("date", .text).notNull()
				t.column("snippet", .text)
				t.column("bodyText", .text)
				t.column("bodyHtml", .text)
				t.column("isRead", .boolean).notNull().defaults(to: false)
				t.column("isFlagged", .boolean).notNull().defaults(to: false)
				t.column("size", .integer).notNull().defaults(to: 0)
				t.uniqueKey(["mailboxId", "uid"])
			}

			try db.create(table: "thread") { t in
				t.primaryKey("id", .text)
				t.belongsTo("account", onDelete: .cascade).notNull()
				t.column("subject", .text)
				t.column("lastDate", .text).notNull()
				t.column("messageCount", .integer).notNull().defaults(to: 0)
			}

			try db.create(table: "threadMessage") { t in
				t.belongsTo("thread", onDelete: .cascade).notNull()
				t.belongsTo("message", onDelete: .cascade).notNull()
				t.primaryKey(["threadId", "messageId"])
			}

			try db.create(table: "attachment") { t in
				t.primaryKey("id", .text)
				t.belongsTo("message", onDelete: .cascade).notNull()
				t.column("filename", .text)
				t.column("mimeType", .text).notNull()
				t.column("size", .integer).notNull().defaults(to: 0)
				t.column("contentId", .text)
				t.column("cachePath", .text)
			}

			try db.create(index: "idx_message_mailbox_uid", on: "message", columns: ["mailboxId", "uid"])
			try db.create(index: "idx_message_messageId", on: "message", columns: ["messageId"])
			try db.create(index: "idx_thread_lastDate", on: "thread", columns: ["lastDate"])
		}

		migrator.registerMigration("v1_fts5") { db in
			try db.create(virtualTable: "messageFts", using: FTS5()) { t in
				t.synchronize(withTable: "message")
				t.tokenizer = .porter(wrapping: .unicode61())
				t.column("subject")
				t.column("fromName")
				t.column("fromAddress")
				t.column("bodyText")
			}
		}

		migrator.registerMigration("v2_smtp") { db in
			try db.alter(table: "account") { t in
				t.add(column: "smtpHost", .text)
				t.add(column: "smtpPort", .integer)
				t.add(column: "smtpSecurity", .text)
			}
		}

		migrator.registerMigration("v2_mailboxRole") { db in
			try db.alter(table: "mailbox") { t in
				t.add(column: "role", .text)
			}
		}

		migrator.registerMigration("v2_draft") { db in
			try db.create(table: "draft") { t in
				t.primaryKey("id", .text)
				t.belongsTo("account", onDelete: .cascade).notNull()
				t.column("inReplyTo", .text)
				t.column("forwardOf", .text)
				t.column("toAddresses", .text)
				t.column("ccAddresses", .text)
				t.column("bccAddresses", .text)
				t.column("subject", .text)
				t.column("bodyText", .text)
				t.column("createdAt", .text).notNull()
				t.column("updatedAt", .text).notNull()
			}
		}

		migrator.registerMigration("v2_pendingAction") { db in
			try db.create(table: "pendingAction") { t in
				t.primaryKey("id", .text)
				t.belongsTo("account", onDelete: .cascade).notNull()
				t.column("actionType", .text).notNull()
				t.column("payload", .text).notNull()
				t.column("createdAt", .text).notNull()
				t.column("retryCount", .integer).notNull().defaults(to: 0)
				t.column("lastError", .text)
			}
			try db.create(index: "idx_pendingAction_createdAt", on: "pendingAction", columns: ["createdAt"])
		}

		migrator.registerMigration("v3_task") { db in
			try db.create(table: "task") { t in
				t.primaryKey("id", .text)
				t.belongsTo("account", onDelete: .cascade).notNull()
				t.column("summary", .text).notNull()
				t.column("description", .text)
				t.column("status", .text).notNull().defaults(to: "NEEDS-ACTION")
				t.column("priority", .integer).notNull().defaults(to: 0)
				t.column("dueDate", .text)
				t.column("deferUntil", .text)
				t.column("createdAt", .text).notNull()
				t.column("updatedAt", .text).notNull()
				t.column("filePath", .text)
				t.column("linkedMessageId", .text)
				t.column("isSomeday", .boolean).notNull().defaults(to: false)
			}
			try db.create(index: "idx_task_status", on: "task", columns: ["status"])
			try db.create(index: "idx_task_deferUntil", on: "task", columns: ["deferUntil"])
			try db.create(index: "idx_task_dueDate", on: "task", columns: ["dueDate"])
			try db.create(index: "idx_task_linkedMessageId", on: "task", columns: ["linkedMessageId"])
		}

		migrator.registerMigration("v3_label") { db in
			try db.create(table: "label") { t in
				t.primaryKey("id", .text)
				t.belongsTo("account", onDelete: .cascade).notNull()
				t.column("name", .text).notNull()
				t.column("isProject", .boolean).notNull().defaults(to: false)
				t.column("color", .text)
			}
			try db.create(
				index: "idx_label_accountId_name",
				on: "label",
				columns: ["accountId", "name"],
				unique: true
			)

			try db.create(table: "itemLabel") { t in
				t.column("labelId", .text).notNull()
					.references("label", onDelete: .cascade)
				t.column("itemType", .text).notNull()
				t.column("itemId", .text).notNull()
				t.primaryKey(["labelId", "itemType", "itemId"])
			}
		}

		migrator.registerMigration("v3_deferral") { db in
			try db.create(table: "deferral") { t in
				t.primaryKey("id", .text)
				t.column("messageId", .text).notNull()
					.references("message", column: "id", onDelete: .cascade)
				t.column("deferUntil", .text)
				t.column("originalMailbox", .text)
				t.column("createdAt", .text).notNull()
				t.uniqueKey(["messageId"])
			}
			try db.create(index: "idx_deferral_deferUntil", on: "deferral", columns: ["deferUntil"])
		}

		return migrator
	}

	public static func openDatabase(atPath path: String) throws -> DatabasePool {
		let pool = try DatabasePool(path: path)
		try migrator().migrate(pool)
		return pool
	}

	public static func openInMemoryDatabase() throws -> DatabaseQueue {
		let queue = try DatabaseQueue()
		try migrator().migrate(queue)
		return queue
	}
}
