import AppKit
import Contacts
import Foundation
import ICalSync
import IMailSync
import IMessageSync
import INoteSync
import IPhotoSync
import IReminderSync
import MacSyncShared
import Photos

@main
enum MacSyncApp {
    static func main() {
        let app = NSApplication.shared
        let delegate = AppDelegate()
        app.delegate = delegate
        app.setActivationPolicy(.accessory)
        NSLog("MacSync: starting")
        app.run()
    }
}

@MainActor
final class AppDelegate: NSObject, NSApplicationDelegate {
    private var statusItem: NSStatusItem?
    // IMessageSync exposes SyncManager via IMessageSyncModule (internal type hidden).
    private let iPhotoSync = IPhotoSync.SyncManager.shared
    private let iMailSync = IMailSync.SyncManager.shared
    private let iCalSync = ICalSync.SyncManager.shared
    private let iReminderSync = IReminderSync.SyncManager.shared
    private let iNoteSync = INoteSync.SyncManager.shared
    private let webServer = LocalWebServer()
    private var syncStatusItem: NSMenuItem?
    private var iphotoStatusItem: NSMenuItem?
    private var imailStatusItem: NSMenuItem?
    private var icalStatusItem: NSMenuItem?
    private var ireminderStatusItem: NSMenuItem?
    private var inoteStatusItem: NSMenuItem?
    private var contactRenderStatusItem: NSMenuItem?

    func applicationDidFinishLaunching(_ notification: Notification) {
        ProcessInfo.processInfo.disableSuddenTermination()
        ProcessInfo.processInfo.disableAutomaticTermination("MacSync running")

        // Merge ~/.config/com.lilith.mac-sync/config.json into UserDefaults.
        // Must run before any module reads serverURL / webServerPort.
        ConfigFile.load()
        webServer.start()

        statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
        statusItem?.button?.image = NSImage(
            systemSymbolName: "arrow.triangle.2.circlepath",
            accessibilityDescription: "MacSync"
        )

        buildMenu()
        startModules()
    }

    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { false }

    private func startModules() {
        if DeviceRegistration.isAuthenticated {
            NSLog("MacSync: authenticated — starting all sync modules")
            IMessageSyncModule.startSync()
            iPhotoSync.startSync()
            iMailSync.startSync()
            iCalSync.startSync()
            iReminderSync.startSync()
            iNoteSync.startSync()
            ContactRenderPoller.shared.start()
        } else {
            let existingDeviceId = try? macSyncSharedKeychain.loadString(key: "deviceId")
            if existingDeviceId != nil {
                NSLog("MacSync: existing deviceId, polling for verification")
                startPollingForVerification()
            } else {
                NSLog("MacSync: no deviceId, registering device")
                Task {
                    do {
                        let (deviceId, code) = try await DeviceRegistration.register()
                        NSLog("MacSync: registered deviceId=\(deviceId) code=\(code)")
                        startPollingForVerification()
                    } catch {
                        NSLog("MacSync: registration failed: \(error)")
                    }
                }
            }
        }
    }

    private func startPollingForVerification() {
        Timer.scheduledTimer(withTimeInterval: 3.0, repeats: true) { [weak self] timer in
            Task { @MainActor [weak self] in
                guard let self else { return }
                do {
                    let verified = try await DeviceRegistration.checkVerification()
                    if verified {
                        timer.invalidate()
                        NSLog("MacSync: device verified — starting sync")
                        IMessageSyncModule.startSync()
                        self.iPhotoSync.startSync()
                        self.iMailSync.startSync()
                        self.iCalSync.startSync()
                        self.iReminderSync.startSync()
                        self.iNoteSync.startSync()
                        ContactRenderPoller.shared.start()
                    }
                } catch {
                    NSLog("MacSync: verification check error: \(error)")
                }
            }
        }
    }

    private func buildMenu() {
        let menu = NSMenu()

        let titleItem = NSMenuItem(title: "MacSync", action: nil, keyEquivalent: "")
        titleItem.isEnabled = false
        menu.addItem(titleItem)

        menu.addItem(.separator())

        let iMsgItem = NSMenuItem(title: "iMessage: idle", action: nil, keyEquivalent: "")
        iMsgItem.isEnabled = false
        menu.addItem(iMsgItem)
        syncStatusItem = iMsgItem

        let iPhotoItem = NSMenuItem(title: "iPhoto: idle", action: nil, keyEquivalent: "")
        iPhotoItem.isEnabled = false
        menu.addItem(iPhotoItem)
        iphotoStatusItem = iPhotoItem

        let iMailItem = NSMenuItem(title: "iMail: idle", action: nil, keyEquivalent: "")
        iMailItem.isEnabled = false
        menu.addItem(iMailItem)
        imailStatusItem = iMailItem

        let iCalItem = NSMenuItem(title: "iCal: idle", action: nil, keyEquivalent: "")
        iCalItem.isEnabled = false
        menu.addItem(iCalItem)
        icalStatusItem = iCalItem

        let iReminderItem = NSMenuItem(title: "Reminders: idle", action: nil, keyEquivalent: "")
        iReminderItem.isEnabled = false
        menu.addItem(iReminderItem)
        ireminderStatusItem = iReminderItem

        let iNoteItem = NSMenuItem(title: "Notes: idle", action: nil, keyEquivalent: "")
        iNoteItem.isEnabled = false
        menu.addItem(iNoteItem)
        inoteStatusItem = iNoteItem

        let contactRenderItem = NSMenuItem(title: "Contacts: idle", action: nil, keyEquivalent: "")
        contactRenderItem.isEnabled = false
        menu.addItem(contactRenderItem)
        contactRenderStatusItem = contactRenderItem

        menu.addItem(.separator())

        let syncNowItem = NSMenuItem(title: "Sync Now", action: #selector(syncNow), keyEquivalent: "s")
        syncNowItem.target = self
        menu.addItem(syncNowItem)

        menu.addItem(.separator())

        let settingsItem = NSMenuItem(title: "Settings", action: #selector(openSettings), keyEquivalent: ",")
        settingsItem.target = self
        menu.addItem(settingsItem)

        menu.addItem(.separator())

        let permissionsItem = NSMenuItem(title: "Permissions", action: nil, keyEquivalent: "")
        permissionsItem.submenu = buildPermissionsMenu()
        menu.addItem(permissionsItem)

        menu.addItem(.separator())

        let quitItem = NSMenuItem(title: "Quit", action: #selector(quit), keyEquivalent: "q")
        quitItem.target = self
        menu.addItem(quitItem)

        statusItem?.menu = menu

        // Update menu on sync state changes
        Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { [weak self] _ in
            Task { @MainActor [weak self] in self?.updateMenuStatus() }
        }
    }

    private func updateMenuStatus() {
        let relFormatter = RelativeDateTimeFormatter()
        relFormatter.unitsStyle = .abbreviated

        // iMessage
        if IMessageSyncModule.isSyncing {
            syncStatusItem?.title = "iMessage: syncing…"
        } else if let last = IMessageSyncModule.lastSyncCompletedAt {
            syncStatusItem?.title = "iMessage: \(relFormatter.localizedString(for: last, relativeTo: Date()))"
        } else if !IMessageSyncModule.syncErrorMessage.isEmpty {
            syncStatusItem?.title = "iMessage: \(IMessageSyncModule.syncErrorMessage)"
        } else {
            syncStatusItem?.title = "iMessage: idle"
        }

        // iPhoto
        if iPhotoSync.isSyncing {
            iphotoStatusItem?.title = "iPhoto: \(iPhotoSync.currentOperation)"
        } else if let last = iPhotoSync.lastSyncCompletedAt {
            let pending = iPhotoSync.stats.pendingUpload
            let suffix = pending > 0 ? " (\(pending) pending)" : ""
            iphotoStatusItem?.title = "iPhoto: \(relFormatter.localizedString(for: last, relativeTo: Date()))\(suffix)"
        } else if iPhotoSync.syncError != .none {
            iphotoStatusItem?.title = "iPhoto: \(iPhotoSync.syncError.message)"
        } else {
            iphotoStatusItem?.title = "iPhoto: idle"
        }

        // iMail
        if iMailSync.isSyncing {
            imailStatusItem?.title = "iMail: \(iMailSync.currentOperation)"
        } else if let last = iMailSync.lastSyncCompletedAt {
            imailStatusItem?.title = "iMail: \(relFormatter.localizedString(for: last, relativeTo: Date()))"
        } else if iMailSync.syncError != .none {
            imailStatusItem?.title = "iMail: \(iMailSync.syncError.message)"
        } else {
            imailStatusItem?.title = "iMail: idle"
        }

        // iCal
        if iCalSync.isSyncing {
            icalStatusItem?.title = "iCal: \(iCalSync.currentOperation)"
        } else if let last = iCalSync.lastSyncCompletedAt {
            let total = iCalSync.stats.eventCount
            let suffix = total > 0 ? " (\(total) events)" : ""
            icalStatusItem?.title = "iCal: \(relFormatter.localizedString(for: last, relativeTo: Date()))\(suffix)"
        } else if iCalSync.syncError != .none {
            icalStatusItem?.title = "iCal: \(iCalSync.syncError.message)"
        } else {
            icalStatusItem?.title = "iCal: idle"
        }

        // Reminders
        if iReminderSync.isSyncing {
            ireminderStatusItem?.title = "Reminders: \(iReminderSync.currentOperation)"
        } else if let last = iReminderSync.lastSyncCompletedAt {
            let total = iReminderSync.stats.reminderCount
            let suffix = total > 0 ? " (\(total) reminders)" : ""
            ireminderStatusItem?.title = "Reminders: \(relFormatter.localizedString(for: last, relativeTo: Date()))\(suffix)"
        } else if iReminderSync.syncError != .none {
            ireminderStatusItem?.title = "Reminders: \(iReminderSync.syncError.message)"
        } else {
            ireminderStatusItem?.title = "Reminders: idle"
        }

        // Notes
        if iNoteSync.isSyncing {
            inoteStatusItem?.title = "Notes: \(iNoteSync.currentOperation)"
        } else if let last = iNoteSync.lastSyncCompletedAt {
            let total = iNoteSync.stats.noteCount
            let suffix = total > 0 ? " (\(total) notes)" : ""
            inoteStatusItem?.title = "Notes: \(relFormatter.localizedString(for: last, relativeTo: Date()))\(suffix)"
        } else if iNoteSync.syncError != .none {
            inoteStatusItem?.title = "Notes: \(iNoteSync.syncError.message)"
        } else {
            inoteStatusItem?.title = "Notes: idle"
        }

        // Contact render
        let poller = ContactRenderPoller.shared
        if !poller.lastError.isEmpty {
            contactRenderStatusItem?.title = "Contacts: \(poller.lastError)"
        } else if let last = poller.lastSyncCompletedAt {
            let count = poller.recentWriteCount
            let suffix = count > 0 ? " (\(count) written)" : ""
            contactRenderStatusItem?.title = "Contacts: \(relFormatter.localizedString(for: last, relativeTo: Date()))\(suffix)"
        } else {
            contactRenderStatusItem?.title = "Contacts: idle"
        }
    }

    @objc private func syncNow() {
        NSLog("MacSync: manual sync triggered")
        IMessageSyncModule.syncNow()
        iPhotoSync.syncNow()
        iMailSync.syncNow()
        iCalSync.syncNow()
        iReminderSync.syncNow()
        iNoteSync.syncNow()
    }

    @objc private func openSettings() {
        if let url = URL(string: "http://localhost:8765/#/settings") {
            NSWorkspace.shared.open(url)
        }
    }

    @objc private func quit() {
        NSLog("MacSync: quit")
        NSApplication.shared.terminate(nil)
    }

    // MARK: - Permissions menu

    private func buildPermissionsMenu() -> NSMenu {
        let submenu = NSMenu()

        let localNet = NSMenuItem(title: "Grant Local Network…", action: #selector(requestLocalNetwork), keyEquivalent: "")
        localNet.target = self
        submenu.addItem(localNet)

        let fda = NSMenuItem(title: "Grant Full Disk Access…", action: #selector(openFullDiskAccess), keyEquivalent: "")
        fda.target = self
        submenu.addItem(fda)

        let contacts = NSMenuItem(title: "Grant Contacts…", action: #selector(requestContacts), keyEquivalent: "")
        contacts.target = self
        submenu.addItem(contacts)

        let photos = NSMenuItem(title: "Grant Photos…", action: #selector(requestPhotos), keyEquivalent: "")
        photos.target = self
        submenu.addItem(photos)

        let calendars = NSMenuItem(title: "Grant Calendars…", action: #selector(requestCalendars), keyEquivalent: "")
        calendars.target = self
        submenu.addItem(calendars)

        let reminders = NSMenuItem(title: "Grant Reminders…", action: #selector(requestReminders), keyEquivalent: "")
        reminders.target = self
        submenu.addItem(reminders)

        let automation = NSMenuItem(title: "Grant Messages + Mail + Notes Automation…", action: #selector(requestAutomation), keyEquivalent: "")
        automation.target = self
        submenu.addItem(automation)

        submenu.addItem(.separator())

        let openSettings = NSMenuItem(title: "Open Privacy & Security Settings", action: #selector(openPrivacySettings), keyEquivalent: "")
        openSettings.target = self
        submenu.addItem(openSettings)

        let revealConfig = NSMenuItem(title: "Reveal Config File in Finder", action: #selector(revealConfigFile), keyEquivalent: "")
        revealConfig.target = self
        submenu.addItem(revealConfig)

        let retry = NSMenuItem(title: "Retry Connection", action: #selector(retryConnection), keyEquivalent: "r")
        retry.target = self
        submenu.addItem(retry)

        return submenu
    }

    @objc private func revealConfigFile() {
        ConfigFile.writeTemplateIfMissing()
        let url = URL(fileURLWithPath: ConfigFile.path)
        NSWorkspace.shared.activateFileViewerSelecting([url])
    }

    @objc private func requestLocalNetwork() {
        NSLog("MacSync: priming local-network TCC + opening settings")
        // macOS only exposes MacSync in the Local Network pane once the app has tried to
        // reach a LAN address. Poke the server, then open the pane so the user can toggle.
        let urlString = UserDefaults.standard.string(forKey: "serverURL") ?? "http://10.0.0.11:3201"
        if let url = URL(string: urlString)?.appendingPathComponent("health") {
            Task.detached {
                _ = try? await URLSession.shared.data(from: url)
                await MainActor.run {
                    _ = NSWorkspace.shared.open(
                        URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_LocalNetwork")!
                    )
                }
            }
        }
    }

    @objc private func openFullDiskAccess() {
        NSWorkspace.shared.open(
            URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles")!
        )
    }

    @objc private func requestContacts() {
        NSLog("MacSync: requesting contacts permission")
        CNContactStore().requestAccess(for: .contacts) { granted, error in
            NSLog("MacSync: contacts granted=\(granted) error=\(String(describing: error))")
            if !granted {
                DispatchQueue.main.async {
                    NSWorkspace.shared.open(
                        URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Contacts")!
                    )
                }
            }
        }
    }

    @objc private func requestPhotos() {
        NSLog("MacSync: requesting photos permission")
        PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in
            NSLog("MacSync: photos status=\(status.rawValue)")
            if status != .authorized {
                DispatchQueue.main.async {
                    NSWorkspace.shared.open(
                        URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Photos")!
                    )
                }
            }
        }
    }

    @objc private func requestCalendars() {
        NSLog("MacSync: requesting calendars permission")
        Task {
            let granted = await iCalSync.reader.requestAuthorization()
            if !granted {
                await MainActor.run {
                    _ = NSWorkspace.shared.open(
                        URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Calendars")!
                    )
                }
            }
        }
    }

    @objc private func requestReminders() {
        NSLog("MacSync: requesting reminders permission")
        Task {
            let granted = await iReminderSync.reader.requestAuthorization()
            if !granted {
                await MainActor.run {
                    _ = NSWorkspace.shared.open(
                        URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Reminders")!
                    )
                }
            }
        }
    }

    @objc private func requestAutomation() {
        NSLog("MacSync: priming Messages + Mail + Notes automation TCC")
        // Sending a harmless AppleScript to each app registers MacSync in the Automation
        // pane and surfaces the first-time "Allow" prompt.
        let probe = """
        tell application \"Messages\" to get name
        tell application \"Mail\" to get name
        tell application \"Notes\" to get name
        """
        var errorInfo: NSDictionary?
        _ = NSAppleScript(source: probe)?.executeAndReturnError(&errorInfo)
        if let err = errorInfo { NSLog("MacSync: automation probe error=\(err)") }
        NSWorkspace.shared.open(
            URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation")!
        )
    }

    @objc private func openPrivacySettings() {
        NSWorkspace.shared.open(
            URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy")!
        )
    }

    @objc private func retryConnection() {
        NSLog("MacSync: retry connection")
        startModules()
    }
}
