import Foundation
import GRDB
import Models

extension MailStore {
	/// Full-text search across subject, sender, and body via FTS5
	public func search(query: String) throws -> [MessageRecord] {
		try dbWriter.read { db in
			guard let pattern = FTS5Pattern(matchingAllPrefixesIn: query) else { return [] }
			return try MessageRecord.fetchAll(db, sql: """
				SELECT message.* FROM message
				JOIN messageFts ON messageFts.rowid = message.rowid
				WHERE messageFts MATCH ?
				""", arguments: [pattern])
		}
	}

	/// Thread summaries with unread count and latest sender info, ordered by lastDate DESC
	public func threadSummaries(accountId: String) throws -> [ThreadSummary] {
		try dbWriter.read { db in
			try Self.threadSummariesFromDB(db, accountId: accountId)
		}
	}

	/// Observe thread summaries reactively — UI updates automatically on DB change.
	public func observeThreadSummaries(accountId: String) -> AsyncValueObservation<[ThreadSummary]> {
		ValueObservation.tracking { db -> [ThreadSummary] in
			try Self.threadSummariesFromDB(db, accountId: accountId)
		}.values(in: dbWriter)
	}

	/// Observe messages in a thread reactively
	public func observeMessages(threadId: String) -> AsyncValueObservation<[MessageSummary]> {
		ValueObservation.tracking { db -> [MessageSummary] in
			let records = try MessageRecord.fetchAll(db, sql: """
				SELECT m.* FROM message m
				JOIN threadMessage tm ON tm.messageId = m.id
				WHERE tm.threadId = ?
				ORDER BY m.date ASC
				""", arguments: [threadId])
			return records.map(Self.toMessageSummary)
		}.values(in: dbWriter)
	}

	// MARK: - Internal helpers

	static func threadSummariesFromDB(_ db: Database, accountId: String) throws -> [ThreadSummary] {
		let sql = """
			SELECT
				t.id, t.accountId, t.subject, t.lastDate, t.messageCount,
				(SELECT COUNT(*) FROM threadMessage tm JOIN message m ON m.id = tm.messageId WHERE tm.threadId = t.id AND m.isRead = 0) as unreadCount,
				(SELECT GROUP_CONCAT(DISTINCT m.fromName) FROM threadMessage tm JOIN message m ON m.id = tm.messageId WHERE tm.threadId = t.id AND m.fromName IS NOT NULL) as senders,
				(SELECT m.snippet FROM threadMessage tm JOIN message m ON m.id = tm.messageId WHERE tm.threadId = t.id ORDER BY m.date DESC LIMIT 1) as snippet
			FROM thread t
			WHERE t.accountId = ?
			ORDER BY t.lastDate DESC
			"""
		let rows = try Row.fetchAll(db, sql: sql, arguments: [accountId])
		return rows.map { row in
			ThreadSummary(
				id: row["id"],
				accountId: row["accountId"],
				subject: row["subject"],
				lastDate: parseDate(row["lastDate"] as String? ?? "") ?? Date.distantPast,
				messageCount: row["messageCount"],
				unreadCount: row["unreadCount"],
				senders: row["senders"] ?? "",
				snippet: row["snippet"]
			)
		}
	}

	nonisolated(unsafe) private static let isoFormatterWithFractional: ISO8601DateFormatter = {
		let f = ISO8601DateFormatter()
		f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
		return f
	}()

	nonisolated(unsafe) private static let isoFormatter: ISO8601DateFormatter = {
		ISO8601DateFormatter()
	}()

	static func parseDate(_ iso: String) -> Date? {
		isoFormatterWithFractional.date(from: iso) ?? isoFormatter.date(from: iso)
	}

	public static func toMessageSummary(_ record: MessageRecord) -> MessageSummary {
		MessageSummary(
			id: record.id,
			messageId: record.messageId,
			threadId: nil,
			from: record.fromAddress.map { EmailAddress.parse($0) },
			to: parseAddressList(record.toAddresses),
			cc: parseAddressList(record.ccAddresses),
			subject: record.subject,
			date: parseDate(record.date) ?? Date.distantPast,
			snippet: record.snippet,
			bodyText: record.bodyText,
			bodyHtml: record.bodyHtml,
			isRead: record.isRead,
			isFlagged: record.isFlagged,
			hasAttachments: false
		)
	}

	static func parseAddressList(_ json: String?) -> [EmailAddress] {
		guard let json, let data = json.data(using: .utf8) else { return [] }
		struct Addr: Codable { var name: String?; var address: String }
		guard let addrs = try? JSONDecoder().decode([Addr].self, from: data) else { return [] }
		return addrs.map { EmailAddress(name: $0.name, address: $0.address) }
	}
}
