// SPDX-FileCopyrightText: © 2025 Nøkken.io <nokken.io@proton.me>
// SPDX-License-Identifier: AGPL-3.0
//
// statistics_provider.dart
//
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:nokken/src/core/services/statistics/health_analytics_service.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/medication_tracker/providers/medication_state.dart';
import 'package:nokken/src/features/medication_tracker/providers/medication_taken_provider.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/stats/models/stat_analysis.dart'
    as stat_models;
import 'package:nokken/src/core/ui/widgets/timeframe_selector.dart';

/// Provides the HealthAnalyticsService instance for statistical analysis
final healthAnalyticsServiceProvider = Provider<HealthAnalyticsService>((ref) {
  debugPrint('Creating HealthAnalyticsService instance');
  return HealthAnalyticsService();
});

/// Normalize factor data to consistent length while preserving as much data as possible
///
/// Rather than truncating all data to the shortest series, this approach preserves
/// data points for factors with sufficient history while ensuring data is aligned.
Map<String, List<double>> _normalizeFactorData(
    Map<String, List<double>> originalData) {
  debugPrint('Normalizing factor data with ${originalData.length} factors');
  if (originalData.isEmpty) return {};

  try {
    // Define thresholds for viable data
    const int MINIMUM_DATA_POINTS = 15;

    // Filter out factors with insufficient data points
    final viableData = <String, List<double>>{};

    for (final factor in originalData.keys) {
      final points = originalData[factor]!.length;

      if (points >= MINIMUM_DATA_POINTS) {
        viableData[factor] = List<double>.from(originalData[factor]!);
        debugPrint('Factor $factor has $points data points - including');
      } else {
        debugPrint('Factor $factor has only $points data points - excluding');
      }
    }

    if (viableData.isEmpty) {
      debugPrint(
          'No factors with sufficient data points, using all available data');

      // Find the minimum length across all factors
      final values = originalData.values.toList();
      if (values.isEmpty) {
        debugPrint('No values to normalize');
        return {};
      }

      final minLength = values
          .map((list) => list.length)
          .fold<int>(values.first.length, (a, b) => a < b ? a : b);

      debugPrint('Minimum length across all factors: $minLength');

      // Create a new map with all factors trimmed to the same length
      final result = <String, List<double>>{};

      for (final factor in originalData.keys) {
        if (originalData[factor]!.length >= minLength) {
          result[factor] =
              List<double>.from(originalData[factor]!.sublist(0, minLength));
        }
      }

      return result;
    }

    // For viable data, find the minimum length
    final values = viableData.values.toList();
    final minLength = values
        .map((list) => list.length)
        .fold<int>(values.first.length, (a, b) => a < b ? a : b);

    // Create normalized map with all factors trimmed to same length
    final result = <String, List<double>>{};

    for (final factor in viableData.keys) {
      result[factor] =
          List<double>.from(viableData[factor]!.sublist(0, minLength));
    }

    return result;
  } catch (e, stack) {
    debugPrint('Error in _normalizeFactorData: $e');
    debugPrint(stack.toString());
    return {};
  }
}

/// Normalizes factor data with advanced handling of sparse and missing data
///
/// Handles cases where factors have different sampling frequencies or missing values
/// by implementing interpolation and alignment strategies
Map<String, List<double>> _advancedNormalizeFactorData(
    Map<String, List<double>> originalData, List<DateTime> dates) {
  if (originalData.isEmpty || dates.isEmpty) return {};

  try {
    const int MINIMUM_DATA_POINTS = 10;
    final result = <String, List<double>>{};

    // First pass - identify viable factors and their data ranges
    final viableFactors = <String>[];
    final startDates = <String, DateTime>{};
    final endDates = <String, DateTime>{};

    for (final factor in originalData.keys) {
      final points = originalData[factor]!.length;

      if (points >= MINIMUM_DATA_POINTS) {
        viableFactors.add(factor);

        // Record date range for this factor
        // Assume dates are sorted chronologically and match the factor data points
        final factorDates = dates.sublist(0, math.min(points, dates.length));
        if (factorDates.isNotEmpty) {
          startDates[factor] = factorDates.first;
          endDates[factor] = factorDates.last;
        }
      }
    }

    if (viableFactors.isEmpty) {
      // Fall back to basic normalization if no viable factors
      return _normalizeFactorData(originalData);
    }

    // Find common date range across viable factors
    DateTime? commonStart = null;
    DateTime? commonEnd = null;

    for (final factor in viableFactors) {
      final start = startDates[factor];
      final end = endDates[factor];

      if (start != null &&
          (commonStart == null || start.isAfter(commonStart))) {
        commonStart = start;
      }

      if (end != null && (commonEnd == null || end.isBefore(commonEnd))) {
        commonEnd = end;
      }
    }

    if (commonStart == null ||
        commonEnd == null ||
        commonStart.isAfter(commonEnd)) {
      // No common time range, fall back to basic normalization
      return _normalizeFactorData(originalData);
    }

    // Second pass - align and interpolate data within common date range
    for (final factor in viableFactors) {
      final factorData = originalData[factor]!;
      final factorDates =
          dates.sublist(0, math.min(factorData.length, dates.length));

      // Prepare aligned data array
      final alignedData = <double>[];

      // Find all dates within common range
      final commonDates = dates
          .where((date) =>
              !date.isBefore(commonStart!) && !date.isAfter(commonEnd!))
          .toList();

      // For each common date, interpolate value if necessary
      for (final date in commonDates) {
        // Find if we have exact data point for this date
        final exactIndex = factorDates.indexOf(date);

        if (exactIndex >= 0 && exactIndex < factorData.length) {
          // Use exact value
          alignedData.add(factorData[exactIndex]);
        } else {
          // Interpolate
          // Find closest date before
          DateTime? beforeDate;
          int? beforeIndex;

          for (int i = 0; i < factorDates.length; i++) {
            if (factorDates[i].isBefore(date)) {
              beforeDate = factorDates[i];
              beforeIndex = i;
            } else {
              break;
            }
          }

          // Find closest date after
          DateTime? afterDate;
          int? afterIndex;

          for (int i = 0; i < factorDates.length; i++) {
            if (factorDates[i].isAfter(date)) {
              afterDate = factorDates[i];
              afterIndex = i;
              break;
            }
          }

          if (beforeIndex != null && afterIndex != null) {
            // Linear interpolation
            final beforeValue = factorData[beforeIndex];
            final afterValue = factorData[afterIndex];

            final totalDuration =
                afterDate!.difference(beforeDate!).inMilliseconds;
            final currentDuration = date.difference(beforeDate).inMilliseconds;

            if (totalDuration > 0) {
              final ratio = currentDuration / totalDuration;
              final interpolatedValue =
                  beforeValue + (afterValue - beforeValue) * ratio;
              alignedData.add(interpolatedValue);
            } else {
              // Same date (shouldn't happen), use average
              alignedData.add((beforeValue + afterValue) / 2);
            }
          } else if (beforeIndex != null) {
            // Only have before value, use it
            alignedData.add(factorData[beforeIndex]);
          } else if (afterIndex != null) {
            // Only have after value, use it
            alignedData.add(factorData[afterIndex]);
          } else {
            // No data points available, use a placeholder
            // Impute missing value using factor average
            final average =
                factorData.reduce((a, b) => a + b) / factorData.length;
            alignedData.add(average);
          }
        }
      }

      // Only include if we have enough aligned data points
      if (alignedData.length >= MINIMUM_DATA_POINTS) {
        result[factor] = alignedData;
      }
    }

    return result;
  } catch (e, stack) {
    debugPrint('Error in _advancedNormalizeFactorData: $e');
    debugPrint(stack.toString());
    return _normalizeFactorData(originalData); // Fall back to basic method
  }
}

/// Detects seasonality patterns in time series data
///
/// @param values Time series data points
/// @param dates Corresponding dates for each data point
/// @return Map with seasonality information including period, strength, and type
Map<String, dynamic> _detectSeasonality(
    List<double> values, List<DateTime> dates) {
  if (values.length < 14 || values.length != dates.length) {
    return {'detected': false};
  }

  try {
    // Check for weekly patterns
    final weekdayAvgs = List<double>.filled(7, 0.0);
    final weekdayCounts = List<int>.filled(7, 0);

    for (int i = 0; i < values.length; i++) {
      final weekday = dates[i].weekday % 7; // 0-6
      weekdayAvgs[weekday] += values[i];
      weekdayCounts[weekday]++;
    }

    for (int i = 0; i < 7; i++) {
      if (weekdayCounts[i] > 0) {
        weekdayAvgs[i] /= weekdayCounts[i];
      }
    }

    // Calculate variance across weekdays
    final weekdayMean = weekdayAvgs.reduce((a, b) => a + b) / 7;
    double weekdayVariance = 0;

    for (int i = 0; i < 7; i++) {
      if (weekdayCounts[i] > 0) {
        weekdayVariance += math.pow(weekdayAvgs[i] - weekdayMean, 2);
      }
    }
    weekdayVariance /= 7;

    // Calculate overall series variance
    final seriesMean = values.reduce((a, b) => a + b) / values.length;
    double seriesVariance = 0;

    for (final value in values) {
      seriesVariance += math.pow(value - seriesMean, 2);
    }
    seriesVariance /= values.length;

    // Calculate weekly seasonality strength
    final weeklyStrength =
        seriesVariance > 0 ? weekdayVariance / seriesVariance : 0;

    // Check for monthly patterns (day of month)
    final monthlyAvgs = List<double>.filled(31, 0.0);
    final monthlyCounts = List<int>.filled(31, 0);

    for (int i = 0; i < values.length; i++) {
      final dayOfMonth = dates[i].day - 1; // 0-30
      monthlyAvgs[dayOfMonth] += values[i];
      monthlyCounts[dayOfMonth]++;
    }

    for (int i = 0; i < 31; i++) {
      if (monthlyCounts[i] > 0) {
        monthlyAvgs[i] /= monthlyCounts[i];
      }
    }

    // Calculate variance across days of month
    final validMonthlyAvgs = <double>[];
    for (int i = 0; i < 31; i++) {
      if (monthlyCounts[i] > 0) {
        validMonthlyAvgs.add(monthlyAvgs[i]);
      }
    }

    double monthlyVariance = 0;
    if (validMonthlyAvgs.isNotEmpty) {
      final monthlyMean =
          validMonthlyAvgs.reduce((a, b) => a + b) / validMonthlyAvgs.length;

      for (final value in validMonthlyAvgs) {
        monthlyVariance += math.pow(value - monthlyMean, 2);
      }
      monthlyVariance /= validMonthlyAvgs.length;
    }

    // Calculate monthly seasonality strength
    final monthlyStrength =
        seriesVariance > 0 ? monthlyVariance / seriesVariance : 0;

    // Determine which pattern is stronger
    if (weeklyStrength > 0.3 && weeklyStrength > monthlyStrength) {
      return {
        'detected': true,
        'type': 'weekly',
        'period': 7,
        'strength': weeklyStrength,
        'dayPatterns': weekdayAvgs,
      };
    } else if (monthlyStrength > 0.3) {
      return {
        'detected': true,
        'type': 'monthly',
        'period': 30,
        'strength': monthlyStrength,
        'dayPatterns': validMonthlyAvgs,
      };
    }

    return {'detected': false};
  } catch (e) {
    debugPrint('Error in _detectSeasonality: $e');
    return {'detected': false};
  }
}

/// Analyzes lag effects between two variables
///
/// Determines if changes in one variable consistently precede changes in another,
/// which may indicate a causal relationship with a time delay
Map<String, dynamic> _analyzeLagEffects(
    List<double> leadingVariable, List<double> followingVariable, int maxLag) {
  if (leadingVariable.length < 10 ||
      leadingVariable.length != followingVariable.length) {
    return {'detected': false};
  }

  try {
    // Compute correlations at different lag intervals
    final correlations = <int, double>{};
    final minLength = leadingVariable.length;

    // For each possible lag, calculate correlation
    for (int lag = 0; lag <= maxLag; lag++) {
      if (lag >= minLength) break;

      // Create aligned series
      final leads = leadingVariable.sublist(0, minLength - lag);
      final follows = followingVariable.sublist(lag);

      // Calculate correlation
      double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0, sumY2 = 0;
      final n = leads.length;

      for (int i = 0; i < n; i++) {
        sumX += leads[i];
        sumY += follows[i];
        sumXY += leads[i] * follows[i];
        sumX2 += leads[i] * leads[i];
        sumY2 += follows[i] * follows[i];
      }

      final denominator =
          math.sqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY));

      if (denominator != 0) {
        correlations[lag] = (n * sumXY - sumX * sumY) / denominator;
      } else {
        correlations[lag] = 0;
      }
    }

    // Find lag with maximum correlation
    int bestLag = 0;
    double maxCorrelation = correlations[0] ?? 0;

    for (final entry in correlations.entries) {
      if (entry.key > 0 && entry.value.abs() > maxCorrelation.abs()) {
        bestLag = entry.key;
        maxCorrelation = entry.value;
      }
    }

    // Only report if we find a meaningful lag effect
    if (bestLag > 0 && maxCorrelation.abs() >= 0.3) {
      return {
        'detected': true,
        'lag': bestLag,
        'correlation': maxCorrelation,
        'correlations': correlations,
      };
    }

    return {'detected': false};
  } catch (e) {
    debugPrint('Error in _analyzeLagEffects: $e');
    return {'detected': false};
  }
}

/// Detects anomalies in a time series using statistical methods
///
/// Identifies outliers and pattern breaks using Z-scores and moving averages
List<stat_models.HealthAnomaly> _detectAnomalies(
    List<double> values, List<DateTime> dates, String factorName) {
  if (values.length < 10 || values.length != dates.length) {
    return [];
  }

  final anomalies = <stat_models.HealthAnomaly>[];

  try {
    // Calculate mean and standard deviation
    final mean = values.reduce((a, b) => a + b) / values.length;

    double sumSquaredDiff = 0;
    for (final value in values) {
      sumSquaredDiff += math.pow(value - mean, 2);
    }

    final stdDev = math.sqrt(sumSquaredDiff / values.length);
    final threshold = 2.5; // Z-score threshold for anomalies

    // Detect outliers using Z-scores
    for (int i = 0; i < values.length; i++) {
      final zScore = (values[i] - mean) / (stdDev > 0 ? stdDev : 1);

      if (zScore.abs() > threshold) {
        String description;
        if (zScore > 0) {
          description = 'Unusually high $factorName';
        } else {
          description = 'Unusually low $factorName';
        }

        anomalies.add(stat_models.HealthAnomaly(
          date: dates[i],
          factorName: factorName,
          anomalyScore: zScore.abs() / 5, // Normalize to 0-1 scale
          description: description,
        ));
      }
    }

    // Detect pattern breaks using moving averages (if enough data)
    if (values.length >= 15) {
      final windowSize = 5;

      for (int i = windowSize; i < values.length - windowSize; i++) {
        // Calculate moving averages before and after this point
        double sumBefore = 0;
        for (int j = i - windowSize; j < i; j++) {
          sumBefore += values[j];
        }
        final avgBefore = sumBefore / windowSize;

        double sumAfter = 0;
        for (int j = i + 1; j <= i + windowSize; j++) {
          sumAfter += values[j];
        }
        final avgAfter = sumAfter / windowSize;

        // Calculate change in moving average
        final change = (avgAfter - avgBefore).abs();
        final relativeChange =
            change / (avgBefore.abs() > 0.01 ? avgBefore.abs() : 0.01);

        // Detect significant pattern breaks
        if (relativeChange > 0.4) {
          // 40% change threshold
          String description;
          if (avgAfter > avgBefore) {
            description = 'Significant increase in $factorName pattern';
          } else {
            description = 'Significant decrease in $factorName pattern';
          }

          // Check if we already have an anomaly for this date
          bool alreadyDetected = anomalies.any((a) => a.date == dates[i]);

          if (!alreadyDetected) {
            anomalies.add(stat_models.HealthAnomaly(
              date: dates[i],
              factorName: factorName,
              anomalyScore: math.min(relativeChange, 1.0), // Cap at 1.0
              description: description,
            ));
          }
        }
      }
    }

    return anomalies;
  } catch (e) {
    debugPrint('Error in _detectAnomalies: $e');
    return [];
  }
}

/// Provider for basic statistical insights from health data
///
/// Generates statistical insights for mood and related factors based on the selected timeframe.
/// Provides summary statistics, trend detection, and correlation analysis.
final statisticsInsightsProvider = FutureProvider.autoDispose
    .family<List<stat_models.StatisticalInsight>, String>(
        (ref, timeframe) async {
  try {
    debugPrint('Running statisticsInsightsProvider for timeframe: $timeframe');
    final service = ref.watch(healthAnalyticsServiceProvider);
    final moodEntries = ref.watch(moodEntriesProvider);

    // Filter entries based on timeframe
    final filteredEntries = TimeframeSelector.filterByTimeframe(
      items: moodEntries,
      timeframe: timeframe,
      getDate: (entry) => entry.date,
    );

    debugPrint('Filtered to ${filteredEntries.length} mood entries');

    if (filteredEntries.isEmpty) {
      debugPrint('No entries available for analysis');
      return [];
    }

    // Initialize the health analytics service
    if (!service.isInitialized) {
      debugPrint('Initializing HealthAnalyticsService');
      final initResult = await service.initialize();
      if (!initResult) {
        debugPrint('Failed to initialize HealthAnalyticsService');
        return [];
      }
    }

    // Generate insights based on the data
    return _generateInsightsFromMoodData(service, filteredEntries);
  } catch (e, stack) {
    debugPrint('Error in statisticsInsightsProvider: $e');
    debugPrint(stack.toString());
    return [];
  }
});

/// Provider for advanced seasonality analysis
///
/// Analyzes cyclical patterns in health data based on day of week, month, or other
/// time-based cycles. Can detect weekly and monthly patterns in mood and factors.
final seasonalityAnalysisProvider = FutureProvider.autoDispose
    .family<Map<String, dynamic>, String>((ref, timeframe) async {
  try {
    final moodEntries = ref.watch(moodEntriesProvider);

    // Filter entries based on timeframe
    final filteredEntries = TimeframeSelector.filterByTimeframe(
      items: moodEntries,
      timeframe: timeframe,
      getDate: (entry) => entry.date,
    );

    if (filteredEntries.length < 14) {
      return {'detected': false, 'reason': 'Insufficient data'};
    }

    // Extract mood scores and dates
    final moodScores =
        filteredEntries.map((e) => 4 - e.mood.index.toDouble()).toList();
    final dates = filteredEntries.map((e) => e.date).toList();

    // Analyze seasonality
    final moodSeasonality = _detectSeasonality(moodScores, dates);

    // Extract other factors if available
    final factorSeasonality = <String, Map<String, dynamic>>{};

    // Analyze sleep if available
    final entriesWithSleep =
        filteredEntries.where((e) => e.sleepQuality != null).toList();
    if (entriesWithSleep.length >= 14) {
      final sleepScores = entriesWithSleep
          .map((e) => 4 - e.sleepQuality!.index.toDouble())
          .toList();
      final sleepDates = entriesWithSleep.map((e) => e.date).toList();
      factorSeasonality['Sleep'] = _detectSeasonality(sleepScores, sleepDates);
    }

    // Analyze energy if available
    final entriesWithEnergy =
        filteredEntries.where((e) => e.energyLevel != null).toList();
    if (entriesWithEnergy.length >= 14) {
      final energyScores = entriesWithEnergy
          .map((e) => 4 - e.energyLevel!.index.toDouble())
          .toList();
      final energyDates = entriesWithEnergy.map((e) => e.date).toList();
      factorSeasonality['Energy'] =
          _detectSeasonality(energyScores, energyDates);
    }

    return {
      'mood': moodSeasonality,
      'factors': factorSeasonality,
    };
  } catch (e) {
    debugPrint('Error in seasonalityAnalysisProvider: $e');
    return {'detected': false, 'error': e.toString()};
  }
});

/// Provider for lag effect analysis between factors
///
/// Detects delayed effects between health factors, such as how changes in sleep
/// quality might affect mood after a delay of several days.
final lagEffectsProvider = FutureProvider.autoDispose
    .family<Map<String, dynamic>, LagAnalysisParams>((ref, params) async {
  try {
    final moodEntries = ref.watch(moodEntriesProvider);

    // Filter entries based on timeframe
    final filteredEntries = TimeframeSelector.filterByTimeframe(
      items: moodEntries,
      timeframe: params.timeframe,
      getDate: (entry) => entry.date,
    );

    if (filteredEntries.length < 10) {
      return {'detected': false, 'reason': 'Insufficient data'};
    }

    // Extract leading and following variable data
    List<double>? leadingData;
    List<double>? followingData;

    // Extract leading variable data
    if (params.leadingFactor == 'Mood') {
      leadingData =
          filteredEntries.map((e) => 4 - e.mood.index.toDouble()).toList();
    } else if (params.leadingFactor == 'Sleep') {
      final entriesWithSleep =
          filteredEntries.where((e) => e.sleepQuality != null).toList();
      if (entriesWithSleep.length >= 10) {
        leadingData = entriesWithSleep
            .map((e) => 4 - e.sleepQuality!.index.toDouble())
            .toList();
      }
    } else if (params.leadingFactor == 'Energy') {
      final entriesWithEnergy =
          filteredEntries.where((e) => e.energyLevel != null).toList();
      if (entriesWithEnergy.length >= 10) {
        leadingData = entriesWithEnergy
            .map((e) => 4 - e.energyLevel!.index.toDouble())
            .toList();
      }
    }

    // Extract following variable data
    if (params.followingFactor == 'Mood') {
      followingData =
          filteredEntries.map((e) => 4 - e.mood.index.toDouble()).toList();
    } else if (params.followingFactor == 'Sleep') {
      final entriesWithSleep =
          filteredEntries.where((e) => e.sleepQuality != null).toList();
      if (entriesWithSleep.length >= 10) {
        followingData = entriesWithSleep
            .map((e) => 4 - e.sleepQuality!.index.toDouble())
            .toList();
      }
    } else if (params.followingFactor == 'Energy') {
      final entriesWithEnergy =
          filteredEntries.where((e) => e.energyLevel != null).toList();
      if (entriesWithEnergy.length >= 10) {
        followingData = entriesWithEnergy
            .map((e) => 4 - e.energyLevel!.index.toDouble())
            .toList();
      }
    }

    if (leadingData == null || followingData == null) {
      return {'detected': false, 'reason': 'Missing required factor data'};
    }

    // Make sure both data series are the same length
    final minLength = math.min(leadingData.length, followingData.length);
    leadingData = leadingData.sublist(0, minLength);
    followingData = followingData.sublist(0, minLength);

    // Analyze lag effects with maximum lag of 7 days
    return _analyzeLagEffects(leadingData, followingData, 7);
  } catch (e) {
    debugPrint('Error in lagEffectsProvider: $e');
    return {'detected': false, 'error': e.toString()};
  }
});

/// Parameters for lag effect analysis between factors
class LagAnalysisParams {
  final String leadingFactor;
  final String followingFactor;
  final String timeframe;

  LagAnalysisParams({
    required this.leadingFactor,
    required this.followingFactor,
    required this.timeframe,
  });

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    return other is LagAnalysisParams &&
        other.leadingFactor == leadingFactor &&
        other.followingFactor == followingFactor &&
        other.timeframe == timeframe;
  }

  @override
  int get hashCode =>
      leadingFactor.hashCode ^ followingFactor.hashCode ^ timeframe.hashCode;
}

/// Provider for anomaly detection in health data
///
/// Identifies statistical outliers and pattern changes in mood and other health factors.
/// Returns a list of detected anomalies with relevant context.
final anomalyDetectionProvider = FutureProvider.autoDispose
    .family<List<stat_models.HealthAnomaly>, String>((ref, timeframe) async {
  try {
    final moodEntries = ref.watch(moodEntriesProvider);

    // Filter entries based on timeframe
    final filteredEntries = TimeframeSelector.filterByTimeframe(
      items: moodEntries,
      timeframe: timeframe,
      getDate: (entry) => entry.date,
    );

    if (filteredEntries.length < 10) {
      return [];
    }

    final anomalies = <stat_models.HealthAnomaly>[];

    // Detect mood anomalies
    final moodScores =
        filteredEntries.map((e) => 4 - e.mood.index.toDouble()).toList();
    final dates = filteredEntries.map((e) => e.date).toList();

    anomalies.addAll(_detectAnomalies(moodScores, dates, 'Mood'));

    // Detect sleep anomalies if available
    final entriesWithSleep =
        filteredEntries.where((e) => e.sleepQuality != null).toList();
    if (entriesWithSleep.length >= 10) {
      final sleepScores = entriesWithSleep
          .map((e) => 4 - e.sleepQuality!.index.toDouble())
          .toList();
      final sleepDates = entriesWithSleep.map((e) => e.date).toList();
      anomalies.addAll(_detectAnomalies(sleepScores, sleepDates, 'Sleep'));
    }

    // Detect energy anomalies if available
    final entriesWithEnergy =
        filteredEntries.where((e) => e.energyLevel != null).toList();
    if (entriesWithEnergy.length >= 10) {
      final energyScores = entriesWithEnergy
          .map((e) => 4 - e.energyLevel!.index.toDouble())
          .toList();
      final energyDates = entriesWithEnergy.map((e) => e.date).toList();
      anomalies.addAll(_detectAnomalies(energyScores, energyDates, 'Energy'));
    }

    // Sort anomalies by date
    anomalies.sort((a, b) => b.date.compareTo(a.date));

    return anomalies;
  } catch (e) {
    debugPrint('Error in anomalyDetectionProvider: $e');
    return [];
  }
});

/// Provider for comprehensive statistical analysis
///
/// Performs advanced statistical analysis on all available health data including mood,
/// medications, bloodwork, and other factors. Generates insights, detects patterns,
/// analyzes relationships, and makes predictions based on historical data.
final comprehensiveAnalysisProvider = FutureProvider.autoDispose
    .family<stat_models.ComprehensiveAnalysis, String>((ref, timeframe) async {
  try {
    debugPrint(
        'Running comprehensiveAnalysisProvider for timeframe: $timeframe');
    final analyticsService = ref.watch(healthAnalyticsServiceProvider);
    final moodEntries = ref.watch(moodEntriesProvider);
    final medications = ref.watch(medicationsProvider);
    final takenMedications = ref.watch(medicationTakenProvider);
    final bloodworkEntries = ref.watch(bloodworkRecordsProvider);

    // Filter entries based on timeframe
    final filteredMoodEntries = TimeframeSelector.filterByTimeframe(
      items: moodEntries,
      timeframe: timeframe,
      getDate: (entry) => entry.date,
    );

    final filteredBloodwork = TimeframeSelector.filterByTimeframe(
      items: bloodworkEntries,
      timeframe: timeframe,
      getDate: (entry) => entry.date,
    );

    // Initialize the health analytics service
    if (!analyticsService.isInitialized) {
      debugPrint('Initializing HealthAnalyticsService');
      final initResult = await analyticsService.initialize();
      if (!initResult) {
        debugPrint('Failed to initialize HealthAnalyticsService');
        return _createEmptyAnalysis(
            timeframe, 'Health analytics service initialization failed');
      }
    }

    // Build the comprehensive analysis
    return _generateComprehensiveAnalysis(
      analyticsService,
      filteredMoodEntries,
      medications,
      takenMedications,
      filteredBloodwork,
      timeframe,
    );
  } catch (e, stack) {
    debugPrint('Error in comprehensiveAnalysisProvider: $e');
    debugPrint(stack.toString());

    // Return an empty analysis with error message
    return _createEmptyAnalysis(timeframe, 'Error: $e');
  }
});

/// Create an empty analysis with a custom message
stat_models.ComprehensiveAnalysis _createEmptyAnalysis(
    String timeframe, String message) {
  return stat_models.ComprehensiveAnalysis(
    generalInsights: [],
    relationships: [],
    patterns: [],
    predictions: [],
    anomalies: [],
    factorRankings: [],
    weekdayAnalysis: null,
    streaks: [],
    hormoneAnalyses: [],
    medicationImpacts: [],
    moodTrend: stat_models.TrendAnalysis(
      type: stat_models.TrendType.none,
      strength: 0.0,
      description: message,
    ),
    analysisDate: DateTime.now(),
    timeframe: timeframe,
  );
}

/// Provider for factor-specific statistical analysis
///
/// Retrieves detailed analysis for a specific health factor from the comprehensive analysis.
/// Filters analysis results to only include information relevant to the requested factor.
final factorAnalysisProvider = FutureProvider.autoDispose
    .family<Map<String, dynamic>, FactorAnalysisParams>((ref, params) async {
  try {
    final comprehensive = await ref.watch(
      comprehensiveAnalysisProvider(params.timeframe).future,
    );

    return comprehensive.getFactorAnalysis(params.factorName);
  } catch (e) {
    debugPrint('Error in factorAnalysisProvider: $e');
    return <String, dynamic>{};
  }
});

/// Parameters for factor-specific analysis
class FactorAnalysisParams {
  final String factorName;
  final String timeframe;

  FactorAnalysisParams({
    required this.factorName,
    required this.timeframe,
  });

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    return other is FactorAnalysisParams &&
        other.factorName == factorName &&
        other.timeframe == timeframe;
  }

  @override
  int get hashCode => factorName.hashCode ^ timeframe.hashCode;
}

/// Generate basic insights from mood and related health data
///
/// Analyzes mood trends, correlations between mood and health factors,
/// and produces statistical insights based on the data.
List<stat_models.StatisticalInsight> _generateInsightsFromMoodData(
  HealthAnalyticsService service,
  List<MoodEntry> entries,
) {
  try {
    if (entries.isEmpty) {
      return [];
    }

    final insights = <stat_models.StatisticalInsight>[];

    // Extract mood scores (0-4 scale, with 4 being great)
    final moodScores = entries.map((e) {
      try {
        return 4 - e.mood.index.toDouble();
      } catch (error) {
        debugPrint('Error extracting mood: $error');
        return 2.0; // Default to neutral if there's an error
      }
    }).toList();

    // Basic statistics for mood using service
    try {
      final moodStats = service.calculateStatistics(moodScores);

      insights.add(
        stat_models.StatisticalInsight(
          title: 'Mood Overview',
          description: 'Statistical summary of your mood during this period',
          confidence: 0.9,
          statistics: [
            stat_models.VariableStatistics(
              variableName: 'Mood',
              average: moodStats.mean,
              median: moodStats.median,
              minimum: moodStats.min,
              maximum: moodStats.max,
              standardDeviation: moodStats.stdDev,
            ),
          ],
        ),
      );
    } catch (e) {
      debugPrint('Error calculating basic mood statistics: $e');
    }

    // Detect mood trends using service
    try {
      if (moodScores.length >= 3) {
        final moodTrend = service.detectTrend(moodScores);

        if (moodTrend.strength > 0.2) {
          stat_models.TrendDirection direction;
          String description;

          if (moodTrend.type == TrendType.increasing) {
            direction = stat_models.TrendDirection.increasing;
            description = 'Your mood appears to be improving over time';
          } else if (moodTrend.type == TrendType.decreasing) {
            direction = stat_models.TrendDirection.decreasing;
            description = 'Your mood appears to be declining over time';
          } else {
            direction = stat_models.TrendDirection.none;
            description = 'Your mood appears stable over time';
          }

          insights.add(
            stat_models.StatisticalInsight(
              title: 'Mood Trend Detected',
              description: description,
              confidence: moodTrend.strength,
              trends: [
                stat_models.HealthTrend(
                  variableName: 'Mood',
                  direction: direction,
                  strength: moodTrend.strength,
                  description: description,
                ),
              ],
            ),
          );
        }
      }
    } catch (e) {
      debugPrint('Error detecting mood trends: $e');
    }

    // Analyze health metrics if available
    final entriesWithSleep =
        entries.where((e) => e.sleepQuality != null).toList();
    final entriesWithEnergy =
        entries.where((e) => e.energyLevel != null).toList();
    final entriesWithAppetite =
        entries.where((e) => e.appetiteLevel != null).toList();
    final entriesWithFocus =
        entries.where((e) => e.focusLevel != null).toList();
    final entriesWithDysphoria =
        entries.where((e) => e.dysphoriaLevel != null).toList();

    // Create factor data map for analysis
    final factors = <String, List<double>>{};
    final correlationInsights = <stat_models.HealthCorrelation>[];

    if (entriesWithSleep.length >= 7) {
      factors['Sleep'] = entriesWithSleep
          .map((e) => 4 - e.sleepQuality!.index.toDouble())
          .toList();
    }

    if (entriesWithEnergy.length >= 7) {
      factors['Energy'] = entriesWithEnergy
          .map((e) => 4 - e.energyLevel!.index.toDouble())
          .toList();
    }

    if (entriesWithAppetite.length >= 7) {
      factors['Appetite'] = entriesWithAppetite
          .map((e) => 4 - e.appetiteLevel!.index.toDouble())
          .toList();
    }

    if (entriesWithFocus.length >= 7) {
      factors['Focus'] = entriesWithFocus
          .map((e) => 4 - e.focusLevel!.index.toDouble())
          .toList();
    }

    if (entriesWithDysphoria.length >= 7) {
      factors['Dysphoria'] = entriesWithDysphoria
          .map((e) => 4 - e.dysphoriaLevel!.index.toDouble())
          .toList();
    }

    // Perform correlation analysis if we have enough factors
    if (factors.length >= 2) {
      try {
        // Add mood as a factor
        factors['Mood'] = moodScores;

        // Align factor data to ensure consistent analysis
        final dates = entries.map((e) => e.date).toList();
        final alignedFactors = _advancedNormalizeFactorData(factors, dates);

        if (alignedFactors.isNotEmpty && alignedFactors.containsKey('Mood')) {
          final moodData = alignedFactors.remove('Mood')!;

          // Use service to find correlations between mood and other factors
          final factorCorrelations = service.findCorrelations(
            moodData,
            alignedFactors,
          );

          // Process the correlation results
          for (final correlation in factorCorrelations) {
            // Only include correlations that are at least weak
            if (correlation.correlation.abs() > 0.2) {
              String description;
              if (correlation.correlation > 0) {
                description =
                    'Better ${correlation.factorName} is associated with better mood';
              } else {
                description =
                    'Better ${correlation.factorName} is associated with worse mood';
              }

              correlationInsights.add(
                stat_models.HealthCorrelation(
                  factorName: correlation.factorName,
                  correlation: correlation.correlation,
                  description: description,
                ),
              );
            }
          }

          // Add correlation insight if we found any meaningful correlations
          if (correlationInsights.isNotEmpty) {
            insights.add(
              stat_models.StatisticalInsight(
                title: 'Factors Affecting Your Mood',
                description:
                    'Statistical correlations between mood and other factors',
                confidence: 0.7,
                correlations: correlationInsights,
              ),
            );
          }
        }
      } catch (e) {
        debugPrint('Error in correlation analysis: $e');
      }
    }

    // Detect seasonality in mood if enough data
    if (moodScores.length >= 14) {
      try {
        final dates = entries.map((e) => e.date).toList();
        final seasonality = _detectSeasonality(moodScores, dates);

        if (seasonality['detected'] == true) {
          final type = seasonality['type'];
          final strength = seasonality['strength'] as double;

          String description;
          if (type == 'weekly') {
            description = 'Your mood shows consistent weekly patterns';
          } else if (type == 'monthly') {
            description = 'Your mood shows consistent monthly patterns';
          } else {
            description = 'Your mood shows consistent cyclical patterns';
          }

          insights.add(
            stat_models.StatisticalInsight(
              title: 'Cyclical Mood Pattern',
              description: description,
              confidence:
                  strength * 0.9, // Scale confidence by pattern strength
              trends: [
                stat_models.HealthTrend(
                  variableName: 'Mood',
                  direction: stat_models
                      .TrendDirection.none, // Cyclical doesn't have direction
                  strength: strength,
                  description: description,
                ),
              ],
            ),
          );
        }
      } catch (e) {
        debugPrint('Error detecting seasonality: $e');
      }
    }

    // Detect anomalies if enough data
    if (moodScores.length >= 10) {
      try {
        final dates = entries.map((e) => e.date).toList();
        final anomalies = _detectAnomalies(moodScores, dates, 'Mood');

        if (anomalies.isNotEmpty) {
          // Only report the most significant anomaly in the insights
          anomalies.sort((a, b) => b.anomalyScore.compareTo(a.anomalyScore));
          final topAnomaly = anomalies.first;

          insights.add(
            stat_models.StatisticalInsight(
              title: 'Unusual Mood Pattern Detected',
              description:
                  'A significant deviation was detected in your mood data',
              confidence: 0.6 +
                  (topAnomaly.anomalyScore *
                      0.3), // Scale confidence by anomaly score
              trends: [
                stat_models.HealthTrend(
                  variableName: 'Mood',
                  direction: stat_models
                      .TrendDirection.none, // Anomaly doesn't have direction
                  strength: topAnomaly.anomalyScore,
                  description: topAnomaly.description,
                ),
              ],
            ),
          );
        }
      } catch (e) {
        debugPrint('Error detecting anomalies: $e');
      }
    }

    return insights;
  } catch (e, stack) {
    debugPrint('Error in _generateInsightsFromMoodData: $e');
    debugPrint(stack.toString());
    return [];
  }
}

/// Generate comprehensive analysis from all available health data
///
/// Performs in-depth statistical analysis on mood, medications, bloodwork, and other
/// health factors. Produces a complete analysis with insights, relationships, patterns,
/// predictions, and anomalies.
stat_models.ComprehensiveAnalysis _generateComprehensiveAnalysis(
  HealthAnalyticsService healthAnalyticsService,
  List<MoodEntry> moodEntries,
  List<Medication> medications,
  Set<String> takenMedications,
  List<Bloodwork> bloodworkEntries,
  String timeframe,
) {
  try {
    debugPrint('Generating comprehensive analysis for timeframe: $timeframe');

    if (moodEntries.isEmpty) {
      debugPrint('No mood entries, returning empty analysis');
      return _createEmptyAnalysis(timeframe, 'No data available for analysis');
    }

    // 1. Extract data for analysis
    // Mood scores (0-4 scale, with 4 being great)
    final moodScores = moodEntries.map((e) {
      try {
        return 4 - e.mood.index.toDouble();
      } catch (error) {
        debugPrint('Error extracting mood: $error');
        return 2.0; // Default to neutral value if error
      }
    }).toList();

    // Dates for time-based analysis
    final dates = moodEntries.map((e) => e.date).toList();

    // Create map of factor data for analysis
    final factorData = <String, List<double>>{
      'Mood': moodScores,
    };

    // Process health metrics
    final entriesWithSleep =
        moodEntries.where((e) => e.sleepQuality != null).toList();
    final entriesWithEnergy =
        moodEntries.where((e) => e.energyLevel != null).toList();
    final entriesWithAppetite =
        moodEntries.where((e) => e.appetiteLevel != null).toList();
    final entriesWithFocus =
        moodEntries.where((e) => e.focusLevel != null).toList();
    final entriesWithDysphoria =
        moodEntries.where((e) => e.dysphoriaLevel != null).toList();
    final entriesWithLibido =
        moodEntries.where((e) => e.libidoLevel != null).toList();
    final entriesWithExercise =
        moodEntries.where((e) => e.exerciseLevel != null).toList();

    // Add health metrics to factor data
    if (entriesWithSleep.length >= 5) {
      factorData['Sleep'] = entriesWithSleep
          .map((e) => 4 - e.sleepQuality!.index.toDouble())
          .toList();
    }

    if (entriesWithEnergy.length >= 5) {
      factorData['Energy'] = entriesWithEnergy
          .map((e) => 4 - e.energyLevel!.index.toDouble())
          .toList();
    }

    if (entriesWithAppetite.length >= 5) {
      factorData['Appetite'] = entriesWithAppetite
          .map((e) => 4 - e.appetiteLevel!.index.toDouble())
          .toList();
    }

    if (entriesWithFocus.length >= 5) {
      factorData['Focus'] = entriesWithFocus
          .map((e) => 4 - e.focusLevel!.index.toDouble())
          .toList();
    }

    if (entriesWithDysphoria.length >= 5) {
      factorData['Dysphoria'] = entriesWithDysphoria
          .map((e) => 4 - e.dysphoriaLevel!.index.toDouble())
          .toList();
    }

    if (entriesWithLibido.length >= 5) {
      factorData['Libido'] = entriesWithLibido
          .map((e) => 4 - e.libidoLevel!.index.toDouble())
          .toList();
    }

    if (entriesWithExercise.length >= 5) {
      factorData['Exercise'] = entriesWithExercise
          .map((e) => 4 - e.exerciseLevel!.index.toDouble())
          .toList();
    }

    // 2. Analyze emotions
    final emotionCounts = <String, int>{};

    // Count emotions and calculate average mood for each
    for (final entry in moodEntries) {
      for (final emotion in entry.emotions) {
        final emotionName = emotion.displayName;
        emotionCounts[emotionName] = (emotionCounts[emotionName] ?? 0) + 1;
      }
    }

    // For each emotion, calculate correlation with mood if we have enough data
    for (final emotionName in emotionCounts.keys) {
      if (emotionCounts[emotionName]! >= 5) {
        // Create binary array (1 if emotion present, 0 if not)
        final emotionPresence = moodEntries.map((entry) {
          return entry.emotions.any((e) => e.displayName == emotionName)
              ? 1.0
              : 0.0;
        }).toList();

        // Add to factor data for multi-factor analysis
        factorData['Emotion: $emotionName'] = emotionPresence;
      }
    }

    // 3. Add medication data
    // For each medication, add binary presence (1 if taken, 0 if not)
    for (final medication in medications) {
      if (takenMedications.contains(medication.id)) {
        // Create medication presence array aligned with mood entries
        final medPresence = moodEntries.map((entry) {
          // Check if medication was being taken on this date
          return (entry.date.isAfter(medication.startDate)) ? 1.0 : 0.0;
        }).toList();

        factorData['Medication: ${medication.name}'] = medPresence;
      }
    }

    // 4. Analyze bloodwork data if available
    final hormoneAnalyses = <stat_models.HormoneAnalysis>[];

    if (bloodworkEntries.isNotEmpty) {
      try {
        // Extract hormone readings
        final allHormoneReadings = <String, List<HormoneReading>>{};

        for (final entry in bloodworkEntries) {
          for (final reading in entry.hormoneReadings) {
            if (!allHormoneReadings.containsKey(reading.name)) {
              allHormoneReadings[reading.name] = [];
            }
            allHormoneReadings[reading.name]!.add(reading);
          }
        }

        // For each hormone, try to find correlation with mood
        for (final hormoneName in allHormoneReadings.keys) {
          final readings = allHormoneReadings[hormoneName]!;
          if (readings.length >= 3) {
            // Match bloodwork dates to nearest mood entries
            final hormoneValues = <double>[];
            final matchedMoodScores = <double>[];
            final readingDates = <DateTime>[];

            for (final reading in readings) {
              final bloodworkEntry = bloodworkEntries
                  .firstWhere((b) => b.hormoneReadings.contains(reading));
              final bloodworkDate = bloodworkEntry.date;
              readingDates.add(bloodworkDate);

              // Find mood entries around this bloodwork date (within 7 days)
              final nearbyEntries = moodEntries.where((entry) {
                final difference =
                    (entry.date.difference(bloodworkDate).inHours / 24).abs();
                return difference <= 7; // Within a week before or after
              }).toList();

              if (nearbyEntries.isNotEmpty) {
                // Use average mood of nearby entries
                final avgMood = nearbyEntries
                        .map((e) => 4 - e.mood.index.toDouble())
                        .reduce((a, b) => a + b) /
                    nearbyEntries.length;

                hormoneValues.add(reading.value);
                matchedMoodScores.add(avgMood);
              }
            }

            // Only analyze if we have enough matched data points
            if (hormoneValues.length >= 3 && matchedMoodScores.length >= 3) {
              // Find correlation between hormone levels and mood
              double correlation = 0;

              // Simple correlation calculation
              double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0, sumY2 = 0;
              int n = hormoneValues.length;

              for (int i = 0; i < n; i++) {
                sumX += hormoneValues[i];
                sumY += matchedMoodScores[i];
                sumXY += hormoneValues[i] * matchedMoodScores[i];
                sumX2 += hormoneValues[i] * hormoneValues[i];
                sumY2 += matchedMoodScores[i] * matchedMoodScores[i];
              }

              double denominator =
                  sqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY));
              if (denominator != 0) {
                correlation = (n * sumXY - sumX * sumY) / denominator;
              }

              // Find min and max normal range if available
              double? minValue = readings.first.minValue;
              double? maxValue = readings.first.maxValue;

              // Calculate average level
              final averageLevel = readings.isNotEmpty
                  ? (readings.map((r) => r.value).reduce((a, b) => a + b) /
                      readings.length)
                  : 0.0;

              // Add analysis to results
              final description =
                  _getHormoneDescription(hormoneName, correlation);

              // Create a proper HealthTrend object
              final trendDirection = correlation > 0.2
                  ? stat_models.TrendDirection.increasing
                  : correlation < -0.2
                      ? stat_models.TrendDirection.decreasing
                      : stat_models.TrendDirection.none;

              final healthTrend = stat_models.HealthTrend(
                  variableName: hormoneName,
                  direction: trendDirection,
                  strength: correlation.abs(),
                  description: description);

              // Create factor relationships
              final relationships = <stat_models.FactorRelationship>[];
              if (correlation.abs() > 0.2) {
                relationships.add(stat_models.FactorRelationship(
                  factorNames: [hormoneName, 'Mood'],
                  strength: correlation.abs(),
                  description: description,
                  type: stat_models.RelationshipType.correlation,
                ));
              }

              final hormoneAnalysis = stat_models.HormoneAnalysis(
                hormoneName: hormoneName,
                averageLevel: averageLevel,
                optimalMinLevel: minValue ?? 0.0,
                optimalMaxLevel: maxValue ?? 0.0,
                trend: healthTrend,
                relationships: relationships,
                description: description,
              );

              hormoneAnalyses.add(hormoneAnalysis);
            }
          }
        }
      } catch (e) {
        debugPrint('Error analyzing bloodwork data: $e');
      }
    }

    // 5. Generate basic statistics insights using the service
    final generalInsights = <stat_models.StatisticalInsight>[];

    try {
      // Basic mood statistics using the service
      final moodStats = healthAnalyticsService.calculateStatistics(moodScores);

      generalInsights.add(
        stat_models.StatisticalInsight(
          title: 'Mood Overview',
          description: 'Statistical summary of your mood during this period',
          confidence: 0.9,
          statistics: [
            stat_models.VariableStatistics(
              variableName: 'Mood',
              average: moodStats.mean,
              median: moodStats.median,
              minimum: moodStats.min,
              maximum: moodStats.max,
              standardDeviation: moodStats.stdDev,
            ),
          ],
        ),
      );
    } catch (e) {
      debugPrint('Error calculating basic mood statistics: $e');
    }

    // 6. Generate high-frequency data relationships
    final relationships = <stat_models.FactorRelationship>[];

    // Use advanced normalization for better data alignment
    final normalizedFactorData =
        _advancedNormalizeFactorData(factorData, dates);

    // Only proceed if we have enough aligned data
    if (normalizedFactorData.length >= 2) {
      try {
        // Look specifically for correlations with mood
        if (normalizedFactorData.containsKey('Mood')) {
          final moodData = normalizedFactorData['Mood']!;
          final factorNames = normalizedFactorData.keys.toList();

          // Check each factor's correlation with mood
          for (final factorName in factorNames) {
            if (factorName != 'Mood') {
              try {
                final factorData = normalizedFactorData[factorName]!;

                // Simple trend matching for correlation
                int sameDirection = 0;
                int totalComparisons = 0;

                // Limit to first 100 points to avoid stack issues
                final limit = moodData.length > 100 ? 100 : moodData.length - 1;

                for (int i = 1; i < limit; i++) {
                  // Check if both factors moved in the same direction
                  bool moodIncreased = moodData[i] > moodData[i - 1];
                  bool factorIncreased = factorData[i] > factorData[i - 1];

                  if (moodIncreased == factorIncreased) {
                    sameDirection++;
                  }
                  totalComparisons++;
                }

                // Calculate a simple correlation coefficient
                double simpleCorrelation = 0.0;
                if (totalComparisons > 0) {
                  simpleCorrelation =
                      (2 * sameDirection / totalComparisons) - 1.0;
                }

                // Use a lower threshold to catch more potential relationships
                if (simpleCorrelation.abs() >= 0.15) {
                  relationships.add(
                    stat_models.FactorRelationship(
                      factorNames: ['Mood', factorName],
                      strength: simpleCorrelation.abs(),
                      description: simpleCorrelation > 0
                          ? 'Positive correlation between Mood and $factorName'
                          : 'Negative correlation between Mood and $factorName',
                      type: stat_models.RelationshipType.correlation,
                    ),
                  );
                }

                // Check for lag effects
                if (moodData.length >= 10 && factorData.length >= 10) {
                  try {
                    // Analyze lag effects with max lag of 7 days
                    final lagResult =
                        _analyzeLagEffects(factorData, moodData, 7);

                    if (lagResult['detected'] == true) {
                      final lag = lagResult['lag'] as int;
                      final lagCorrelation = lagResult['correlation'] as double;

                      if (lagCorrelation.abs() >= 0.3) {
                        final description = lagCorrelation > 0
                            ? 'Changes in $factorName are followed by similar changes in Mood after $lag days'
                            : 'Changes in $factorName are followed by opposite changes in Mood after $lag days';

                        relationships.add(
                          stat_models.FactorRelationship(
                            factorNames: [factorName, 'Mood'],
                            strength: lagCorrelation.abs(),
                            description: description,
                            type: stat_models.RelationshipType
                                .causation, // Use causation for lag effects
                          ),
                        );
                      }
                    }
                  } catch (e) {
                    debugPrint(
                        'Error analyzing lag effects for $factorName: $e');
                  }
                }
              } catch (e) {
                debugPrint('Error calculating correlation for $factorName: $e');
              }
            }
          }
        }
      } catch (e) {
        debugPrint('Error finding multivariate correlations: $e');
      }
    }

    // 7. Generate health patterns using the service
    final patterns = <stat_models.HealthPattern>[];

    // Initialize with default values for statMoodTrend
    var statMoodTrend = stat_models.TrendAnalysis(
      type: stat_models.TrendType.none,
      strength: 0.0,
      description: 'No significant trend detected',
    );

    try {
      // Detect mood trends using the service
      if (moodScores.length >= 3) {
        final moodTrend = healthAnalyticsService.detectTrend(moodScores);
        statMoodTrend = stat_models.TrendAnalysis(
          type: moodTrend.type == TrendType.increasing
              ? stat_models.TrendType.increasing
              : moodTrend.type == TrendType.decreasing
                  ? stat_models.TrendType.decreasing
                  : stat_models.TrendType.none,
          strength: moodTrend.strength,
          description: moodTrend.description,
        );

        if (moodTrend.strength > 0.3) {
          stat_models.PatternType patternType;
          String description;
          List<String> relatedFactors = ['Mood'];

          if (moodTrend.type == TrendType.increasing) {
            patternType = stat_models.PatternType.trend;
            description = 'Your mood appears to be improving over time';
          } else if (moodTrend.type == TrendType.decreasing) {
            patternType = stat_models.PatternType.trend;
            description = 'Your mood appears to be declining over time';
          } else if (moodTrend.type == TrendType.cyclic) {
            patternType = stat_models.PatternType.cyclical;
            description = 'Your mood appears to follow a cyclical pattern';
          } else {
            patternType = stat_models.PatternType.unknown;
            description = 'Your mood shows variable patterns over time';
          }

          patterns.add(
            stat_models.HealthPattern(
              name: 'Mood Trend',
              description: description,
              significance: moodTrend.strength,
              relatedFactors: relatedFactors,
              type: patternType,
            ),
          );
        }
      }
    } catch (e) {
      debugPrint('Error detecting mood trend: $e');
    }

    // Detect seasonality patterns
    try {
      if (moodScores.length >= 14) {
        final seasonality = _detectSeasonality(moodScores, dates);

        if (seasonality['detected'] == true) {
          final type = seasonality['type'] as String;
          final strength = seasonality['strength'] as double;

          String name, description;
          if (type == 'weekly') {
            name = 'Weekly Mood Pattern';
            description =
                'Your mood shows consistent patterns across days of the week';
          } else {
            name = 'Monthly Mood Pattern';
            description =
                'Your mood shows consistent patterns across days of the month';
          }

          patterns.add(
            stat_models.HealthPattern(
              name: name,
              description: description,
              significance: strength,
              relatedFactors: ['Mood'],
              type: stat_models.PatternType.cyclical,
            ),
          );
        }
      }
    } catch (e) {
      debugPrint('Error detecting seasonality patterns: $e');
    }

    // Initialize empty collections for the remaining analysis parts
    final predictions = <stat_models.HealthPrediction>[];
    final anomalies = <stat_models.HealthAnomaly>[];
    final factorRankings = <stat_models.FactorImpactRanking>[];
    stat_models.WeekdayAnalysis? weekdayAnalysis;
    final streaks = <stat_models.HealthStreak>[];
    final medicationImpacts = <stat_models.MedicationImpact>[];

    // 8. Calculate medication impacts
    for (final medication in medications) {
      if (takenMedications.contains(medication.id)) {
        // Split mood entries into before and after
        final beforeEntries = moodEntries
            .where((e) => e.date.isBefore(medication.startDate))
            .toList();

        final afterEntries = moodEntries
            .where((e) => !e.date.isBefore(medication.startDate))
            .toList();

        if (beforeEntries.length >= 7 && afterEntries.length >= 7) {
          // Extract mood scores
          final beforeMood =
              beforeEntries.map((e) => 4 - e.mood.index.toDouble()).toList();

          final afterMood =
              afterEntries.map((e) => 4 - e.mood.index.toDouble()).toList();

          try {
            final medImpact = healthAnalyticsService.analyzeMedicationImpact(
              medication.name,
              'Mood',
              beforeMood,
              afterMood,
            );

            // Calculate before/after changes
            final beforeAvg = medImpact.beforeMean;
            final afterAvg = medImpact.afterMean;
            final changeMagnitude = medImpact.changeMagnitude;
            final effectStrength = medImpact.overallImpact;

            // Create a map for before/after changes
            final beforeAfterChanges = <String, double>{
              'Mood_before': beforeAvg,
              'Mood_after': afterAvg,
              'Mood_change': changeMagnitude,
            };

            final medicationImpact = stat_models.MedicationImpact(
              medicationName: medication.name,
              description: medImpact.description,
              daysToEffect: medImpact.daysToEffect,
              effectStrength: effectStrength,
              beforeAfterChanges: beforeAfterChanges,
            );

            medicationImpacts.add(medicationImpact);
          } catch (e) {
            debugPrint('Error analyzing medication impact: $e');
          }
        }
      }
    }

    // 9. Factor impact ranking
    if (normalizedFactorData.length >= 2 &&
        normalizedFactorData.containsKey('Mood')) {
      try {
        // Make a copy without the target
        final factorsWithoutMood =
            Map<String, List<double>>.from(normalizedFactorData);
        final moodData = factorsWithoutMood.remove('Mood')!;

        if (factorsWithoutMood.isNotEmpty) {
          final impacts = healthAnalyticsService.rankFactorImpacts(
            factorsWithoutMood,
            moodData,
          );

          for (final impact in impacts) {
            // Create a RankedFactor object
            final rankedFactor = stat_models.RankedFactor(
                name: impact.factorName,
                impactScore: impact.impactScore,
                description: impact.mechanism);

            factorRankings.add(
              stat_models.FactorImpactRanking(
                targetFactor: 'Mood',
                impactingFactors: [rankedFactor],
                description: impact.mechanism,
              ),
            );
          }
        }
      } catch (e) {
        debugPrint('Error ranking factor impacts: $e');
      }
    }

    // 10. Calculate weekday analysis
    if (moodEntries.length >= 14) {
      try {
        final weekdayAverages = List<double>.filled(7, 0.0);
        final weekdayCounts = List<int>.filled(7, 0);

        for (final entry in moodEntries) {
          final weekday = entry.date.weekday % 7; // 0-6, 0 is Sunday
          weekdayAverages[weekday] += 4 - entry.mood.index.toDouble();
          weekdayCounts[weekday]++;
        }

        for (int i = 0; i < 7; i++) {
          if (weekdayCounts[i] > 0) {
            weekdayAverages[i] /= weekdayCounts[i];
          }
        }

        // Find best and worst days
        final bestDaysMap = <String, int>{};
        final worstDaysMap = <String, int>{};

        int bestDayIndex = 0;
        int worstDayIndex = 0;
        double bestScore = weekdayAverages[0];
        double worstScore = weekdayAverages[0];

        for (int i = 1; i < 7; i++) {
          if (weekdayCounts[i] > 0) {
            if (weekdayAverages[i] > bestScore) {
              bestScore = weekdayAverages[i];
              bestDayIndex = i;
            }
            if (weekdayAverages[i] < worstScore) {
              worstScore = weekdayAverages[i];
              worstDayIndex = i;
            }
          }
        }

        // Add to the maps - use day names as keys and indices as values
        bestDaysMap[_dayOfWeekName(bestDayIndex)] = bestDayIndex;
        worstDaysMap[_dayOfWeekName(worstDayIndex)] = worstDayIndex;

        // Create factor-by-weekday data structure
        final factorsByWeekday = <String, List<double>>{};
        for (int i = 0; i < 7; i++) {
          if (weekdayCounts[i] > 0) {
            factorsByWeekday['Mood-${i + 1}'] = [weekdayAverages[i]];
          }
        }

        weekdayAnalysis = stat_models.WeekdayAnalysis(
          bestDays: bestDaysMap,
          worstDays: worstDaysMap,
          factorsByWeekday: factorsByWeekday,
          description:
              'Your mood tends to be highest on ${_dayOfWeekName(bestDayIndex)} and lowest on ${_dayOfWeekName(worstDayIndex)}',
        );
      } catch (e) {
        debugPrint('Error calculating weekday analysis: $e');
      }
    }

    // 11. Detect mood anomalies
    try {
      anomalies.addAll(_detectAnomalies(moodScores, dates, 'Mood'));
    } catch (e) {
      debugPrint('Error detecting mood anomalies: $e');
    }

    // 12. Generate simple predictions
    try {
      if (moodScores.length >= 14) {
        // Extract last 14 days
        final recentMood = moodScores.length > 14
            ? moodScores.sublist(moodScores.length - 14)
            : moodScores;

        final recentDates =
            dates.length > 14 ? dates.sublist(dates.length - 14) : dates;

        // Detect trend
        final trendResult = healthAnalyticsService.detectTrend(recentMood);

        // Only make prediction if we have a clear trend
        if (trendResult.strength >= 0.4) {
          final lastDate = recentDates.last;
          final lastScore = recentMood.last;

          // Calculate average daily change
          double totalChange = 0;
          int changeCount = 0;

          for (int i = 1; i < recentMood.length; i++) {
            totalChange += recentMood[i] - recentMood[i - 1];
            changeCount++;
          }

          final avgDailyChange =
              changeCount > 0 ? totalChange / changeCount : 0;

          // Predict next 7 days
          for (int i = 1; i <= 7; i++) {
            final predictionDate = lastDate.add(Duration(days: i));

            // Apply diminishing trend (trend tends to revert to mean over time)
            final diminishFactor = math.max(0.0, 1.0 - (i * 0.1));
            final predictedChange = avgDailyChange * diminishFactor;
            final predictedValue = lastScore + (predictedChange * i);

            // Cap predicted value to valid range (0-4)
            final clampedValue = math.min(4.0, math.max(0.0, predictedValue));

            // Scale confidence based on days ahead and trend strength
            final daysAheadFactor = math.max(0.0, 1.0 - (i * 0.1));
            final confidence = trendResult.strength * daysAheadFactor;

            // Create prediction
            String description;
            if (predictedValue > lastScore) {
              description = 'Your mood is predicted to improve';
            } else if (predictedValue < lastScore) {
              description = 'Your mood may decrease slightly';
            } else {
              description = 'Your mood is expected to remain stable';
            }

            predictions.add(
              stat_models.HealthPrediction(
                factorName: 'Mood',
                predictedValue: clampedValue,
                confidence: confidence,
                description: description,
                targetDate: predictionDate,
              ),
            );
          }
        }
      }
    } catch (e) {
      debugPrint('Error generating predictions: $e');
    }

    return stat_models.ComprehensiveAnalysis(
      generalInsights: generalInsights,
      relationships: relationships,
      patterns: patterns,
      predictions: predictions,
      anomalies: anomalies,
      factorRankings: factorRankings,
      weekdayAnalysis: weekdayAnalysis,
      streaks: streaks,
      hormoneAnalyses: hormoneAnalyses,
      medicationImpacts: medicationImpacts,
      moodTrend: statMoodTrend,
      analysisDate: DateTime.now(),
      timeframe: timeframe,
    );
  } catch (e, stack) {
    debugPrint('Error in _generateComprehensiveAnalysis: $e');
    debugPrint(stack.toString());
    return _createEmptyAnalysis(timeframe, 'Error: $e');
  }
}

/// Get the name of a day of the week from its index
String _dayOfWeekName(int dayIndex) {
  const days = [
    'Sunday',
    'Monday',
    'Tuesday',
    'Wednesday',
    'Thursday',
    'Friday',
    'Saturday'
  ];
  return days[dayIndex];
}

/// Generate a description of hormone effects based on correlation with mood
String _getHormoneDescription(String hormoneName, double correlation) {
  final strength = correlation.abs() > 0.6
      ? 'strong'
      : correlation.abs() > 0.3
          ? 'moderate'
          : 'weak';

  final direction = correlation > 0 ? 'positive' : 'negative';

  if (correlation.abs() < 0.2) {
    return '$hormoneName levels do not appear to correlate significantly with your mood';
  }

  return '$hormoneName shows a $strength $direction correlation with your mood (r=${correlation.toStringAsFixed(2)})';
}

/// Helper function to calculate a simple square root
double sqrt(double value) {
  return value <= 0 ? 0 : math.sqrt(value);
}
