import SwiftUI
import Models
import MailStore
import SyncEngine
import SMTPClient

enum ComposeMode: Sendable {
	case new
	case reply(to: MessageSummary)
	case replyAll(to: MessageSummary)
	case forward(of: MessageSummary)
	case draft(DraftRecord)
}

@Observable
@MainActor
final class ComposeViewModel {
	var to: String = ""
	var cc: String = ""
	var bcc: String = ""
	var subject: String = ""
	var bodyText: String = ""

	var isSending: Bool = false
	var errorMessage: String?

	let mode: ComposeMode

	private let accountConfig: AccountConfig
	private let store: MailStore
	private let actionQueue: ActionQueue

	private var draftId: String?
	private var savedTo: String = ""
	private var savedCc: String = ""
	private var savedBcc: String = ""
	private var savedSubject: String = ""
	private var savedBodyText: String = ""
	private var autoSaveTask: Task<Void, Never>?

	var isDirty: Bool {
		to != savedTo || cc != savedCc || bcc != savedBcc
			|| subject != savedSubject || bodyText != savedBodyText
	}

	init(mode: ComposeMode, accountConfig: AccountConfig, store: MailStore, actionQueue: ActionQueue) {
		self.mode = mode
		self.accountConfig = accountConfig
		self.store = store
		self.actionQueue = actionQueue

		prefill(mode: mode)
		snapshotSavedState()
		startAutoSave()
	}

	nonisolated deinit {
		// autoSaveTask cancels itself when the ViewModel is deallocated
		// since it holds a weak reference to self
	}

	// MARK: - Send

	func send() async throws {
		isSending = true
		errorMessage = nil

		do {
			let toAddresses = parseAddressList(to)
			guard !toAddresses.isEmpty else {
				throw ComposeError.noRecipients
			}

			let ccAddresses = parseAddressList(cc)
			let bccAddresses = parseAddressList(bcc)

			let domain = MessageFormatter.domainFromEmail(accountConfig.email)
			let messageId = MessageFormatter.generateMessageId(domain: domain)
			let from = EmailAddress(name: accountConfig.name, address: accountConfig.email)

			var inReplyTo: String?
			var references: String?

			switch mode {
			case .reply(let original), .replyAll(let original):
				inReplyTo = original.messageId
				if let origId = original.messageId {
					references = "<\(origId)>"
				}
			case .forward(let original):
				if let origId = original.messageId {
					references = "<\(origId)>"
				}
			case .new, .draft:
				break
			}

			let outgoing = OutgoingMessage(
				from: from,
				to: toAddresses,
				cc: ccAddresses,
				bcc: bccAddresses,
				subject: subject,
				bodyText: bodyText,
				inReplyTo: inReplyTo,
				references: references,
				messageId: messageId
			)

			let formatted = MessageFormatter.format(outgoing)

			let sendAction = PendingAction(
				accountId: accountConfig.id,
				actionType: .send,
				payload: .send(message: outgoing)
			)
			let appendAction = PendingAction(
				accountId: accountConfig.id,
				actionType: .append,
				payload: .append(mailbox: "Sent", messageData: formatted, flags: ["\\Seen"])
			)

			try await actionQueue.enqueue(sendAction)
			try await actionQueue.enqueue(appendAction)

			if let draftId {
				try? store.deleteDraft(id: draftId)
				self.draftId = nil
			}

			snapshotSavedState()
			isSending = false
		} catch {
			isSending = false
			errorMessage = error.localizedDescription
			throw error
		}
	}

	// MARK: - Draft Management

	func saveDraft() {
		let now = ISO8601DateFormatter().string(from: Date())
		let id = draftId ?? UUID().uuidString

		var inReplyTo: String?
		var forwardOf: String?

		switch mode {
		case .reply(let msg), .replyAll(let msg):
			inReplyTo = msg.messageId
		case .forward(let msg):
			forwardOf = msg.messageId
		case .new, .draft:
			break
		}

		let draft = DraftRecord(
			id: id,
			accountId: accountConfig.id,
			inReplyTo: inReplyTo,
			forwardOf: forwardOf,
			toAddresses: encodeAddresses(to),
			ccAddresses: encodeAddresses(cc),
			bccAddresses: encodeAddresses(bcc),
			subject: subject,
			bodyText: bodyText,
			createdAt: draftId == nil ? now : (try? store.draft(id: id))?.createdAt ?? now,
			updatedAt: now
		)

		do {
			if draftId != nil {
				try store.updateDraft(draft)
			} else {
				try store.insertDraft(draft)
				draftId = id
			}
			snapshotSavedState()
		} catch {
			errorMessage = error.localizedDescription
		}
	}

	func deleteDraft() {
		guard let draftId else { return }
		try? store.deleteDraft(id: draftId)
		self.draftId = nil
	}

	// MARK: - Prefill

	private func prefill(mode: ComposeMode) {
		switch mode {
		case .new:
			break

		case .reply(let original):
			if let sender = original.from {
				to = MessageFormatter.formatAddress(sender)
			}
			subject = prefixSubject("Re:", original.subject ?? "")
			bodyText = quoteBody(original)

		case .replyAll(let original):
			if let sender = original.from {
				to = MessageFormatter.formatAddress(sender)
			}
			// CC = all other recipients minus self
			let selfAddress = accountConfig.email.lowercased()
			let others = (original.to + original.cc)
				.filter { $0.address.lowercased() != selfAddress }
				.filter { $0.address.lowercased() != original.from?.address.lowercased() }
			cc = others.map(MessageFormatter.formatAddress).joined(separator: ", ")
			subject = prefixSubject("Re:", original.subject ?? "")
			bodyText = quoteBody(original)

		case .forward(let original):
			subject = prefixSubject("Fwd:", original.subject ?? "")
			bodyText = forwardBody(original)

		case .draft(let draft):
			to = draft.toAddresses ?? ""
			cc = draft.ccAddresses ?? ""
			bcc = draft.bccAddresses ?? ""
			subject = draft.subject ?? ""
			bodyText = draft.bodyText ?? ""
			draftId = draft.id
		}
	}

	// MARK: - Auto-Save

	private func startAutoSave() {
		autoSaveTask = Task { [weak self] in
			while !Task.isCancelled {
				try? await Task.sleep(for: .seconds(10))
				guard !Task.isCancelled else { break }
				guard let self else { break }
				if self.isDirty && !self.isSending {
					self.saveDraft()
				}
			}
		}
	}

	private func snapshotSavedState() {
		savedTo = to
		savedCc = cc
		savedBcc = bcc
		savedSubject = subject
		savedBodyText = bodyText
	}

	// MARK: - Helpers

	func prefixSubject(_ prefix: String, _ original: String) -> String {
		// Strip existing Re:/Fwd: prefixes (case-insensitive)
		var stripped = original
		while let range = stripped.range(
			of: #"^(Re|Fwd|Fw)\s*:\s*"#,
			options: [.regularExpression, .caseInsensitive]
		) {
			stripped = String(stripped[range.upperBound...])
		}
		return "\(prefix) \(stripped)"
	}

	func quoteBody(_ msg: MessageSummary) -> String {
		let dateFormatter = DateFormatter()
		dateFormatter.dateStyle = .medium
		dateFormatter.timeStyle = .short
		let dateString = dateFormatter.string(from: msg.date)
		let sender = msg.from?.displayName ?? "Unknown"
		let originalBody = msg.bodyText ?? ""
		let quoted = originalBody
			.components(separatedBy: .newlines)
			.map { "> \($0)" }
			.joined(separator: "\n")
		return "\n\nOn \(dateString), \(sender) wrote:\n\(quoted)"
	}

	func forwardBody(_ msg: MessageSummary) -> String {
		let dateFormatter = DateFormatter()
		dateFormatter.dateStyle = .medium
		dateFormatter.timeStyle = .short
		let dateString = dateFormatter.string(from: msg.date)
		let from = msg.from.map(MessageFormatter.formatAddress) ?? "Unknown"
		let toList = msg.to.map(MessageFormatter.formatAddress).joined(separator: ", ")
		let subjectLine = msg.subject ?? "(No Subject)"
		let body = msg.bodyText ?? ""

		return """


		---------- Forwarded message ----------
		From: \(from)
		Date: \(dateString)
		Subject: \(subjectLine)
		To: \(toList)

		\(body)
		"""
	}

	func parseAddressList(_ field: String) -> [EmailAddress] {
		let trimmed = field.trimmingCharacters(in: .whitespacesAndNewlines)
		guard !trimmed.isEmpty else { return [] }
		return trimmed
			.components(separatedBy: ",")
			.map { $0.trimmingCharacters(in: .whitespaces) }
			.filter { !$0.isEmpty }
			.map(EmailAddress.parse)
	}

	private func encodeAddresses(_ field: String) -> String? {
		let trimmed = field.trimmingCharacters(in: .whitespacesAndNewlines)
		return trimmed.isEmpty ? nil : trimmed
	}
}

enum ComposeError: LocalizedError {
	case noRecipients

	var errorDescription: String? {
		switch self {
		case .noRecipients:
			return "At least one recipient is required."
		}
	}
}
