import 'dart:io' show Platform;

import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:timezone/data/latest.dart' as tz_data;
import 'package:timezone/timezone.dart' as tz;

import '../local/models/app_settings.dart';
import 'notification_analytics_service.dart';
import 'notification_runtime_service.dart';

class NotificationPermissionStatus {
  const NotificationPermissionStatus({
    required this.notificationsAllowed,
    required this.exactAlarmAllowed,
  });

  final bool notificationsAllowed;
  final bool exactAlarmAllowed;
}

class NotificationPendingAlert {
  const NotificationPendingAlert({
    required this.id,
    required this.type,
    required this.title,
    required this.body,
    required this.scheduledAt,
  });

  final int id;
  final String type;
  final String title;
  final String body;
  final DateTime? scheduledAt;
}

/// Notification service for Adzan and Iqamah reminders.
///
/// This service owns the local notifications setup, permission requests,
/// timezone setup, and scheduling lifecycle for prayer notifications.
class NotificationService {
  NotificationService._();
  static final NotificationService instance = NotificationService._();

  final FlutterLocalNotificationsPlugin _plugin =
      FlutterLocalNotificationsPlugin();

  bool _initialized = false;
  String? _lastSyncSignature;
  static const int _checklistReminderId = 920001;

  static const _adhanDetails = NotificationDetails(
    android: AndroidNotificationDetails(
      'adhan_channel',
      'Adzan Notifications',
      channelDescription: 'Pengingat waktu adzan',
      importance: Importance.max,
      priority: Priority.high,
      playSound: true,
    ),
    iOS: DarwinNotificationDetails(
      presentAlert: true,
      presentBadge: true,
      presentSound: true,
    ),
    macOS: DarwinNotificationDetails(
      presentAlert: true,
      presentBadge: true,
      presentSound: true,
    ),
  );

  static const _iqamahDetails = NotificationDetails(
    android: AndroidNotificationDetails(
      'iqamah_channel',
      'Iqamah Reminders',
      channelDescription: 'Pengingat waktu iqamah',
      importance: Importance.high,
      priority: Priority.high,
      playSound: true,
    ),
    iOS: DarwinNotificationDetails(
      presentAlert: true,
      presentSound: true,
    ),
    macOS: DarwinNotificationDetails(
      presentAlert: true,
      presentSound: true,
    ),
  );

  static const _habitDetails = NotificationDetails(
    android: AndroidNotificationDetails(
      'habit_channel',
      'Pengingat Ibadah Harian',
      channelDescription: 'Pengingat checklist, streak, dan kebiasaan ibadah',
      importance: Importance.high,
      priority: Priority.high,
      playSound: true,
    ),
    iOS: DarwinNotificationDetails(
      presentAlert: true,
      presentSound: true,
    ),
    macOS: DarwinNotificationDetails(
      presentAlert: true,
      presentSound: true,
    ),
  );

  static const _systemDetails = NotificationDetails(
    android: AndroidNotificationDetails(
      'system_channel',
      'Peringatan Sistem',
      channelDescription: 'Peringatan status izin dan sinkronisasi jadwal',
      importance: Importance.defaultImportance,
      priority: Priority.defaultPriority,
      playSound: false,
    ),
    iOS: DarwinNotificationDetails(
      presentAlert: true,
      presentSound: false,
    ),
    macOS: DarwinNotificationDetails(
      presentAlert: true,
      presentSound: false,
    ),
  );

  /// Initialize plugin, permissions, and timezone once.
  Future<void> init() async {
    if (_initialized) return;

    tz_data.initializeTimeZones();
    _configureLocalTimeZone();

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

    const settings = InitializationSettings(
      android: androidSettings,
      iOS: darwinSettings,
      macOS: darwinSettings,
    );

    await _plugin.initialize(settings: settings);
    await _requestPermissions();
    _initialized = true;
  }

  void _configureLocalTimeZone() {
    final tzId = _resolveTimeZoneIdByOffset(DateTime.now().timeZoneOffset);
    try {
      tz.setLocalLocation(tz.getLocation(tzId));
    } catch (_) {
      tz.setLocalLocation(tz.UTC);
    }
  }

  // We prioritize Indonesian zones for better prayer scheduling defaults.
  String _resolveTimeZoneIdByOffset(Duration offset) {
    switch (offset.inMinutes) {
      case 420:
        return 'Asia/Jakarta';
      case 480:
        return 'Asia/Makassar';
      case 540:
        return 'Asia/Jayapura';
      default:
        if (offset.inMinutes % 60 == 0) {
          final etcHours = -(offset.inMinutes ~/ 60);
          final sign = etcHours >= 0 ? '+' : '';
          return 'Etc/GMT$sign$etcHours';
        }
        return 'UTC';
    }
  }

  Future<void> _requestPermissions() async {
    if (Platform.isAndroid) {
      final androidPlugin = _plugin.resolvePlatformSpecificImplementation<
          AndroidFlutterLocalNotificationsPlugin>();
      await androidPlugin?.requestNotificationsPermission();
      await androidPlugin?.requestExactAlarmsPermission();
      return;
    }

    if (Platform.isIOS) {
      await _plugin
          .resolvePlatformSpecificImplementation<
              IOSFlutterLocalNotificationsPlugin>()
          ?.requestPermissions(alert: true, badge: true, sound: true);
      return;
    }

    if (Platform.isMacOS) {
      await _plugin
          .resolvePlatformSpecificImplementation<
              MacOSFlutterLocalNotificationsPlugin>()
          ?.requestPermissions(alert: true, badge: true, sound: true);
    }
  }

  Future<void> syncPrayerNotifications({
    required String cityId,
    required Map<String, bool> adhanEnabled,
    required Map<String, int> iqamahOffset,
    required Map<String, Map<String, String>> schedulesByDate,
    required bool reportReminderEnabled,
    required int reportReminderDelayMinutes,
    required int reportReminderRepeatCount,
    required int reportReminderRepeatIntervalMinutes,
  }) async {
    await init();

    final hasAnyEnabled = adhanEnabled.values.any((v) => v);
    if (!hasAnyEnabled) {
      await _cancelPrayerPending();
      _lastSyncSignature = null;
      return;
    }

    final signature = _buildSyncSignature(
      cityId,
      adhanEnabled,
      iqamahOffset,
      schedulesByDate,
      reportReminderEnabled: reportReminderEnabled,
      reportReminderDelayMinutes: reportReminderDelayMinutes,
      reportReminderRepeatCount: reportReminderRepeatCount,
      reportReminderRepeatIntervalMinutes: reportReminderRepeatIntervalMinutes,
    );
    if (_lastSyncSignature == signature) return;

    await _cancelPrayerPending();

    final now = DateTime.now();
    final dateEntries = schedulesByDate.entries.toList()
      ..sort((a, b) => a.key.compareTo(b.key));

    for (final dateEntry in dateEntries) {
      final date = DateTime.tryParse(dateEntry.key);
      if (date == null) continue;

      for (final prayerKey in const [
        'subuh',
        'dzuhur',
        'ashar',
        'maghrib',
        'isya',
      ]) {
        final canonicalPrayer = _canonicalPrayerKey(prayerKey);
        if (canonicalPrayer == null) continue;
        if (!(adhanEnabled[canonicalPrayer] ?? false)) continue;

        final rawTime = (dateEntry.value[prayerKey] ?? '').trim();
        final prayerTime = _parseScheduleDateTime(date, rawTime);
        if (prayerTime == null || !prayerTime.isAfter(now)) continue;

        await _scheduleAdhan(
          id: _notificationId(
            cityId: cityId,
            dateKey: dateEntry.key,
            prayerKey: canonicalPrayer,
            isIqamah: false,
          ),
          prayerName: _localizedPrayerName(canonicalPrayer),
          time: prayerTime,
        );

        final offsetMinutes = iqamahOffset[canonicalPrayer] ?? 0;
        if (offsetMinutes <= 0) continue;

        final iqamahTime = prayerTime.add(Duration(minutes: offsetMinutes));
        if (!iqamahTime.isAfter(now)) continue;

        await _scheduleIqamah(
          id: _notificationId(
            cityId: cityId,
            dateKey: dateEntry.key,
            prayerKey: canonicalPrayer,
            isIqamah: true,
          ),
          prayerName: _localizedPrayerName(canonicalPrayer),
          iqamahTime: iqamahTime,
          offsetMinutes: offsetMinutes,
        );

        if (!reportReminderEnabled || reportReminderDelayMinutes <= 0) continue;
        final repeats =
            reportReminderRepeatCount < 0 ? 0 : reportReminderRepeatCount;
        final repeatGap = reportReminderRepeatIntervalMinutes <= 0
            ? 15
            : reportReminderRepeatIntervalMinutes;
        for (int index = 0; index <= repeats; index++) {
          final reminderAt = prayerTime.add(
            Duration(minutes: reportReminderDelayMinutes + (index * repeatGap)),
          );
          if (!reminderAt.isAfter(now)) continue;
          await _scheduleShalatReportReminder(
            id: _reportReminderId(
              cityId: cityId,
              dateKey: dateEntry.key,
              prayerKey: canonicalPrayer,
              reminderIndex: index,
            ),
            prayerName: _localizedPrayerName(canonicalPrayer),
            reminderTime: reminderAt,
            reminderIndex: index,
          );
        }
      }
    }

    _lastSyncSignature = signature;
  }

  Future<void> _scheduleAdhan({
    required int id,
    required String prayerName,
    required DateTime time,
  }) async {
    await _plugin.zonedSchedule(
      id: id,
      title: 'Adzan • $prayerName',
      body: 'Waktu sholat $prayerName telah masuk.',
      scheduledDate: tz.TZDateTime.from(time, tz.local),
      notificationDetails: _adhanDetails,
      androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
      payload: 'adhan|$prayerName|${time.toIso8601String()}',
    );
    await NotificationAnalyticsService.instance.track(
      'notif_push_scheduled',
      dimensions: <String, dynamic>{
        'event_type': 'adhan',
        'prayer': prayerName,
      },
    );
  }

  Future<void> _scheduleIqamah({
    required int id,
    required String prayerName,
    required DateTime iqamahTime,
    required int offsetMinutes,
  }) async {
    await _plugin.zonedSchedule(
      id: id,
      title: 'Iqamah • $prayerName',
      body: 'Iqamah $prayerName dalam $offsetMinutes menit.',
      scheduledDate: tz.TZDateTime.from(iqamahTime, tz.local),
      notificationDetails: _iqamahDetails,
      androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
      payload: 'iqamah|$prayerName|${iqamahTime.toIso8601String()}',
    );
    await NotificationAnalyticsService.instance.track(
      'notif_push_scheduled',
      dimensions: <String, dynamic>{
        'event_type': 'iqamah',
        'prayer': prayerName,
      },
    );
  }

  DateTime? _parseScheduleDateTime(DateTime date, String hhmm) {
    final match = RegExp(r'^(\d{1,2}):(\d{2})').firstMatch(hhmm);
    if (match == null) return null;

    final hour = int.tryParse(match.group(1) ?? '');
    final minute = int.tryParse(match.group(2) ?? '');
    if (hour == null || minute == null) return null;
    if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return null;
    return DateTime(date.year, date.month, date.day, hour, minute);
  }

  String? _canonicalPrayerKey(String scheduleKey) {
    switch (scheduleKey) {
      case 'subuh':
      case 'fajr':
        return 'fajr';
      case 'dzuhur':
      case 'dhuhr':
        return 'dhuhr';
      case 'ashar':
      case 'asr':
        return 'asr';
      case 'maghrib':
        return 'maghrib';
      case 'isya':
      case 'isha':
        return 'isha';
      default:
        return null;
    }
  }

  String _localizedPrayerName(String canonicalPrayerKey) {
    switch (canonicalPrayerKey) {
      case 'fajr':
        return 'Subuh';
      case 'dhuhr':
        return 'Dzuhur';
      case 'asr':
        return 'Ashar';
      case 'maghrib':
        return 'Maghrib';
      case 'isha':
        return 'Isya';
      default:
        return canonicalPrayerKey;
    }
  }

  int _notificationId({
    required String cityId,
    required String dateKey,
    required String prayerKey,
    required bool isIqamah,
  }) {
    final seed = '$cityId|$dateKey|$prayerKey|${isIqamah ? 'iqamah' : 'adhan'}';
    var hash = 17;
    for (final rune in seed.runes) {
      hash = 37 * hash + rune;
    }
    final bounded = hash.abs() % 700000;
    return isIqamah ? bounded + 800000 : bounded + 100000;
  }

  int _reportReminderId({
    required String cityId,
    required String dateKey,
    required String prayerKey,
    required int reminderIndex,
  }) {
    final seed = '$cityId|$dateKey|$prayerKey|report|$reminderIndex';
    var hash = 23;
    for (final rune in seed.runes) {
      hash = 43 * hash + rune;
    }
    return 700000 + (hash.abs() % 90000);
  }

  String _buildSyncSignature(
    String cityId,
    Map<String, bool> adhanEnabled,
    Map<String, int> iqamahOffset,
    Map<String, Map<String, String>> schedulesByDate, {
    required bool reportReminderEnabled,
    required int reportReminderDelayMinutes,
    required int reportReminderRepeatCount,
    required int reportReminderRepeatIntervalMinutes,
  }) {
    final sortedAdhan = adhanEnabled.entries.toList()
      ..sort((a, b) => a.key.compareTo(b.key));
    final sortedIqamah = iqamahOffset.entries.toList()
      ..sort((a, b) => a.key.compareTo(b.key));
    final sortedDates = schedulesByDate.entries.toList()
      ..sort((a, b) => a.key.compareTo(b.key));

    final buffer = StringBuffer(cityId);
    for (final e in sortedAdhan) {
      buffer.write('|${e.key}:${e.value ? 1 : 0}');
    }
    for (final e in sortedIqamah) {
      buffer.write('|${e.key}:${e.value}');
    }
    buffer.write(
      '|report:${reportReminderEnabled ? 1 : 0}:$reportReminderDelayMinutes:$reportReminderRepeatCount:$reportReminderRepeatIntervalMinutes',
    );
    for (final dateEntry in sortedDates) {
      buffer.write('|${dateEntry.key}');
      final times = dateEntry.value.entries.toList()
        ..sort((a, b) => a.key.compareTo(b.key));
      for (final t in times) {
        buffer.write('|${t.key}:${t.value}');
      }
    }
    return buffer.toString();
  }

  Future<void> _scheduleShalatReportReminder({
    required int id,
    required String prayerName,
    required DateTime reminderTime,
    required int reminderIndex,
  }) async {
    final attemptLabel = reminderIndex == 0 ? '' : ' (#${reminderIndex + 1})';
    await _plugin.zonedSchedule(
      id: id,
      title: 'Lapor Shalat • $prayerName$attemptLabel',
      body: 'Sudah shalat $prayerName? Yuk tandai di checklist sekarang.',
      scheduledDate: tz.TZDateTime.from(reminderTime, tz.local),
      notificationDetails: _habitDetails,
      androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
      payload: 'report|$prayerName|${reminderTime.toIso8601String()}',
    );
  }

  Future<void> cancelShalatReportReminders({
    required String cityId,
    required String dateKey,
    required String canonicalPrayer,
    required int repeatCount,
  }) async {
    final repeats = repeatCount < 0 ? 0 : repeatCount;
    for (int index = 0; index <= repeats; index++) {
      await _plugin.cancel(
        id: _reportReminderId(
          cityId: cityId,
          dateKey: dateKey,
          prayerKey: canonicalPrayer,
          reminderIndex: index,
        ),
      );
    }
  }

  Future<void> cancelAllPending() async {
    try {
      await _plugin.cancelAllPendingNotifications();
    } catch (_) {
      await _plugin.cancelAll();
    }
  }

  Future<void> _cancelPrayerPending() async {
    final pending = await _plugin.pendingNotificationRequests();
    for (final request in pending) {
      final id = request.id;
      final isPrayerSchedule = id >= 100000 && id < 900000;
      if (isPrayerSchedule) {
        await _plugin.cancel(id: id);
      }
    }
  }

  Future<int> pendingCount() async {
    final pending = await _plugin.pendingNotificationRequests();
    return pending.length;
  }

  Future<void> syncHabitNotifications({
    required AppSettings settings,
  }) async {
    await init();

    if (!settings.alertsEnabled || !settings.dailyChecklistReminderEnabled) {
      await cancelChecklistReminder();
      return;
    }

    final reminderTime = settings.checklistReminderTime ?? '09:00';
    final parts = _parseHourMinute(reminderTime);
    if (parts == null) {
      await cancelChecklistReminder();
      return;
    }

    final now = DateTime.now();
    var target = DateTime(
      now.year,
      now.month,
      now.day,
      parts.$1,
      parts.$2,
    );
    if (!target.isAfter(now)) {
      target = target.add(const Duration(days: 1));
    }

    await _plugin.zonedSchedule(
      id: _checklistReminderId,
      title: 'Checklist Ibadah Harian',
      body: 'Jangan lupa perbarui progres ibadah hari ini.',
      scheduledDate: tz.TZDateTime.from(target, tz.local),
      notificationDetails: _habitDetails,
      androidScheduleMode: AndroidScheduleMode.inexactAllowWhileIdle,
      matchDateTimeComponents: DateTimeComponents.time,
      payload: 'checklist|daily|${target.toIso8601String()}',
    );
  }

  Future<void> cancelChecklistReminder() async {
    await _plugin.cancel(id: _checklistReminderId);
  }

  int nonPrayerNotificationId(String seed) {
    var hash = 17;
    for (final rune in seed.runes) {
      hash = 41 * hash + rune;
    }
    return 900000 + (hash.abs() % 80000);
  }

  Future<bool> showNonPrayerAlert({
    required AppSettings settings,
    required int id,
    required String title,
    required String body,
    String payloadType = 'system',
    bool silent = false,
    bool bypassQuietHours = false,
    bool bypassDailyCap = false,
  }) async {
    await init();

    final runtime = NotificationRuntimeService.instance;
    if (!settings.alertsEnabled) return false;
    if (!bypassQuietHours && runtime.isWithinQuietHours(settings)) return false;
    if (!bypassDailyCap &&
        runtime.nonPrayerPushCountToday() >= settings.maxNonPrayerPushPerDay) {
      return false;
    }

    await _plugin.show(
      id: id,
      title: title,
      body: body,
      notificationDetails: silent ? _systemDetails : _habitDetails,
      payload: '$payloadType|non_prayer|${DateTime.now().toIso8601String()}',
    );

    if (!bypassDailyCap) {
      await runtime.incrementNonPrayerPushCount();
    }
    await NotificationAnalyticsService.instance.track(
      'notif_push_fired',
      dimensions: <String, dynamic>{
        'event_type': payloadType,
        'channel': 'push',
      },
    );
    return true;
  }

  Future<NotificationPermissionStatus> getPermissionStatus() async {
    await init();

    try {
      if (Platform.isAndroid) {
        final androidPlugin = _plugin.resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>();
        final notificationsAllowed =
            await androidPlugin?.areNotificationsEnabled() ?? true;
        final exactAlarmAllowed =
            await androidPlugin?.canScheduleExactNotifications() ?? true;
        return NotificationPermissionStatus(
          notificationsAllowed: notificationsAllowed,
          exactAlarmAllowed: exactAlarmAllowed,
        );
      }

      if (Platform.isIOS) {
        final iosPlugin = _plugin.resolvePlatformSpecificImplementation<
            IOSFlutterLocalNotificationsPlugin>();
        final options = await iosPlugin?.checkPermissions();
        return NotificationPermissionStatus(
          notificationsAllowed: options?.isEnabled ?? true,
          exactAlarmAllowed: true,
        );
      }

      if (Platform.isMacOS) {
        final macPlugin = _plugin.resolvePlatformSpecificImplementation<
            MacOSFlutterLocalNotificationsPlugin>();
        final options = await macPlugin?.checkPermissions();
        return NotificationPermissionStatus(
          notificationsAllowed: options?.isEnabled ?? true,
          exactAlarmAllowed: true,
        );
      }
    } catch (_) {
      // Fallback to non-blocking defaults if platform query fails.
    }

    return const NotificationPermissionStatus(
      notificationsAllowed: true,
      exactAlarmAllowed: true,
    );
  }

  Future<List<NotificationPendingAlert>> pendingAlerts() async {
    final pending = await _plugin.pendingNotificationRequests();
    final alerts = pending.map(_mapPendingRequest).toList()
      ..sort((a, b) {
        final aTime = a.scheduledAt;
        final bTime = b.scheduledAt;
        if (aTime == null && bTime == null) return a.id.compareTo(b.id);
        if (aTime == null) return 1;
        if (bTime == null) return -1;
        return aTime.compareTo(bTime);
      });
    return alerts;
  }

  NotificationPendingAlert _mapPendingRequest(PendingNotificationRequest raw) {
    final payload = raw.payload ?? '';
    final parts = payload.split('|');
    if (parts.length >= 3) {
      final type = parts[0].trim().toLowerCase();
      final title = raw.title ?? '${_labelForType(type)} • ${parts[1].trim()}';
      final body = raw.body ?? '';
      final scheduledAt = DateTime.tryParse(parts[2].trim());
      return NotificationPendingAlert(
        id: raw.id,
        type: type,
        title: title,
        body: body,
        scheduledAt: scheduledAt,
      );
    }

    final fallbackType = _inferTypeFromTitle(raw.title ?? '');
    return NotificationPendingAlert(
      id: raw.id,
      type: fallbackType,
      title: raw.title ?? 'Pengingat',
      body: raw.body ?? '',
      scheduledAt: null,
    );
  }

  String _inferTypeFromTitle(String title) {
    final normalized = title.toLowerCase();
    if (normalized.contains('iqamah')) return 'iqamah';
    if (normalized.contains('adzan')) return 'adhan';
    return 'alert';
  }

  String _labelForType(String type) {
    switch (type) {
      case 'adhan':
        return 'Adzan';
      case 'iqamah':
        return 'Iqamah';
      case 'checklist':
        return 'Checklist';
      case 'streak_risk':
        return 'Streak';
      case 'system':
        return 'Sistem';
      default:
        return 'Pengingat';
    }
  }

  (int, int)? _parseHourMinute(String hhmm) {
    final match = RegExp(r'^(\d{1,2}):(\d{2})$').firstMatch(hhmm.trim());
    if (match == null) return null;
    final hour = int.tryParse(match.group(1) ?? '');
    final minute = int.tryParse(match.group(2) ?? '');
    if (hour == null || minute == null) return null;
    if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return null;
    return (hour, minute);
  }
}
