import 'package:flutter/material.dart';
import '../models/user_profile.dart';
import '../models/cycle_entry.dart';

class CycleInfo {
  final CyclePhase phase;
  final int dayOfCycle;
  final int daysUntilPeriod;
  final bool isPeriodExpected;

  CycleInfo({
    required this.phase,
    required this.dayOfCycle,
    required this.daysUntilPeriod,
    required this.isPeriodExpected,
  });

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is CycleInfo &&
          runtimeType == other.runtimeType &&
          phase == other.phase &&
          dayOfCycle == other.dayOfCycle &&
          daysUntilPeriod == other.daysUntilPeriod &&
          isPeriodExpected == other.isPeriodExpected;

  @override
  int get hashCode =>
      phase.hashCode ^
      dayOfCycle.hashCode ^
      daysUntilPeriod.hashCode ^
      isPeriodExpected.hashCode;
}

class CycleService {
  /// Calculates the current cycle information based on user profile
  /// Calculates the current cycle information based on user profile and cycle entries
  static CycleInfo calculateCycleInfo(
      UserProfile? user, List<CycleEntry> entries) {
    if (user == null) {
      return CycleInfo(
        phase: CyclePhase.follicular,
        dayOfCycle: 1,
        daysUntilPeriod: 28,
        isPeriodExpected: false,
      );
    }

    DateTime? lastPeriodStart = user.lastPeriodStartDate;

    // Find the most recent period start from entries if available and more recent
    // We look for a sequence of period days and take the first one
    if (entries.isNotEmpty) {
      final sortedEntries = List<CycleEntry>.from(entries)
        ..sort((a, b) => b.date.compareTo(a.date));

      for (var entry in sortedEntries) {
        if (entry.isPeriodDay) {
          // Check if this is a "start" of a period (previous day was not period or no entry)
          // Simplified logic: Just take the most recent period day found and assume it's part of the current/last period
          // A better approach for "Day 1" is to find the First day of the contiguous block.

          // However, for basic calculation, if we find a period day at Date X,
          // and today is Date Y.
          // If X is very recent, we are in the period.

          // Correct logic: Identify the START DATE of the last period group.
          // 1. Find the latest period entry.
          // 2. Look backwards from there as long as there are consecutive period days.

          // Check if we have a period day "tomorrow" relative to this entry? No, we are iterating backwards (descending).
          // So if we found a period day, we need to check if the NEXT entry (which is earlier in time) is also a period day.
          // If so, that earlier day is the better candidate for "Start".

          // Let's iterate linearly.
          // Since we sorted DESC, `entry` is the LATEST period day.
          // We need to see if there are consecutive period days before it.

          // But wait, the user might have logged Day 1, Day 2, Day 3.
          // `entry` will be Day 3.
          // We want Day 1.

          // Let's try a different approach:
          // Get all period days sorted DESC.
          final periodDays = sortedEntries.where((e) => e.isPeriodDay).toList();

          if (periodDays.isNotEmpty) {
            // Take the latest block
            DateTime latestParams = periodDays.first.date;

            // Now find the "start" of this block
            // We iterate backwards from the *latest* date found

            DateTime currentSearch = latestParams;
            DateTime startOfBlock = latestParams;

            // Check if we have an entry for the day before
            bool foundPrevious = true;
            while (foundPrevious) {
              final dayBefore = currentSearch.subtract(const Duration(days: 1));
              final hasDayBefore =
                  periodDays.any((e) => DateUtils.isSameDay(e.date, dayBefore));
              if (hasDayBefore) {
                currentSearch = dayBefore;
                startOfBlock = dayBefore;
              } else {
                foundPrevious = false;
              }
            }

            // If this calculated start is more recent than the user profile one, use it
            if (lastPeriodStart == null ||
                startOfBlock.isAfter(lastPeriodStart)) {
              lastPeriodStart = startOfBlock;
            }
          }
          break; // We only care about the most recent period block
        }
      }
    }

    if (lastPeriodStart == null) {
      return CycleInfo(
        phase: CyclePhase.follicular,
        dayOfCycle: 1,
        daysUntilPeriod: user.averageCycleLength,
        isPeriodExpected: false,
      );
    }

    // Check if the calculated last period is in the future (invalid state validation)
    if (lastPeriodStart.isAfter(DateTime.now())) {
      // Fallback to today if data is weird, or just use it (maybe user logged future?)
      // Let's stick to standard logic:
    }

    final cycleLength = user.averageCycleLength;
    final now = DateTime.now();

    // Normalize dates to midnight for accurate day counting
    final startOfToday = DateTime(now.year, now.month, now.day);
    final startOfCycle = DateTime(
        lastPeriodStart.year, lastPeriodStart.month, lastPeriodStart.day);

    final daysSinceLastPeriod =
        startOfToday.difference(startOfCycle).inDays + 1;

    // If negative (future date), handle gracefully
    if (daysSinceLastPeriod < 1) {
      return CycleInfo(
        phase: CyclePhase.follicular,
        dayOfCycle: 1,
        daysUntilPeriod: cycleLength,
        isPeriodExpected: false,
      );
    }

    // Handle cases where last period was long ago (more than one cycle)
    final dayOfCycle = ((daysSinceLastPeriod - 1) % cycleLength) + 1;
    final daysUntilPeriod = cycleLength - dayOfCycle;

    CyclePhase phase;
    if (dayOfCycle <= user.averagePeriodLength) {
      // Use variable period length
      phase = CyclePhase.menstrual;
    } else if (dayOfCycle <= 13) {
      phase = CyclePhase.follicular;
    } else if (dayOfCycle <= 16) {
      phase = CyclePhase.ovulation;
    } else {
      phase = CyclePhase.luteal;
    }

    return CycleInfo(
      phase: phase,
      dayOfCycle: dayOfCycle,
      daysUntilPeriod: daysUntilPeriod,
      isPeriodExpected: daysUntilPeriod <= 0 || dayOfCycle <= 5,
    );
  }

  /// Calculates the cycle phase for a specific date (past or future)
  static CyclePhase? getPhaseForDate(DateTime date, UserProfile? user) {
    if (user == null || user.lastPeriodStartDate == null) return null;

    final lastPeriodStart = user.lastPeriodStartDate!;

    // Normalize dates
    final checkDate = DateTime(date.year, date.month, date.day);
    final startCycle = DateTime(
        lastPeriodStart.year, lastPeriodStart.month, lastPeriodStart.day);

    final daysDifference = checkDate.difference(startCycle).inDays;

    // If date is before the last known period, we can't reliably predict using this simple logic
    // (though in reality we could project backwards, but let's stick to forward/current)
    if (daysDifference < 0) return null;

    final cycleLength = user.averageCycleLength;
    final dayOfCycle = (daysDifference % cycleLength) + 1;

    if (dayOfCycle <= user.averagePeriodLength) return CyclePhase.menstrual;
    if (dayOfCycle <= 13) return CyclePhase.follicular;
    if (dayOfCycle <= 16) return CyclePhase.ovulation;
    return CyclePhase.luteal;
  }

  /// Predicts period days for the next [months] months
  static List<DateTime> predictNextPeriodDays(UserProfile? user,
      {int months = 12}) {
    if (user == null || user.lastPeriodStartDate == null) return [];

    final predictedDays = <DateTime>[];
    final lastPeriodStart = user.lastPeriodStartDate!;
    final cycleLength = user.averageCycleLength;
    final periodLength = user.averagePeriodLength;

    // Start predicting from the NEXT cycle if the current one is finished,
    // or just project out from the last start date.
    // We want to list all future period days.

    DateTime currentCycleStart = lastPeriodStart;

    // Project forward for roughly 'months' months
    // A safe upper bound for loop is months * 30 days
    final limitDate = DateTime.now().add(Duration(days: months * 30));

    while (currentCycleStart.isBefore(limitDate)) {
      // Add period days for this cycle
      for (int i = 0; i < periodLength; i++) {
        final periodDay = currentCycleStart.add(Duration(days: i));
        if (periodDay.isAfter(DateTime.now())) {
          predictedDays.add(periodDay);
        }
      }

      // Move to next cycle
      currentCycleStart = currentCycleStart.add(Duration(days: cycleLength));
    }

    return predictedDays;
  }

  /// Format cycle day for display
  static String getDayOfCycleDisplay(int day) => 'Day $day';

  /// Get phase description
  static String getPhaseDescription(CyclePhase phase) {
    switch (phase) {
      case CyclePhase.menstrual:
        return 'Your body is resting and clearing. Be gentle with yourself.';
      case CyclePhase.follicular:
        return 'Energy and optimism are rising. A great time for new projects.';
      case CyclePhase.ovulation:
        return 'You are at your peak energy and fertility.';
      case CyclePhase.luteal:
        return 'Progesterone is rising. You may feel more introverted or sensitive.';
    }
  }
}
