import AppKit
import EventKit
import Foundation
import LilithLogging
import MacSyncShared

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

// MARK: - Stats

public struct IReminderSyncStats: Equatable, Sendable {
    public var calendarCount: Int = 0
    public var reminderCount: Int = 0

    public init() {}
}

// MARK: - Sync Error

public enum IReminderSyncError: Equatable, Sendable {
    case none
    case reminderAccessRequired
    case backendUnreachable
    case connectionFailed(String)

    public var message: String {
        switch self {
        case .none: return ""
        case .reminderAccessRequired: return "Reminders access required"
        case .backendUnreachable: return "Cannot connect to backend server"
        case .connectionFailed(let m): return m
        }
    }

    public var isConnectionError: Bool {
        switch self {
        case .backendUnreachable, .connectionFailed: return true
        default: return false
        }
    }
}

// MARK: - SyncManager

@MainActor
public final class SyncManager: BaseSyncManager<IReminderSyncStats, IReminderSyncError> {
    public static let shared = SyncManager()

    public let reader = ReminderReader.shared
    private let apiClient = APIClient.shared

    private let reminderBatchSize = 200

    private lazy var sendQueueClient: SendQueueClient<ReminderSendTransport> = {
        let transport = ReminderSendTransport(apiClient: apiClient)
        let sender = ReminderSender(eventStore: reader.eventStore)
        return SendQueueClient(label: "ireminders", transport: transport, interval: 60) { item in
            await sender.apply(item)
        }
    }()

    private init() {
        super.init(
            initialStats: IReminderSyncStats(),
            noError: .none,
            persistenceKey: "ireminders",
            timerInterval: 300
        )
    }

    // MARK: - Authorization hooks

    public override func isAuthorized() async -> Bool { reader.isAuthorized }

    public override func requestAuthorization() async -> Bool {
        await reader.requestAuthorization()
    }

    public override func onAuthorizationDenied() {
        log.warning("Reminders access denied")
        syncError = .reminderAccessRequired
    }

    // MARK: - Send-queue lifecycle

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

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

    public func openReminderAccessSettings() {
        if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Reminders") {
            NSWorkspace.shared.open(url)
        }
    }

    // MARK: - Sync cycle

    public override func performSync() async {
        log.info("performSync starting")
        let df = ISO8601DateFormatter()

        // Phase 1: Reminder lists (calendars). Server stats currently only
        // surfaces totalReminders; reminder-list payloads are computed for
        // local stats display.
        currentOperation = "Fetching reminder lists…"
        let calendars = reader.fetchCalendars()
        log.info("Fetched \(calendars.count) reminder lists")
        stats.calendarCount = calendars.count

        // Phase 2: Reminders
        currentOperation = "Fetching reminders…"
        let reminders = await reader.fetchReminders(since: lastSync)
        log.info("Fetched \(reminders.count) reminders")

        if !reminders.isEmpty {
            let batches = stride(from: 0, to: reminders.count, by: reminderBatchSize).map {
                Array(reminders[$0..<min($0 + reminderBatchSize, reminders.count)])
            }

            for (idx, batch) in batches.enumerated() {
                currentOperation = "Syncing reminders… (\(idx + 1)/\(batches.count))"
                let payloads = batch.map { r in
                    SyncReminderPayload(
                        reminderIdentifier: r.reminderIdentifier,
                        calendarIdentifier: r.calendarIdentifier,
                        title: r.title,
                        notes: r.notes,
                        dueDate: r.dueDate.map { df.string(from: $0) },
                        priority: r.priority,
                        isCompleted: r.isCompleted,
                        modifiedAt: r.lastModifiedDate.map { df.string(from: $0) }
                    )
                }
                do {
                    let synced = try await apiClient.syncReminders(payloads)
                    log.info("Batch \(idx + 1)/\(batches.count) synced=\(synced)")
                    if syncError.isConnectionError { syncError = .none }
                } catch {
                    log.warning("Reminder batch \(idx + 1) failed: \(error.localizedDescription)")
                    setConnectionError(error)
                }
            }
        }

        await updateStats()
        currentOperation = "Sync complete"
        log.info("performSync complete lists=\(calendars.count) reminders=\(reminders.count)")
    }

    private func updateStats() async {
        do {
            let response = try await apiClient.getStats()
            stats.reminderCount = response.totalReminders
            log.info("Stats: reminders=\(response.totalReminders)")
        } catch {
            log.warning("updateStats failed: \(error.localizedDescription)")
        }
    }

    private func setConnectionError(_ error: Error) {
        if SyncConnectionErrorHeuristic.isConnectionError(error) {
            syncError = .backendUnreachable
        }
    }
}
