import AppKit
import Foundation
import LilithLogging
import MacSyncShared

private let log = AppLogger.logger(for: "IMail.Sync")

// MARK: - Stats

public struct IMailSyncStats: Equatable, Sendable {
    public var totalEmails: Int = 0
    public var totalFolders: Int = 0
    public var syncedThisSession: Int = 0
    public init() {}
}

// MARK: - Sync Error

public enum IMailSyncError: Equatable, Sendable {
    case none
    case mailAppUnavailable
    case automationDenied
    case connectionFailed(String)

    public var message: String {
        switch self {
        case .none: return ""
        case .mailAppUnavailable: return "Mail.app unavailable — ensure it is running"
        case .automationDenied: return "Automation access denied — check System Settings > Privacy > Automation"
        case .connectionFailed(let m): return m
        }
    }
}

// MARK: - SyncManager

/// Orchestrates iMail sync: read messages from Mail.app → push to server in batches.
///
/// Watermark: handled by `BaseSyncManager` — `lastSync` is updated at the end of every
/// cycle (cycle-completion time, not the latest-message date). The reader applies a
/// ±1-minute tolerance which makes the cycle-time watermark safe for catching late
/// arrivals on the next cycle.
///
/// Runs in parallel with the server-side IMAP module during transition. De-duplication
/// is handled server-side via `externalId` (Message-ID).
@MainActor
public final class SyncManager: BaseSyncManager<IMailSyncStats, IMailSyncError> {
    public static let shared = SyncManager()

    private let reader = Reader.shared
    private let sender = Sender.shared
    private let apiClient = APIClient.shared

    private let batchSize = 50

    // Outbound send queue (server → Mac Mail.app sends).
    private lazy var sendQueueClient: SendQueueClient<IMailSendTransport> = {
        let transport = IMailSendTransport(apiClient: apiClient)
        let mailSender = MailSender(sender: sender)
        return SendQueueClient(
            label: "imail",
            transport: transport,
            interval: 60
        ) { item in
            switch item.action {
            case "send_mail":
                return await mailSender.send(item.payload)
            default:
                return .failed(reason: "unknown action \(item.action)")
            }
        }
    }()

    public override func didStartSync() {
        sendQueueClient.start()
    }

    public override func willStopSync() {
        sendQueueClient.stop()
    }

    private init() {
        super.init(
            initialStats: IMailSyncStats(),
            noError: .none,
            persistenceKey: "imail",
            timerInterval: 300
        )

        // Migrate legacy UserDefaults watermark keys (one-time).
        if lastSync == nil, let legacy = UserDefaults.standard.object(forKey: "imailLastSync") as? Date {
            setLastSync(legacy)
            UserDefaults.standard.removeObject(forKey: "imailLastSync")
        }
        if lastSyncCompletedAt == nil, let legacy = UserDefaults.standard.object(forKey: "imailLastSyncCompletedAt") as? Date {
            lastSyncCompletedAt = legacy
            UserDefaults.standard.set(legacy, forKey: "imail.lastSyncCompletedAt")
            UserDefaults.standard.removeObject(forKey: "imailLastSyncCompletedAt")
        }
    }

    public func forceFullResync() {
        guard !isSyncing else { return }
        log.info("Forcing full resync")
        setLastSync(nil)
        syncNow()
    }

    // MARK: - Sync cycle

    public override func performSync() async {
        log.info("performSync starting")
        currentOperation = "Reading Mail.app…"

        // Reader runs synchronously on a background thread — dispatch off main
        let watermark = lastSync
        let messages = await Task.detached(priority: .userInitiated) { [reader] in
            reader.fetchMessages(since: watermark)
        }.value

        log.info("Fetched \(messages.count) messages from Mail.app")

        guard !messages.isEmpty else {
            currentOperation = "No new mail"
            await fetchStats()
            return
        }

        currentOperation = "Syncing \(messages.count) messages…"
        var totalSynced = 0

        let batches = stride(from: 0, to: messages.count, by: batchSize).map {
            Array(messages[$0..<min($0 + batchSize, messages.count)])
        }

        for (idx, batch) in batches.enumerated() {
            currentOperation = "Syncing… (\(idx + 1)/\(batches.count))"

            let payloads = batch.map { msg -> SyncEmailPayload in
                SyncEmailPayload(
                    messageId: msg.messageId,
                    threadId: msg.threadId,
                    subject: msg.subject,
                    fromAddress: msg.fromAddress,
                    fromName: msg.fromName,
                    to: msg.to.map { EmailAddressPayload(address: $0.address, name: $0.name) },
                    cc: msg.cc.map { EmailAddressPayload(address: $0.address, name: $0.name) },
                    folder: msg.folder,
                    direction: msg.direction,
                    textBody: msg.textBody,
                    htmlBody: msg.htmlBody,
                    hasAttachments: msg.hasAttachments,
                    attachmentCount: msg.attachmentCount,
                    sentAt: msg.sentAt,
                    receivedAt: msg.receivedAt,
                    isRead: msg.isRead
                )
            }

            do {
                let synced = try await apiClient.syncMail(payloads)
                totalSynced += synced
                log.info("Batch \(idx + 1)/\(batches.count) synced=\(synced)")
            } catch {
                log.warning("Batch \(idx + 1) failed: \(error.localizedDescription)")
                if SyncConnectionErrorHeuristic.isConnectionError(error) {
                    syncError = .connectionFailed(error.localizedDescription)
                }
            }
        }

        stats.syncedThisSession = totalSynced

        await fetchStats()
        currentOperation = "Sync complete"
        log.info("performSync complete: synced=\(totalSynced)")
    }

    private func fetchStats() async {
        do {
            let response = try await apiClient.getStats()
            stats.totalEmails = response.totalEmails
            stats.totalFolders = response.totalFolders
            if let serverLastSync = response.lastSyncAt, lastSync == nil {
                setLastSync(serverLastSync)
            }
        } catch {
            log.warning("fetchStats failed: \(error.localizedDescription)")
        }
    }

    // MARK: - Send (outbound via Mail.app)

    /// Send an email via Mail.app. Called by the local web server on `/api/imail/send` requests.
    public func sendEmail(
        to: [String],
        cc: [String] = [],
        subject: String,
        body: String,
        isHTML: Bool = false
    ) async -> Bool {
        let request = Sender.SendRequest(to: to, cc: cc, subject: subject, body: body, isHTML: isHTML)
        let result = await Task.detached(priority: .userInitiated) { [sender] in
            sender.send(request)
        }.value
        if !result.success {
            log.warning("Send failed: \(result.error ?? "unknown")")
        }
        return result.success
    }
}
