import 'dart:convert';
import 'dart:io';

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:real_estate_mobile/features/notifications/data/notification_repository.dart';

/// Top-level handler for background messages (must be top-level function).
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  debugPrint('[FCM] Background message: ${message.messageId}');
}

class PushNotificationService {
  static final PushNotificationService _instance =
      PushNotificationService._internal();
  factory PushNotificationService() => _instance;
  PushNotificationService._internal();

  FirebaseMessaging? _messaging;
  FlutterLocalNotificationsPlugin? _localNotifications;
  final NotificationRepository _repo = NotificationRepository();

  String? _currentToken;
  bool _initialized = false;

  /// Callback when user taps a notification — set by the app to navigate.
  /// Receives the full FCM data payload so the app can route by `type`
  /// (e.g. 'message' → open chat) instead of only relying on `actionUrl`.
  ///
  /// When this setter is assigned and a pending tap was buffered (because the
  /// terminated-state tap fired before the app UI was ready), the buffered
  /// tap is replayed immediately.
  void Function(Map<String, dynamic> data)? _onNotificationTap;
  Map<String, dynamic>? _pendingTap;

  set onNotificationTap(void Function(Map<String, dynamic> data)? handler) {
    _onNotificationTap = handler;
    // Replay any buffered tap once a listener is attached
    if (handler != null && _pendingTap != null) {
      final buffered = _pendingTap!;
      _pendingTap = null;
      // Defer to next microtask so the caller's initState finishes first
      Future.microtask(() => handler(buffered));
    }
  }

  void Function(Map<String, dynamic> data)? get onNotificationTap =>
      _onNotificationTap;

  void _dispatchTap(Map<String, dynamic> data) {
    final handler = _onNotificationTap;
    if (handler != null) {
      handler(data);
    } else {
      // No listener yet — buffer for replay once one attaches
      _pendingTap = data;
    }
  }

  /// The conversation ID currently being viewed — suppress message push
  /// notifications for this conversation to avoid self-notifications.
  String? activeConversationId;

  /// Initialize Firebase Messaging and local notifications.
  Future<void> initialize() async {
    if (_initialized) return;
    _initialized = true;

    _messaging = FirebaseMessaging.instance;
    _localNotifications = FlutterLocalNotificationsPlugin();

    // Register background handler
    FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

    // Request permission
    final settings = await _messaging!.requestPermission(
      alert: true,
      badge: true,
      sound: true,
      provisional: false,
    );
    debugPrint('[FCM] Permission: ${settings.authorizationStatus}');

    if (settings.authorizationStatus == AuthorizationStatus.denied) {
      return;
    }

    // Setup local notifications for foreground display
    await _setupLocalNotifications();

    // Get token and register with backend
    await _getAndRegisterToken();

    // Listen for token refresh
    _messaging!.onTokenRefresh.listen(_onTokenRefresh);

    // Handle foreground messages
    FirebaseMessaging.onMessage.listen(_onForegroundMessage);

    // Handle notification taps (app was in background)
    FirebaseMessaging.onMessageOpenedApp.listen(_onMessageOpenedApp);

    // Check if app was opened from a terminated state notification
    final initialMessage = await _messaging!.getInitialMessage();
    if (initialMessage != null) {
      _onMessageOpenedApp(initialMessage);
    }

    // iOS: set foreground notification presentation options
    if (Platform.isIOS) {
      await _messaging!.setForegroundNotificationPresentationOptions(
        alert: true,
        badge: true,
        sound: true,
      );
    }
  }

  Future<void> _setupLocalNotifications() async {
    const androidChannel = AndroidNotificationChannel(
      'high_importance_channel',
      'High Importance Notifications',
      description: 'Used for important notifications',
      importance: Importance.high,
    );

    await _localNotifications!
        .resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()
        ?.createNotificationChannel(androidChannel);

    const androidSettings =
        AndroidInitializationSettings('@mipmap/ic_launcher');
    const iosSettings = DarwinInitializationSettings(
      requestAlertPermission: false,
      requestBadgePermission: false,
      requestSoundPermission: false,
    );

    await _localNotifications!.initialize(
      const InitializationSettings(
        android: androidSettings,
        iOS: iosSettings,
      ),
      onDidReceiveNotificationResponse: (response) {
        final payload = response.payload;
        if (payload == null || payload.isEmpty) {
          _dispatchTap(const {});
          return;
        }
        try {
          final data = jsonDecode(payload) as Map<String, dynamic>;
          _dispatchTap(data);
        } catch (_) {
          // Legacy payloads may be a bare actionUrl string
          _dispatchTap({'actionUrl': payload});
        }
      },
    );
  }

  Future<void> _getAndRegisterToken() async {
    try {
      // On iOS, wait for APNS token before requesting FCM token.
      // Apple may not deliver it immediately — retry a few times.
      if (Platform.isIOS) {
        String? apnsToken;
        for (int attempt = 0; attempt < 5; attempt++) {
          apnsToken = await _messaging!.getAPNSToken();
          if (apnsToken != null) break;
          debugPrint('[FCM] APNS token not available yet, retrying in ${attempt + 1}s...');
          await Future.delayed(Duration(seconds: attempt + 1));
        }
        if (apnsToken == null) {
          debugPrint('[FCM] APNS token not available after retries (simulator?), skipping FCM token registration');
          return;
        }
      }
      final token = await _messaging!.getToken();
      if (token != null) {
        _currentToken = token;
        debugPrint('[FCM] Token: $token');
        await _registerTokenWithBackend(token);
      }
    } catch (e) {
      debugPrint('[FCM] Failed to get token: $e');
    }
  }

  Future<void> _onTokenRefresh(String token) async {
    debugPrint('[FCM] Token refreshed: $token');
    _currentToken = token;
    await _registerTokenWithBackend(token);
  }

  Future<void> _registerTokenWithBackend(String token) async {
    try {
      await _repo.registerFcmToken(token, Platform.isIOS ? 'ios' : 'android');
      debugPrint('[FCM] Token registered with backend');
    } catch (e) {
      debugPrint('[FCM] Failed to register token: $e');
    }
  }

  /// Callback to refresh notification count when FCM arrives
  void Function()? onNotificationReceived;

  void _onForegroundMessage(RemoteMessage message) {
    debugPrint('[FCM] Foreground message: ${message.notification?.title}');

    final notification = message.notification;
    if (notification == null) return;

    // Suppress message push notifications when user is viewing that conversation
    final msgType = message.data['type'] as String?;
    final convId = message.data['conversationId'] as String?;
    if (msgType == 'message' && convId != null && convId == activeConversationId) {
      debugPrint('[FCM] Suppressed notification — user is viewing conversation $convId');
      return;
    }

    // Refresh notification count immediately
    onNotificationReceived?.call();

    // Show local notification on Android (iOS handles it natively)
    if (Platform.isAndroid) {
      _localNotifications?.show(
        notification.hashCode,
        notification.title,
        notification.body,
        const NotificationDetails(
          android: AndroidNotificationDetails(
            'high_importance_channel',
            'High Importance Notifications',
            channelDescription: 'Used for important notifications',
            importance: Importance.high,
            priority: Priority.high,
            icon: '@mipmap/ic_launcher',
          ),
        ),
        // Encode the full FCM data as JSON so the tap handler can route
        // by `type` (e.g. open the specific chat for message notifications).
        payload: jsonEncode(message.data),
      );
    }
  }

  void _onMessageOpenedApp(RemoteMessage message) {
    debugPrint('[FCM] Notification tap: ${message.data}');
    _dispatchTap(Map<String, dynamic>.from(message.data));
  }

  /// Unregister FCM token from backend and delete locally (call on logout).
  Future<void> unregister() async {
    // Remove from backend so it stops sending push to this device
    if (_currentToken != null) {
      try {
        await _repo.removeFcmToken(_currentToken!);
        debugPrint('[FCM] Token unregistered from backend');
      } catch (e) {
        debugPrint('[FCM] Failed to unregister token: $e');
      }
    }
    _currentToken = null;

    // Delete the local FCM token so Firebase stops delivering notifications
    try {
      await _messaging?.deleteToken();
      debugPrint('[FCM] Local FCM token deleted');
    } catch (e) {
      debugPrint('[FCM] Failed to delete local token: $e');
    }
  }

  /// Re-register token after login.
  Future<void> registerAfterLogin() async {
    await _getAndRegisterToken();
  }
}
