import FirebaseCore
import FirebaseCrashlytics
import FirebaseMessaging
import Intents
import SwiftUI
import UserNotifications

// MARK: - Firebase AppDelegate

final class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate,
    MessagingDelegate
{
    /// Pending chat route from notification tap — consumed by ChatListView on appear.
    /// Handles both terminated app (notification posted before ChatListView exists)
    /// and background app (fallback if .onReceive misses the synchronous post).
    static var pendingChatRoute: ChatRoute?

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
    ) -> Bool {
        FirebaseApp.configure()

        // Set delegates
        Messaging.messaging().delegate = self
        UNUserNotificationCenter.current().delegate = self

        // Register notification category with CarPlay support.
        let messageCategory = UNNotificationCategory(
            identifier: "message",
            actions: [],
            intentIdentifiers: [],
            options: [.allowInCarPlay]
        )
        UNUserNotificationCenter.current().setNotificationCategories([messageCategory])

        // Clear caches on memory pressure to prevent system from killing the app.
        NotificationCenter.default.addObserver(
            forName: UIApplication.didReceiveMemoryWarningNotification,
            object: nil,
            queue: .main
        ) { _ in
            Task { @MainActor in
                AvatarRepository.shared.clearCache()
            }
        }

        // Request notification permission (including CarPlay display).
        // .carPlay enables "Show in CarPlay" toggle in Settings > Notifications > Rosetta.
        UNUserNotificationCenter.current().requestAuthorization(
            options: [.alert, .badge, .sound, .carPlay]
        ) { granted, _ in
            if granted {
                DispatchQueue.main.async {
                    application.registerForRemoteNotifications()
                }
            }
        }

        return true
    }

    // Forward APNs token to Firebase Messaging + SessionManager
    func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
    ) {
        Messaging.messaging().apnsToken = deviceToken
    }

    // MARK: - Background Push (Badge + Local Notification with Sound)

    /// Called when a push notification arrives with `content-available: 1`.
    /// Two scenarios:
    /// 1. Server sends data-only push (no alert) → we create a local notification with sound.
    /// 2. Server sends visible push + content-available → NSE handles sound/badge,
    ///    we only sync the badge count here.
    /// Android parity: 10-second dedup window per sender.
    /// Prevents duplicate push notifications from rapid server retries.
    private static var lastNotifTimestamps: [String: TimeInterval] = [:]
    private static let dedupWindowSeconds: TimeInterval = 10

    func application(
        _ application: UIApplication,
        didReceiveRemoteNotification userInfo: [AnyHashable: Any],
        fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
    ) {
        // Foreground: WebSocket handles messages in real-time — skip.
        guard application.applicationState != .active else {
            completionHandler(.noData)
            return
        }

        let shared = UserDefaults(suiteName: "group.com.rosetta.dev")

        // Background/inactive: increment badge from shared App Group storage.
        let currentBadge = shared?.integer(forKey: "app_badge_count") ?? 0
        let newBadge = currentBadge + 1
        shared?.set(newBadge, forKey: "app_badge_count")
        UserDefaults.standard.set(newBadge, forKey: "app_badge_count")
        UNUserNotificationCenter.current().setBadgeCount(newBadge)

        // Android parity: extract sender key with multi-key fallback.
        // Server may send under different key names depending on version.
        let senderKey = Self.extractSenderKey(from: userInfo)
        let senderName = Self.firstNonBlank(userInfo, keys: [
            "sender_name", "from_title", "sender", "title", "name"
        ]) ?? "New message"
        let messageText = Self.firstNonBlank(userInfo, keys: [
            "message_preview", "message", "text", "body"
        ]) ?? "New message"

        // Android parity: 10-second dedup per sender.
        let dedupKey = senderKey.isEmpty ? "__no_sender__" : senderKey
        let now = Date().timeIntervalSince1970
        if let lastTs = Self.lastNotifTimestamps[dedupKey], now - lastTs < Self.dedupWindowSeconds {
            completionHandler(.noData)
            return
        }
        Self.lastNotifTimestamps[dedupKey] = now

        // Check if the server already sent a visible alert (aps.alert exists).
        let aps = userInfo["aps"] as? [String: Any]
        let hasVisibleAlert = aps?["alert"] != nil

        // Don't notify for muted chats.
        let isMuted: Bool = {
            let mutedSet = UserDefaults.standard.stringArray(forKey: "muted_chats_keys") ?? []
            return mutedSet.contains(senderKey)
        }()

        // If server sent visible alert, NSE handles sound+badge. Just sync badge.
        guard !hasVisibleAlert && !isMuted else {
            completionHandler(.newData)
            return
        }

        let content = UNMutableNotificationContent()
        content.title = senderName
        content.body = messageText
        content.sound = .default
        content.badge = NSNumber(value: newBadge)
        content.categoryIdentifier = "message"
        // Always set sender_public_key in userInfo for notification tap navigation.
        content.userInfo = ["sender_public_key": senderKey, "sender_name": senderName]

        // Communication Notification via INSendMessageIntent (CarPlay + Focus parity).
        let handle = INPersonHandle(value: senderKey, type: .unknown)
        let sender = INPerson(
            personHandle: handle,
            nameComponents: nil,
            displayName: senderName.isEmpty ? "Rosetta" : senderName,
            image: nil,
            contactIdentifier: nil,
            customIdentifier: senderKey
        )
        let intent = INSendMessageIntent(
            recipients: nil,
            outgoingMessageType: .outgoingMessageText,
            content: messageText,
            speakableGroupName: nil,
            conversationIdentifier: senderKey,
            serviceName: "Rosetta",
            sender: sender,
            attachments: nil
        )
        let interaction = INInteraction(intent: intent, response: nil)
        interaction.direction = .incoming
        interaction.donate(completion: nil)

        let finalContent: UNNotificationContent
        if let updated = try? content.updating(from: intent) {
            finalContent = updated
        } else {
            finalContent = content
        }

        let request = UNNotificationRequest(
            identifier: "msg_\(senderKey)_\(Int(now))",
            content: finalContent,
            trigger: nil
        )
        UNUserNotificationCenter.current().add(request) { _ in
            completionHandler(.newData)
        }
    }

    // MARK: - Push Payload Helpers (Android parity)

    /// Android parity: extract sender public key from multiple possible key names.
    /// Server may use different key names across versions.
    private static func extractSenderKey(from userInfo: [AnyHashable: Any]) -> String {
        firstNonBlank(userInfo, keys: [
            "sender_public_key", "from_public_key", "fromPublicKey",
            "public_key", "publicKey"
        ]) ?? ""
    }

    /// Android parity: `firstNonBlank(data, ...)` — try multiple key names, return first non-empty.
    private static func firstNonBlank(_ dict: [AnyHashable: Any], keys: [String]) -> String? {
        for key in keys {
            if let value = dict[key] as? String, !value.trimmingCharacters(in: .whitespaces).isEmpty {
                return value
            }
        }
        return nil
    }

    // MARK: - MessagingDelegate

    /// Called when FCM token is received or refreshed.
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
        guard let token = fcmToken else { return }
        Task { @MainActor in
            SessionManager.shared.setAPNsToken(token)
        }
    }

    // MARK: - UNUserNotificationCenterDelegate

    /// Handle foreground notifications — suppress ALL when app is in foreground.
    /// Android parity: messages arrive via WebSocket in real-time, push is background-only.
    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        willPresent notification: UNNotification,
        withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) ->
            Void
    ) {
        completionHandler([])
    }

    /// Handle notification tap — navigate to the sender's chat.
    /// Android parity: extracts sender key with multi-key fallback.
    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        didReceive response: UNNotificationResponse,
        withCompletionHandler completionHandler: @escaping () -> Void
    ) {
        let userInfo = response.notification.request.content.userInfo
        // Android parity: try multiple key names for sender identification.
        let senderKey = Self.extractSenderKey(from: userInfo)

        if !senderKey.isEmpty {
            let senderName = Self.firstNonBlank(userInfo, keys: [
                "sender_name", "from_title", "sender", "title", "name"
            ]) ?? ""
            let route = ChatRoute(
                publicKey: senderKey,
                title: senderName,
                username: "",
                verified: 0
            )
            // Store pending route BEFORE posting — handles terminated app case
            // where ChatListView doesn't exist yet, and background app case
            // where .onReceive might miss the synchronous post.
            Self.pendingChatRoute = route
            NotificationCenter.default.post(
                name: .openChatFromNotification,
                object: route
            )
            // Clear all delivered notifications from this sender
            center.getDeliveredNotifications { delivered in
                let idsToRemove = delivered
                    .filter { notification in
                        let info = notification.request.content.userInfo
                        let key = Self.extractSenderKey(from: info)
                        return key == senderKey
                    }
                    .map { $0.request.identifier }
                if !idsToRemove.isEmpty {
                    center.removeDeliveredNotifications(withIdentifiers: idsToRemove)
                }
            }
        }

        completionHandler()
    }
}

// MARK: - App State

private enum AppState {
    case onboarding
    case auth
    case unlock
    case main
}

// MARK: - RosettaApp

@main
struct RosettaApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate

    init() {
        UIWindow.appearance().backgroundColor = .black

        // Detect fresh install: UserDefaults are wiped on uninstall, Keychain is not.
        // If this is the first launch after install, clear any stale Keychain data.
        if !UserDefaults.standard.bool(forKey: "hasLaunchedBefore") {
            try? AccountManager.shared.deleteAccount()
            try? KeychainManager.shared.delete(forKey: Account.KeychainKey.allAccounts)
            UserDefaults.standard.removeObject(forKey: Account.KeychainKey.activeAccountKey)
            UserDefaults.standard.set(true, forKey: "hasLaunchedBefore")
        }

        // Avoid heavy startup work on MainActor; Lottie assets load lazily on first use.
        DebugPerformanceBenchmarks.runIfRequested()
    }

    @AppStorage("hasCompletedOnboarding") private var hasCompletedOnboarding = false
    @AppStorage("isLoggedIn") private var isLoggedIn = false
    @AppStorage("hasLaunchedBefore") private var hasLaunchedBefore = false
    @State private var appState: AppState?
    @State private var transitionOverlay: Bool = false

    var body: some Scene {
        WindowGroup {
            ZStack {
                RosettaColors.Dark.background
                    .ignoresSafeArea()

                if let appState {
                    rootView(for: appState)
                }

                // Fade-through-black overlay for smooth screen transitions.
                // Avoids UIKit-hosted Lottie views fighting SwiftUI opacity transitions.
                Color.black
                    .ignoresSafeArea()
                    .opacity(transitionOverlay ? 1 : 0)
                    .allowsHitTesting(transitionOverlay)
                    .animation(.easeInOut(duration: 0.035), value: transitionOverlay)
            }
            .preferredColorScheme(.dark)
            .onAppear {
                if appState == nil {
                    appState = initialState()
                }
            }
        }
    }

    @ViewBuilder
    private func rootView(for state: AppState) -> some View {
        switch state {
        case .onboarding:
            OnboardingView {
                hasCompletedOnboarding = true
                fadeTransition(to: .auth)
            }

        case .auth:
            AuthCoordinator(
                onAuthComplete: {
                    isLoggedIn = true
                    fadeTransition(to: .main)
                },
                onBackToUnlock: AccountManager.shared.hasAccount ? {
                    fadeTransition(to: .unlock)
                } : nil
            )

        case .unlock:
            UnlockView(
                onUnlocked: {
                    isLoggedIn = true
                    fadeTransition(to: .main)
                },
                onCreateNewAccount: {
                    // Go to auth flow (Welcome screen with back button)
                    // Does NOT delete the old account — Android keeps multiple accounts
                    fadeTransition(to: .auth)
                }
            )

        case .main:
            MainTabView(
                onLogout: {
                    isLoggedIn = false
                    // Desktop parity: if other accounts remain after deletion, go to unlock.
                    // Only go to onboarding if no accounts left.
                    if AccountManager.shared.hasAccount {
                        fadeTransition(to: .unlock)
                    } else {
                        hasCompletedOnboarding = false
                        fadeTransition(to: .onboarding)
                    }
                },
            )
            // Force full view recreation on account switch. Without this,
            // SwiftUI may reuse the old MainTabView's @StateObject instances
            // (SettingsViewModel, ChatListViewModel) when appState cycles
            // .main → .unlock → .main, causing stale profile data to persist.
            .id(AccountManager.shared.currentAccount?.publicKey ?? "")
        }
    }

    /// Fade-through-black transition: overlay fades in → swap content → overlay fades out.
    /// Avoids UIKit-hosted views (Lottie, UIPageViewController) fighting SwiftUI transitions.
    private func fadeTransition(to newState: AppState) {
        guard !transitionOverlay else { return }
        transitionOverlay = true
        Task { @MainActor in
            try? await Task.sleep(nanoseconds: 35_000_000) // wait for overlay fade-in
            appState = newState
            try? await Task.sleep(nanoseconds: 10_000_000) // brief settle
            transitionOverlay = false
        }
    }

    private func initialState() -> AppState {
        if AccountManager.shared.hasAccount {
            return .unlock
        } else {
            hasCompletedOnboarding = false
            return .onboarding
        }
    }
}

// MARK: - Notification Names

extension Notification.Name {
    /// Posted when user taps a push notification — carries a `ChatRoute` as `object`.
    static let openChatFromNotification = Notification.Name("openChatFromNotification")
    /// Posted when own profile (displayName/username) is updated from the server.
    static let profileDidUpdate = Notification.Name("profileDidUpdate")
    /// Posted when user taps an attachment in the bubble overlay — carries attachment ID (String) as `object`.
    /// MessageImageView / MessageFileView listen and trigger download/share.
    static let triggerAttachmentDownload = Notification.Name("triggerAttachmentDownload")
    /// Posted when user taps "Chats" toolbar title — triggers scroll-to-top.
    static let chatListScrollToTop = Notification.Name("chatListScrollToTop")
}
