"""
Habit Tracker Helper Functions

This module provides core helper functions for calculating streaks,
checking relevance, and computing stats for habits.
"""

from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional


def calculate_streak(habit: Dict[str, Any]) -> int:
    """
    Calculate the current streak for a habit based on its frequency type.
    Skips maintain the streak (don't break it) but don't count toward the total.
    
    Args:
        habit: Dict containing habit data with frequency, completions, etc.
    
    Returns:
        int: Current streak count (days, weeks, or months depending on frequency)
    """
    frequency_type = habit.get("frequency", {}).get("type", "daily")
    completions = habit.get("completions", [])
    
    if not completions:
        return 0
    
    # Sort completions by date (newest first)
    sorted_completions = sorted(
        [c for c in completions if c.get("date")],
        key=lambda x: x["date"],
        reverse=True
    )
    
    if not sorted_completions:
        return 0
    
    if frequency_type == "daily":
        return _calculate_daily_streak(sorted_completions)
    elif frequency_type == "specific_days":
        return _calculate_specific_days_streak(habit, sorted_completions)
    elif frequency_type == "x_per_week":
        return _calculate_x_per_week_streak(habit, sorted_completions)
    elif frequency_type == "weekly":
        return _calculate_weekly_streak(sorted_completions)
    elif frequency_type == "monthly":
        return _calculate_monthly_streak(sorted_completions)
    elif frequency_type == "custom":
        return _calculate_custom_streak(habit, sorted_completions)
    
    return 0


def _calculate_daily_streak(completions: List[Dict[str, Any]]) -> int:
    """
    Calculate streak for daily habits (consecutive days).
    Skips maintain the streak (don't break it) but don't count toward the total.
    """
    streak = 0
    today = datetime.now().date()
    expected_date = today
    
    for completion in completions:
        completion_date = datetime.fromisoformat(completion["date"]).date()
        completion_type = completion.get("type", "check")
        
        if completion_date == expected_date:
            # Only count 'check' completions toward streak total
            # 'skip' completions maintain the streak but don't extend it
            if completion_type == "check":
                streak += 1
            expected_date = completion_date - timedelta(days=1)
        elif completion_date < expected_date:
            # Gap found, streak breaks
            break
    
    return streak


def _calculate_specific_days_streak(habit: Dict[str, Any], completions: List[Dict[str, Any]]) -> int:
    """Calculate streak for specific days habits (only count relevant days)."""
    relevant_days = set(habit.get("frequency", {}).get("days", []))
    if not relevant_days:
        return 0
    
    streak = 0
    today = datetime.now().date()
    current_date = today
    
    # Find the most recent relevant day
    while current_date.weekday() not in relevant_days:
        current_date -= timedelta(days=1)
    
    for completion in completions:
        completion_date = datetime.fromisoformat(completion["date"]).date()
        
        if completion_date == current_date:
            streak += 1
            # Move to previous relevant day
            current_date -= timedelta(days=1)
            while current_date.weekday() not in relevant_days:
                current_date -= timedelta(days=1)
        elif completion_date < current_date:
            # Check if we missed a relevant day
            temp_date = current_date
            found_gap = False
            while temp_date > completion_date:
                if temp_date.weekday() in relevant_days:
                    found_gap = True
                    break
                temp_date -= timedelta(days=1)
            if found_gap:
                break
    
    return streak


def _calculate_x_per_week_streak(habit: Dict[str, Any], completions: List[Dict[str, Any]]) -> int:
    """Calculate streak for x_per_week habits (consecutive days with check-ins).

    For x_per_week habits, streak counts consecutive DAYS with check-ins,
    not consecutive weeks meeting the target. The weekly target (e.g., 4/week)
    is a goal, but streak measures the chain of check-in days.
    """
    # Use the same logic as daily habits - count consecutive check-in days
    return _calculate_daily_streak(completions)


def _calculate_weekly_streak(completions: List[Dict[str, Any]]) -> int:
    """Calculate streak for weekly habits (consecutive days with check-ins).

    For weekly habits, streak counts consecutive DAYS with check-ins,
    just like daily habits. The weekly frequency just means you should
    check in at least once per week.
    """
    return _calculate_daily_streak(completions)


def _calculate_monthly_streak(completions: List[Dict[str, Any]]) -> int:
    """Calculate streak for monthly habits (consecutive days with check-ins).

    For monthly habits, streak counts consecutive DAYS with check-ins,
    just like daily habits. The monthly frequency just means you should
    check in at least once per month.
    """
    return _calculate_daily_streak(completions)


def _calculate_custom_streak(habit: Dict[str, Any], completions: List[Dict[str, Any]]) -> int:
    """Calculate streak for custom interval habits (every X days)."""
    interval = habit.get("frequency", {}).get("interval", 1)
    if interval <= 0:
        return 0
    
    streak = 0
    expected_date = datetime.now().date()
    
    for completion in completions:
        completion_date = datetime.fromisoformat(completion["date"]).date()
        
        # Allow completion within the interval window
        days_diff = (expected_date - completion_date).days
        if 0 <= days_diff <= interval - 1:
            streak += 1
            expected_date = completion_date - timedelta(days=interval)
        else:
            break
    
    return streak


def should_check_today(habit: Dict[str, Any]) -> bool:
    """
    Check if a habit is relevant for today based on its frequency type.
    
    Args:
        habit: Dict containing habit data with frequency settings
    
    Returns:
        bool: True if the habit should be checked today
    """
    frequency_type = habit.get("frequency", {}).get("type", "daily")
    today = datetime.now().date()
    weekday = today.weekday()  # 0=Monday, 6=Sunday
    
    if frequency_type == "daily":
        return True
    
    elif frequency_type == "specific_days":
        relevant_days = set(habit.get("frequency", {}).get("days", []))
        return weekday in relevant_days
    
    elif frequency_type == "x_per_week":
        # Always relevant for x_per_week (can check any day)
        return True
    
    elif frequency_type == "weekly":
        # Always relevant (can check any day of the week)
        return True
    
    elif frequency_type == "monthly":
        # Always relevant (can check any day of the month)
        return True
    
    elif frequency_type == "custom":
        # Check if enough days have passed since last completion
        completions = habit.get("completions", [])
        if not completions:
            return True
        
        interval = habit.get("frequency", {}).get("interval", 1)
        last_completion = max(completions, key=lambda x: x.get("date", ""))
        last_date = datetime.fromisoformat(last_completion["date"]).date()
        days_since = (today - last_date).days
        
        return days_since >= interval
    
    return False


def get_completion_rate(habit: Dict[str, Any], days: int = 30) -> float:
    """
    Calculate the completion rate as a percentage over the last N days.
    
    Args:
        habit: Dict containing habit data
        days: Number of days to look back (default 30)
    
    Returns:
        float: Completion rate as percentage (0-100)
    """
    frequency_type = habit.get("frequency", {}).get("type", "daily")
    completions = habit.get("completions", [])
    
    today = datetime.now().date()
    start_date = today - timedelta(days=days - 1)
    
    # Count relevant days and checked days
    relevant_days = 0
    checked_dates = set()
    
    for completion in completions:
        completion_date = datetime.fromisoformat(completion["date"]).date()
        if start_date <= completion_date <= today:
            checked_dates.add(completion_date)
    
    # Calculate relevant days based on frequency type
    if frequency_type == "daily":
        relevant_days = days
    
    elif frequency_type == "specific_days":
        relevant_day_set = set(habit.get("frequency", {}).get("days", []))
        current = start_date
        while current <= today:
            if current.weekday() in relevant_day_set:
                relevant_days += 1
            current += timedelta(days=1)
    
    elif frequency_type == "x_per_week":
        target_per_week = habit.get("frequency", {}).get("count", 1)
        num_weeks = days // 7
        relevant_days = num_weeks * target_per_week
    
    elif frequency_type == "weekly":
        num_weeks = days // 7
        relevant_days = num_weeks
    
    elif frequency_type == "monthly":
        num_months = days // 30
        relevant_days = num_months
    
    elif frequency_type == "custom":
        interval = habit.get("frequency", {}).get("interval", 1)
        relevant_days = days // interval if interval > 0 else 0
    
    if relevant_days == 0:
        return 0.0
    
    checked_days = len(checked_dates)
    return (checked_days / relevant_days) * 100


def get_weekly_summary(habit: Dict[str, Any]) -> Dict[str, str]:
    """
    Get a summary of the current week showing status for each day.

    Args:
        habit: Dict containing habit data

    Returns:
        Dict mapping day names to status: "checked", "skipped", "missed", or "upcoming"
    """
    frequency_type = habit.get("frequency", {}).get("type", "daily")
    completions = habit.get("completions", [])

    today = datetime.now().date()

    # Start of current week (Monday)
    start_of_week = today - timedelta(days=today.weekday())

    # Create completion map
    completion_map = {}
    for completion in completions:
        completion_date = datetime.fromisoformat(completion["date"]).date()
        if completion_date >= start_of_week:
            completion_type = completion.get("type", "check")
            completion_map[completion_date] = completion_type

    # Build summary for each day of the week
    summary = {}
    day_names = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]

    for i, day_name in enumerate(day_names):
        day_date = start_of_week + timedelta(days=i)

        if day_date > today:
            summary[day_name] = "upcoming"
        elif day_date in completion_map:
            if completion_map[day_date] == "skip":
                summary[day_name] = "skipped"
            else:
                summary[day_name] = "checked"
        else:
            # Check if this day was relevant
            if frequency_type == "specific_days":
                relevant_days = set(habit.get("frequency", {}).get("days", []))
                if day_date.weekday() not in relevant_days:
                    summary[day_name] = "not_relevant"
                else:
                    summary[day_name] = "missed"
            else:
                summary[day_name] = "missed"

    return summary


def check_and_award_weekly_lives(habit: Dict[str, Any]) -> tuple[int, bool]:
    """
    Check if habit qualifies for weekly lives recovery and award +1 life if eligible.

    Awards +1 life if:
    - At least one check-in in the previous week (Monday-Sunday)
    - Not already awarded this week

    Args:
        habit: Dict containing habit data with completions and lastLivesAward

    Returns:
        tuple[int, bool]: (new_lives_count, was_awarded)
    """
    completions = habit.get("completions", [])
    current_lives = habit.get("lives", 3)

    today = datetime.now().date()

    # Calculate current week start (Monday 00:00)
    current_week_start = today - timedelta(days=today.weekday())

    # Check if already awarded this week
    last_lives_award = habit.get("lastLivesAward")
    if last_lives_award:
        last_award_date = datetime.fromisoformat(last_lives_award).date()
        if last_award_date >= current_week_start:
            # Already awarded this week
            return (current_lives, False)

    # Calculate previous week boundaries
    previous_week_start = current_week_start - timedelta(days=7)
    previous_week_end = current_week_start - timedelta(days=1)

    # Count check-ins in previous week
    checkins_in_previous_week = 0
    for completion in completions:
        completion_date = datetime.fromisoformat(completion["date"]).date()
        completion_type = completion.get("type", "check")

        if previous_week_start <= completion_date <= previous_week_end:
            if completion_type == "check":
                checkins_in_previous_week += 1

    # Award life if at least 1 check-in found
    if checkins_in_previous_week >= 1:
        new_lives = current_lives + 1
        return (new_lives, True)

    return (current_lives, False)
