import AudioToolbox
import AVFAudio
import Combine
import SwiftUI

/// Manages in-app notification banners (Telegram parity).
/// Shows custom overlay instead of system banners when app is in foreground.
@MainActor
final class InAppNotificationManager: ObservableObject {

    static let shared = InAppNotificationManager()

    @Published private(set) var currentNotification: InAppNotification?

    private var dismissTask: Task<Void, Never>?
    private var soundPlayer: AVAudioPlayer?

    private static let autoDismissSeconds: UInt64 = 5

    private init() {}

    // MARK: - Data Model

    struct InAppNotification: Identifiable, Equatable {
        let id: UUID
        let senderKey: String
        let senderName: String
        let messagePreview: String
        let avatar: UIImage?
        let avatarColorIndex: Int
        let initials: String

        static func == (lhs: InAppNotification, rhs: InAppNotification) -> Bool {
            lhs.id == rhs.id
        }
    }

    // MARK: - Public API

    /// Called from `willPresent` — extracts sender info and shows banner if appropriate.
    func show(userInfo: [AnyHashable: Any]) {
        let senderKey = userInfo["dialog"] as? String
            ?? AppDelegate.extractSenderKey(from: userInfo)

        // --- Suppression logic (Telegram parity) ---
        guard !senderKey.isEmpty else { return }
        guard !MessageRepository.shared.isDialogActive(senderKey) else { return }

        let mutedKeys = UserDefaults(suiteName: "group.com.rosetta.dev")?
            .stringArray(forKey: "muted_chats_keys") ?? []
        guard !mutedKeys.contains(senderKey) else { return }

        // --- Resolve display data ---
        let contactNames = UserDefaults(suiteName: "group.com.rosetta.dev")?
            .dictionary(forKey: "contact_display_names") as? [String: String] ?? [:]
        let name = contactNames[senderKey]
            ?? firstString(userInfo, keys: ["title", "sender_name", "from_title", "name"])
            ?? "Rosetta"

        let preview = extractMessagePreview(from: userInfo)
        let avatar = AvatarRepository.shared.loadAvatar(publicKey: senderKey)
        let dialog = DialogRepository.shared.dialogs[senderKey]

        let notification = InAppNotification(
            id: UUID(),
            senderKey: senderKey,
            senderName: name,
            messagePreview: preview,
            avatar: avatar,
            avatarColorIndex: dialog?.avatarColorIndex ?? abs(senderKey.hashValue) % 11,
            initials: dialog?.initials ?? String(name.prefix(1)).uppercased()
        )

        withAnimation(.spring(response: 0.35, dampingFraction: 0.85)) {
            currentNotification = notification
        }

        playNotificationSound()
        playHaptic()
        scheduleAutoDismiss()
    }

    func dismiss() {
        dismissTask?.cancel()
        withAnimation(.easeOut(duration: 0.25)) {
            currentNotification = nil
        }
    }

    /// Testable: checks whether a notification should be suppressed.
    static func shouldSuppress(senderKey: String) -> Bool {
        if senderKey.isEmpty { return true }
        if MessageRepository.shared.isDialogActive(senderKey) { return true }
        let mutedKeys = UserDefaults(suiteName: "group.com.rosetta.dev")?
            .stringArray(forKey: "muted_chats_keys") ?? []
        if mutedKeys.contains(senderKey) { return true }
        return false
    }

    // MARK: - Private

    private func scheduleAutoDismiss() {
        dismissTask?.cancel()
        dismissTask = Task {
            try? await Task.sleep(nanoseconds: Self.autoDismissSeconds * 1_000_000_000)
            guard !Task.isCancelled else { return }
            dismiss()
        }
    }

    private func playNotificationSound() {
        // System "Tink" haptic feedback sound — lightweight, no custom mp3 needed.
        AudioServicesPlaySystemSound(1057)
    }

    private func playHaptic() {
        // UIImpactFeedbackGenerator style tap via AudioServices.
        let generator = UIImpactFeedbackGenerator(style: .light)
        generator.impactOccurred()
    }

    private func extractMessagePreview(from userInfo: [AnyHashable: Any]) -> String {
        // Try notification body directly.
        if let body = userInfo["body"] as? String, !body.isEmpty { return body }
        // Try aps.alert (can be string or dict).
        if let aps = userInfo["aps"] as? [String: Any] {
            if let alert = aps["alert"] as? String, !alert.isEmpty { return alert }
            if let alertDict = aps["alert"] as? [String: Any],
               let body = alertDict["body"] as? String, !body.isEmpty { return body }
        }
        return "New message"
    }

    private func firstString(_ 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
    }
}
