// SPDX-FileCopyrightText: © 2025 Nøkken.io <nokken.io@proton.me>
// SPDX-License-Identifier: AGPL-3.0
//
//  calendar_event_tracker.dart
//  Tracks medication schedules and events for the calendar
//
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:nokken/src/core/utils/date_time_formatter.dart';
import 'package:nokken/src/features/bloodwork_tracker/models/bloodwork.dart';
import 'package:nokken/src/features/bloodwork_tracker/providers/bloodwork_state.dart';
import 'package:nokken/src/features/medication_tracker/models/medication.dart';
import 'package:nokken/src/features/mood_tracker/models/mood_entry.dart';
import 'package:nokken/src/features/mood_tracker/providers/mood_state.dart';
import 'package:nokken/src/features/scheduler/services/medication_schedule_service.dart';

/// Provider to efficiently track medication dates for the calendar
final medicationDatesProvider = Provider.family<MedicationDateInfo, DateTime>(
  (ref, focusedMonth) {
    final medications = ref.watch(medicationsProvider);
    final bloodworkDates = ref.watch(bloodworkDatesProvider);
    final moodEntries = ref.watch(moodEntriesProvider);

    // Create the medication date tracking service
    final tracker = CalendarEventTracker(
      medications: medications,
      bloodworkDates: bloodworkDates,
      moodEntries: moodEntries,
    );

    // Get the visible range for the calendar
    final visibleRange = DateTimeFormatter.visibleCalendarRange(focusedMonth);

    // Calculate medication dates for the visible range
    return tracker.calculateMedicationDates(visibleRange);
  },
  dependencies: [
    medicationsProvider,
    bloodworkDatesProvider,
    moodEntriesProvider
  ],
);

/// Provider for all medications
final medicationsProvider = StateProvider<List<Medication>>((ref) => []);

/// Data class to store medication date information
class MedicationDateInfo {
  final Map<DateTime, bool> injectionDates;
  final Map<DateTime, bool> patchDates;
  final Map<DateTime, bool> oralDates;
  final Map<DateTime, bool> topicalDates;
  final Map<DateTime, AppointmentType?> bloodworkDates;
  final Map<DateTime, MoodRating?> moodEntryDates;

  MedicationDateInfo({
    required this.injectionDates,
    required this.patchDates,
    required this.oralDates,
    required this.topicalDates,
    required this.bloodworkDates,
    required this.moodEntryDates,
  });
}

/// Tracks medication schedules and events for the calendar
///
/// Provides optimized access to medication and event data for specific date ranges
class CalendarEventTracker {
  final List<Medication> medications;
  final Set<DateTime> bloodworkDates;
  final List<MoodEntry> moodEntries;

  // Cached data for performance optimization
  final Map<DateTime, bool> _cachedInjectionDates = {};
  final Map<DateTime, bool> _cachedPatchDates = {};
  final Map<DateTime, bool> _cachedOralDates = {};
  final Map<DateTime, bool> _cachedTopicalDates = {};
  final Map<DateTime, AppointmentType?> _cachedBloodworkTypes = {};
  final Map<DateTime, MoodRating?> _cachedMoodEntries = {};

  /// Creates a new CalendarEventTracker
  CalendarEventTracker({
    required this.medications,
    required this.bloodworkDates,
    required this.moodEntries,
  });

  /// Calculates medication dates for a specific date range
  ///
  /// Efficiently caches results to avoid recalculation
  MedicationDateInfo calculateMedicationDates(Iterable<DateTime> dateRange) {
    // Clear previous cached data if medications have changed
    final dateRangeSet = dateRange.toSet();

    // Process medications by type
    _processInjectionMedications(dateRangeSet);
    _processPatchMedications(dateRangeSet);
    _processOralMedications(dateRangeSet);
    _processTopicalMedications(dateRangeSet);

    // Process bloodwork dates
    _processBloodworkDates();

    // Process mood entries
    _processMoodEntries();

    // Return a snapshot of the current date information
    return MedicationDateInfo(
      injectionDates: Map.from(_cachedInjectionDates),
      patchDates: Map.from(_cachedPatchDates),
      oralDates: Map.from(_cachedOralDates),
      topicalDates: Map.from(_cachedTopicalDates),
      bloodworkDates: Map.from(_cachedBloodworkTypes),
      moodEntryDates: Map.from(_cachedMoodEntries),
    );
  }

  void _processMedicationsByType(Set<DateTime> dateRange,
      MedicationType medicationType, Map<DateTime, bool> cacheMap) {
    final filteredMedications = medications
        .where((med) => med.medicationType == medicationType)
        .toList();

    if (filteredMedications.isEmpty) return;

    for (final date in dateRange) {
      final normalizedDate = DateTimeFormatter.normalizeDate(date);

      // Skip if we've already calculated for this date
      if (cacheMap.containsKey(normalizedDate)) continue;

      // Check if any medications of this type are scheduled for this date
      final hasMedication = filteredMedications.any((medication) {
        final medsForDay = MedicationScheduleService.getMedicationsForDate(
            [medication], normalizedDate);
        return medsForDay.isNotEmpty;
      });

      cacheMap[normalizedDate] = hasMedication;
    }
  }

// Then use this generic method for each medication type
  void _processInjectionMedications(Set<DateTime> dateRange) {
    _processMedicationsByType(
        dateRange, MedicationType.injection, _cachedInjectionDates);
  }

  void _processPatchMedications(Set<DateTime> dateRange) {
    _processMedicationsByType(
        dateRange, MedicationType.patch, _cachedPatchDates);
  }

  void _processOralMedications(Set<DateTime> dateRange) {
    _processMedicationsByType(
        dateRange, MedicationType.patch, _cachedOralDates);
  }

  void _processTopicalMedications(Set<DateTime> dateRange) {
    _processMedicationsByType(
        dateRange, MedicationType.patch, _cachedTopicalDates);
  }

  /// Processes bloodwork dates and appointment types
  void _processBloodworkDates() {
    for (final date in bloodworkDates) {
      final normalizedDate = DateTimeFormatter.normalizeDate(date);

      // Skip if we've already calculated for this date
      if (_cachedBloodworkTypes.containsKey(normalizedDate)) continue;

      // Set a default appointment type (will be updated later when we get the actual data)
      _cachedBloodworkTypes[normalizedDate] = AppointmentType.bloodwork;
    }
  }

  /// Processes mood entries to cache dates and ratings
  void _processMoodEntries() {
    for (final entry in moodEntries) {
      final normalizedDate = DateTimeFormatter.normalizeDate(entry.date);

      // Store the mood rating for this date
      _cachedMoodEntries[normalizedDate] = entry.mood;
    }
  }

  /// Gets the appointment type for a specific date
  AppointmentType? getAppointmentTypeForDate(
      DateTime date, List<Bloodwork> bloodworkRecords) {
    final normalizedDate = DateTimeFormatter.normalizeDate(date);

    // Check if we have cached the appointment type
    if (_cachedBloodworkTypes.containsKey(normalizedDate)) {
      return _cachedBloodworkTypes[normalizedDate];
    }

    // Get bloodwork records for this date
    final recordsForDate = bloodworkRecords.where((record) {
      final recordDate = DateTimeFormatter.normalizeDate(record.date);
      return recordDate.isAtSameMomentAs(normalizedDate);
    }).toList();

    // Determine appointment type based on priority
    if (recordsForDate.isNotEmpty) {
      if (recordsForDate
          .any((r) => r.appointmentType == AppointmentType.surgery)) {
        _cachedBloodworkTypes[normalizedDate] = AppointmentType.surgery;
        return AppointmentType.surgery;
      } else if (recordsForDate
          .any((r) => r.appointmentType == AppointmentType.appointment)) {
        _cachedBloodworkTypes[normalizedDate] = AppointmentType.appointment;
        return AppointmentType.appointment;
      } else {
        _cachedBloodworkTypes[normalizedDate] = AppointmentType.bloodwork;
        return AppointmentType.bloodwork;
      }
    }

    // No bloodwork records for this date
    return null;
  }

  /// Checks if a date has an injection due
  bool hasInjectionDue(DateTime date) {
    final normalizedDate = DateTimeFormatter.normalizeDate(date);
    return _cachedInjectionDates[normalizedDate] ?? false;
  }

  /// Checks if a date has a patch due
  bool hasPatchDue(DateTime date) {
    final normalizedDate = DateTimeFormatter.normalizeDate(date);
    return _cachedPatchDates[normalizedDate] ?? false;
  }

  /// Checks if a date has oral medication due
  bool hasOralMedicationDue(DateTime date) {
    final normalizedDate = DateTimeFormatter.normalizeDate(date);
    return _cachedOralDates[normalizedDate] ?? false;
  }

  /// Checks if a date has topical medication due
  bool hasTopicalMedicationDue(DateTime date) {
    final normalizedDate = DateTimeFormatter.normalizeDate(date);
    return _cachedTopicalDates[normalizedDate] ?? false;
  }

  /// Gets the mood rating for a specific date
  MoodRating? getMoodForDate(DateTime date) {
    final normalizedDate = DateTimeFormatter.normalizeDate(date);
    return _cachedMoodEntries[normalizedDate];
  }
}
