import 'package:hive_flutter/hive_flutter.dart';
import 'package:intl/intl.dart';

import '../local/models.dart';
import 'myquran_service.dart';

class ScheduleCacheStatus {
  final DateTime? startDate;
  final DateTime? endDate;
  final int cachedDays;
  final int daysUntilRefresh;

  const ScheduleCacheStatus({
    required this.startDate,
    required this.endDate,
    required this.cachedDays,
    required this.daysUntilRefresh,
  });

  const ScheduleCacheStatus.empty()
      : startDate = null,
        endDate = null,
        cachedDays = 0,
        daysUntilRefresh = -1;

  bool get hasData => startDate != null && endDate != null && cachedDays > 0;
  bool get isExpired => hasData && daysUntilRefresh < 0;
  bool get needsRefreshSoon => hasData && daysUntilRefresh <= 3;

  static ScheduleCacheStatus fromSchedules(
    Iterable<DailyPrayerSchedule> schedules,
    DateTime referenceDate,
  ) {
    DateTime? startDate;
    DateTime? endDate;
    var cachedDays = 0;

    for (final schedule in schedules) {
      final parsedDate = DateTime.tryParse(schedule.date);
      if (parsedDate == null) continue;

      final normalized = DateTime(
        parsedDate.year,
        parsedDate.month,
        parsedDate.day,
      );

      cachedDays++;
      startDate = startDate == null || normalized.isBefore(startDate)
          ? normalized
          : startDate;
      endDate = endDate == null || normalized.isAfter(endDate)
          ? normalized
          : endDate;
    }

    if (startDate == null || endDate == null || cachedDays == 0) {
      return const ScheduleCacheStatus.empty();
    }

    final today = DateTime(
      referenceDate.year,
      referenceDate.month,
      referenceDate.day,
    );

    return ScheduleCacheStatus(
      startDate: startDate,
      endDate: endDate,
      cachedDays: cachedDays,
      daysUntilRefresh: endDate.difference(today).inDays,
    );
  }
}

/// Service to sync monthly prayer data from MyQuran API → Hive.
class SyncService {
  SyncService._();
  static final SyncService instance = SyncService._();
  static const Duration _autoRefreshCooldown = Duration(hours: 12);

  static Set<String> rollingWindowMonths(DateTime referenceDate) {
    final currentMonth = DateTime(referenceDate.year, referenceDate.month, 1);
    final nextMonth = DateTime(referenceDate.year, referenceDate.month + 1, 1);
    return {
      DateFormat('yyyy-MM').format(currentMonth),
      DateFormat('yyyy-MM').format(nextMonth),
    };
  }

  static List<String> staleScheduleKeys(
    Iterable<String> keys,
    DateTime referenceDate,
  ) {
    final allowedMonths = rollingWindowMonths(referenceDate);
    final staleKeys = <String>[];

    for (final key in keys) {
      final parsed = DateTime.tryParse(key);
      if (parsed == null) {
        staleKeys.add(key);
        continue;
      }

      final monthKey = DateFormat('yyyy-MM').format(parsed);
      if (!allowedMonths.contains(monthKey)) {
        staleKeys.add(key);
      }
    }

    return staleKeys;
  }

  Future<void> _pruneScheduleCache(
    Box<DailyPrayerSchedule> scheduleBox,
    DateTime referenceDate,
  ) async {
    final staleKeys = staleScheduleKeys(
      scheduleBox.keys.cast<String>(),
      referenceDate,
    );
    if (staleKeys.isNotEmpty) {
      await scheduleBox.deleteAll(staleKeys);
      await scheduleBox.compact();
    }
  }

  bool _shouldAttemptAutoRefresh({
    required ScheduleCacheStatus status,
    required bool hasTodayData,
  }) {
    return !hasTodayData || !status.hasData || status.needsRefreshSoon;
  }

  DateTime? _parseAttemptTimestamp(String? value) {
    if (value == null || value.isEmpty) return null;
    return DateTime.tryParse(value);
  }

  /// Sync current month + next month prayer data for the configured city.
  /// Returns true on success.
  Future<bool> syncMonthlyData({DateTime? referenceDate}) async {
    final settingsBox = Hive.box<AppSettings>(HiveBoxes.settings);
    final settings = settingsBox.get('default');
    if (settings == null) return false;

    final cityId = settings.cityIdApi;
    final now = referenceDate ?? DateTime.now();
    final currentMonth = DateFormat('yyyy-MM').format(now);

    // Also fetch next month for continuity
    final nextMonthDate = DateTime(now.year, now.month + 1, 1);
    final nextMonth = DateFormat('yyyy-MM').format(nextMonthDate);

    final api = MyQuranSholatService.instance;
    final scheduleBox = Hive.box<DailyPrayerSchedule>(HiveBoxes.prayerSchedule);

    var success = false;
    var hasCurrentMonth = false;
    var hasNextMonth = false;

    // Fetch current month
    final currentData = await api.getMonthlySchedule(cityId, currentMonth);
    if (currentData.isNotEmpty) {
      hasCurrentMonth = true;
      for (final entry in currentData.entries) {
        final jadwal = entry.value;
        scheduleBox.put(
          entry.key,
          DailyPrayerSchedule(
            date: entry.key,
            imsak: jadwal['imsak'] ?? '00:00',
            subuh: jadwal['subuh'] ?? '00:00',
            terbit: jadwal['terbit'] ?? '00:00',
            dhuha: jadwal['dhuha'] ?? '00:00',
            dzuhur: jadwal['dzuhur'] ?? '00:00',
            ashar: jadwal['ashar'] ?? '00:00',
            maghrib: jadwal['maghrib'] ?? '00:00',
            isya: jadwal['isya'] ?? '00:00',
          ),
        );
      }
      success = true;
    }

    // Fetch next month
    final nextData = await api.getMonthlySchedule(cityId, nextMonth);
    if (nextData.isNotEmpty) {
      hasNextMonth = true;
      for (final entry in nextData.entries) {
        final jadwal = entry.value;
        scheduleBox.put(
          entry.key,
          DailyPrayerSchedule(
            date: entry.key,
            imsak: jadwal['imsak'] ?? '00:00',
            subuh: jadwal['subuh'] ?? '00:00',
            terbit: jadwal['terbit'] ?? '00:00',
            dhuha: jadwal['dhuha'] ?? '00:00',
            dzuhur: jadwal['dzuhur'] ?? '00:00',
            ashar: jadwal['ashar'] ?? '00:00',
            maghrib: jadwal['maghrib'] ?? '00:00',
            isya: jadwal['isya'] ?? '00:00',
          ),
        );
      }
    }

    if (success) {
      if (hasCurrentMonth && hasNextMonth) {
        await _pruneScheduleCache(scheduleBox, now);
      }
      settings.lastSyncDate = DateFormat('yyyy-MM-dd HH:mm').format(now);
      await settings.save();
    }

    return success;
  }

  Future<AutoRefreshResult> autoRefreshIfNeeded({
    DateTime? referenceDate,
  }) async {
    final settingsBox = Hive.box<AppSettings>(HiveBoxes.settings);
    final settings = settingsBox.get('default');
    if (settings == null) {
      return const AutoRefreshResult.skipped('settings-missing');
    }

    final now = referenceDate ?? DateTime.now();
    final status = getCacheStatus(now);
    final hasTodayData = getTodaySchedule(now) != null;

    if (!_shouldAttemptAutoRefresh(status: status, hasTodayData: hasTodayData)) {
      return const AutoRefreshResult.skipped('cache-fresh');
    }

    final lastAttempt = _parseAttemptTimestamp(settings.lastAutoSyncAttemptDate);
    if (lastAttempt != null &&
        now.difference(lastAttempt) < _autoRefreshCooldown) {
      return const AutoRefreshResult.skipped('cooldown');
    }

    settings.lastAutoSyncAttemptDate = now.toIso8601String();
    await settings.save();

    final synced = await syncMonthlyData(referenceDate: now);
    return synced
        ? const AutoRefreshResult.synced()
        : const AutoRefreshResult.failed('sync-failed');
  }

  /// Get today's prayer schedule from local Hive cache.
  DailyPrayerSchedule? getTodaySchedule([DateTime? targetDate]) {
    final scheduleBox =
        Hive.box<DailyPrayerSchedule>(HiveBoxes.prayerSchedule);
    final dateToFetch = targetDate ?? DateTime.now();
    final dateStr = DateFormat('yyyy-MM-dd').format(dateToFetch);
    return scheduleBox.get(dateStr);
  }

  ScheduleCacheStatus getCacheStatus([DateTime? referenceDate]) {
    final scheduleBox =
        Hive.box<DailyPrayerSchedule>(HiveBoxes.prayerSchedule);
    return ScheduleCacheStatus.fromSchedules(
      scheduleBox.values,
      referenceDate ?? DateTime.now(),
    );
  }
}

class AutoRefreshResult {
  final bool attempted;
  final bool synced;
  final String reason;

  const AutoRefreshResult._({
    required this.attempted,
    required this.synced,
    required this.reason,
  });

  const AutoRefreshResult.skipped(String reason)
      : this._(attempted: false, synced: false, reason: reason);

  const AutoRefreshResult.synced()
      : this._(attempted: true, synced: true, reason: 'synced');

  const AutoRefreshResult.failed(String reason)
      : this._(attempted: true, synced: false, reason: reason);
}
