"""
Tests for habits_helpers.py

Tests cover all helper functions for habit tracking including:
- calculate_streak for all 6 frequency types
- should_check_today for all frequency types
- get_completion_rate
- get_weekly_summary
"""

import sys
import os
from datetime import datetime, timedelta

# Add parent directory to path to import habits_helpers
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from habits_helpers import (
    calculate_streak,
    should_check_today,
    get_completion_rate,
    get_weekly_summary,
    check_and_award_weekly_lives
)


def test_calculate_streak_daily_consecutive():
    """Test daily streak with consecutive days."""
    today = datetime.now().date()
    habit = {
        "frequency": {"type": "daily"},
        "completions": [
            {"date": today.isoformat()},
            {"date": (today - timedelta(days=1)).isoformat()},
            {"date": (today - timedelta(days=2)).isoformat()},
        ]
    }
    assert calculate_streak(habit) == 3


def test_calculate_streak_daily_with_gap():
    """Test daily streak breaks on gap."""
    today = datetime.now().date()
    habit = {
        "frequency": {"type": "daily"},
        "completions": [
            {"date": today.isoformat()},
            {"date": (today - timedelta(days=1)).isoformat()},
            # Gap here (day 2 missing)
            {"date": (today - timedelta(days=3)).isoformat()},
        ]
    }
    assert calculate_streak(habit) == 2


def test_calculate_streak_daily_empty():
    """Test daily streak with no completions."""
    habit = {
        "frequency": {"type": "daily"},
        "completions": []
    }
    assert calculate_streak(habit) == 0


def test_calculate_streak_specific_days():
    """Test specific_days streak (Mon, Wed, Fri)."""
    today = datetime.now().date()
    
    # Find the most recent Monday
    days_since_monday = today.weekday()
    last_monday = today - timedelta(days=days_since_monday)
    
    habit = {
        "frequency": {
            "type": "specific_days",
            "days": [0, 2, 4]  # Mon, Wed, Fri (0=Mon in Python weekday)
        },
        "completions": [
            {"date": last_monday.isoformat()},  # Mon
            {"date": (last_monday - timedelta(days=2)).isoformat()},  # Fri previous week
            {"date": (last_monday - timedelta(days=4)).isoformat()},  # Wed previous week
        ]
    }
    
    # Should count 3 consecutive relevant days
    streak = calculate_streak(habit)
    assert streak >= 1  # At least the most recent relevant day


def test_calculate_streak_x_per_week():
    """Test x_per_week streak (3 times per week)."""
    today = datetime.now().date()
    
    # Find Monday of current week
    days_since_monday = today.weekday()
    monday = today - timedelta(days=days_since_monday)
    
    # Current week: 3 completions (Mon, Tue, Wed)
    # Previous week: 3 completions (Mon, Tue, Wed)
    habit = {
        "frequency": {
            "type": "x_per_week",
            "count": 3
        },
        "completions": [
            {"date": monday.isoformat()},  # This week Mon
            {"date": (monday + timedelta(days=1)).isoformat()},  # This week Tue
            {"date": (monday + timedelta(days=2)).isoformat()},  # This week Wed
            # Previous week
            {"date": (monday - timedelta(days=7)).isoformat()},  # Last week Mon
            {"date": (monday - timedelta(days=6)).isoformat()},  # Last week Tue
            {"date": (monday - timedelta(days=5)).isoformat()},  # Last week Wed
        ]
    }
    
    streak = calculate_streak(habit)
    assert streak >= 2  # Both weeks meet the target


def test_calculate_streak_weekly():
    """Test weekly streak (at least 1 per week)."""
    today = datetime.now().date()
    
    habit = {
        "frequency": {"type": "weekly"},
        "completions": [
            {"date": today.isoformat()},  # This week
            {"date": (today - timedelta(days=7)).isoformat()},  # Last week
            {"date": (today - timedelta(days=14)).isoformat()},  # 2 weeks ago
        ]
    }
    
    streak = calculate_streak(habit)
    assert streak >= 1


def test_calculate_streak_monthly():
    """Test monthly streak (at least 1 per month)."""
    today = datetime.now().date()
    
    # This month
    habit = {
        "frequency": {"type": "monthly"},
        "completions": [
            {"date": today.isoformat()},
        ]
    }
    
    streak = calculate_streak(habit)
    assert streak >= 1


def test_calculate_streak_custom_interval():
    """Test custom interval streak (every 3 days)."""
    today = datetime.now().date()
    
    habit = {
        "frequency": {
            "type": "custom",
            "interval": 3
        },
        "completions": [
            {"date": today.isoformat()},
            {"date": (today - timedelta(days=3)).isoformat()},
            {"date": (today - timedelta(days=6)).isoformat()},
        ]
    }
    
    streak = calculate_streak(habit)
    assert streak == 3


def test_should_check_today_daily():
    """Test should_check_today for daily habit."""
    habit = {"frequency": {"type": "daily"}}
    assert should_check_today(habit) is True


def test_should_check_today_specific_days():
    """Test should_check_today for specific_days habit."""
    today_weekday = datetime.now().date().weekday()
    
    # Habit relevant today
    habit = {
        "frequency": {
            "type": "specific_days",
            "days": [today_weekday]
        }
    }
    assert should_check_today(habit) is True
    
    # Habit not relevant today
    other_day = (today_weekday + 1) % 7
    habit = {
        "frequency": {
            "type": "specific_days",
            "days": [other_day]
        }
    }
    assert should_check_today(habit) is False


def test_should_check_today_x_per_week():
    """Test should_check_today for x_per_week habit."""
    habit = {
        "frequency": {
            "type": "x_per_week",
            "count": 3
        }
    }
    assert should_check_today(habit) is True


def test_should_check_today_weekly():
    """Test should_check_today for weekly habit."""
    habit = {"frequency": {"type": "weekly"}}
    assert should_check_today(habit) is True


def test_should_check_today_monthly():
    """Test should_check_today for monthly habit."""
    habit = {"frequency": {"type": "monthly"}}
    assert should_check_today(habit) is True


def test_should_check_today_custom_ready():
    """Test should_check_today for custom interval when ready."""
    today = datetime.now().date()
    
    habit = {
        "frequency": {
            "type": "custom",
            "interval": 3
        },
        "completions": [
            {"date": (today - timedelta(days=3)).isoformat()}
        ]
    }
    assert should_check_today(habit) is True


def test_should_check_today_custom_not_ready():
    """Test should_check_today for custom interval when not ready."""
    today = datetime.now().date()
    
    habit = {
        "frequency": {
            "type": "custom",
            "interval": 3
        },
        "completions": [
            {"date": (today - timedelta(days=1)).isoformat()}
        ]
    }
    assert should_check_today(habit) is False


def test_get_completion_rate_daily_perfect():
    """Test completion rate for daily habit with 100%."""
    today = datetime.now().date()
    
    completions = []
    for i in range(30):
        completions.append({"date": (today - timedelta(days=i)).isoformat()})
    
    habit = {
        "frequency": {"type": "daily"},
        "completions": completions
    }
    
    rate = get_completion_rate(habit, days=30)
    assert rate == 100.0


def test_get_completion_rate_daily_half():
    """Test completion rate for daily habit with 50%."""
    today = datetime.now().date()
    
    completions = []
    for i in range(0, 30, 2):  # Every other day
        completions.append({"date": (today - timedelta(days=i)).isoformat()})
    
    habit = {
        "frequency": {"type": "daily"},
        "completions": completions
    }
    
    rate = get_completion_rate(habit, days=30)
    assert 45 <= rate <= 55  # Around 50%


def test_get_completion_rate_specific_days():
    """Test completion rate for specific_days habit."""
    today = datetime.now().date()
    today_weekday = today.weekday()
    
    # Create habit for Mon, Wed, Fri
    habit = {
        "frequency": {
            "type": "specific_days",
            "days": [0, 2, 4]
        },
        "completions": []
    }
    
    # Add completions for all relevant days in last 30 days
    for i in range(30):
        check_date = today - timedelta(days=i)
        if check_date.weekday() in [0, 2, 4]:
            habit["completions"].append({"date": check_date.isoformat()})
    
    rate = get_completion_rate(habit, days=30)
    assert rate == 100.0


def test_get_completion_rate_empty():
    """Test completion rate with no completions."""
    habit = {
        "frequency": {"type": "daily"},
        "completions": []
    }
    
    rate = get_completion_rate(habit, days=30)
    assert rate == 0.0


def test_get_weekly_summary():
    """Test weekly summary returns correct structure."""
    today = datetime.now().date()
    
    habit = {
        "frequency": {"type": "daily"},
        "completions": [
            {"date": today.isoformat()},
            {"date": (today - timedelta(days=1)).isoformat()},
        ]
    }
    
    summary = get_weekly_summary(habit)
    
    # Check structure
    assert isinstance(summary, dict)
    assert "Monday" in summary
    assert "Tuesday" in summary
    assert "Wednesday" in summary
    assert "Thursday" in summary
    assert "Friday" in summary
    assert "Saturday" in summary
    assert "Sunday" in summary
    
    # Check values are valid
    valid_statuses = ["checked", "skipped", "missed", "upcoming", "not_relevant"]
    for day, status in summary.items():
        assert status in valid_statuses


def test_get_weekly_summary_with_skip():
    """Test weekly summary handles skipped days."""
    today = datetime.now().date()
    
    habit = {
        "frequency": {"type": "daily"},
        "completions": [
            {"date": today.isoformat(), "type": "check"},
            {"date": (today - timedelta(days=1)).isoformat(), "type": "skip"},
        ]
    }
    
    summary = get_weekly_summary(habit)
    
    # Find today's day name
    day_names = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
    today_name = day_names[today.weekday()]
    yesterday_name = day_names[(today.weekday() - 1) % 7]
    
    assert summary[today_name] == "checked"
    assert summary[yesterday_name] == "skipped"


def test_get_weekly_summary_specific_days():
    """Test weekly summary marks non-relevant days correctly."""
    today = datetime.now().date()
    today_weekday = today.weekday()
    
    # Habit only for Monday (0)
    habit = {
        "frequency": {
            "type": "specific_days",
            "days": [0]
        },
        "completions": []
    }
    
    summary = get_weekly_summary(habit)
    
    # All days except Monday should be not_relevant or upcoming
    for day_name, status in summary.items():
        if day_name == "Monday":
            continue  # Monday can be any status
        if status not in ["upcoming", "not_relevant"]:
            # Day should be not_relevant if it's in the past
            pass


def test_check_and_award_weekly_lives_awards_life_with_checkin():
    """Test that +1 life is awarded if there was ≥1 check-in in previous week."""
    today = datetime.now().date()
    current_week_start = today - timedelta(days=today.weekday())
    previous_week_start = current_week_start - timedelta(days=7)

    # Add check-in in previous week (Wednesday)
    habit = {
        "lives": 2,
        "completions": [
            {"date": (previous_week_start + timedelta(days=2)).isoformat(), "type": "check"}
        ]
    }

    new_lives, was_awarded = check_and_award_weekly_lives(habit)

    assert was_awarded == True
    assert new_lives == 3


def test_check_and_award_weekly_lives_no_award_without_checkin():
    """Test that no life is awarded if there were no check-ins in previous week."""
    today = datetime.now().date()
    current_week_start = today - timedelta(days=today.weekday())

    # Add check-in in current week only
    habit = {
        "lives": 2,
        "completions": [
            {"date": (current_week_start + timedelta(days=1)).isoformat(), "type": "check"}
        ]
    }

    new_lives, was_awarded = check_and_award_weekly_lives(habit)

    assert was_awarded == False
    assert new_lives == 2


def test_check_and_award_weekly_lives_no_duplicate_award():
    """Test that life is not awarded twice in the same week."""
    today = datetime.now().date()
    current_week_start = today - timedelta(days=today.weekday())
    previous_week_start = current_week_start - timedelta(days=7)

    # Add check-in in previous week and mark as already awarded this week
    habit = {
        "lives": 3,
        "lastLivesAward": current_week_start.isoformat(),
        "completions": [
            {"date": (previous_week_start + timedelta(days=2)).isoformat(), "type": "check"}
        ]
    }

    new_lives, was_awarded = check_and_award_weekly_lives(habit)

    assert was_awarded == False
    assert new_lives == 3


def test_check_and_award_weekly_lives_skip_doesnt_count():
    """Test that skips don't count toward weekly recovery."""
    today = datetime.now().date()
    current_week_start = today - timedelta(days=today.weekday())
    previous_week_start = current_week_start - timedelta(days=7)

    # Add only skips in previous week, no check-ins
    habit = {
        "lives": 1,
        "completions": [
            {"date": (previous_week_start + timedelta(days=2)).isoformat(), "type": "skip"},
            {"date": (previous_week_start + timedelta(days=4)).isoformat(), "type": "skip"}
        ]
    }

    new_lives, was_awarded = check_and_award_weekly_lives(habit)

    assert was_awarded == False
    assert new_lives == 1


def test_check_and_award_weekly_lives_multiple_checkins():
    """Test that award works with multiple check-ins in previous week."""
    today = datetime.now().date()
    current_week_start = today - timedelta(days=today.weekday())
    previous_week_start = current_week_start - timedelta(days=7)

    # Add multiple check-ins in previous week
    habit = {
        "lives": 2,
        "completions": [
            {"date": (previous_week_start + timedelta(days=1)).isoformat(), "type": "check"},
            {"date": (previous_week_start + timedelta(days=3)).isoformat(), "type": "check"},
            {"date": (previous_week_start + timedelta(days=5)).isoformat(), "type": "check"}
        ]
    }

    new_lives, was_awarded = check_and_award_weekly_lives(habit)

    assert was_awarded == True
    assert new_lives == 3


def test_check_and_award_weekly_lives_no_cap():
    """Test that lives can accumulate beyond 3."""
    today = datetime.now().date()
    current_week_start = today - timedelta(days=today.weekday())
    previous_week_start = current_week_start - timedelta(days=7)

    # Habit with 5 lives
    habit = {
        "lives": 5,
        "completions": [
            {"date": (previous_week_start + timedelta(days=2)).isoformat(), "type": "check"}
        ]
    }

    new_lives, was_awarded = check_and_award_weekly_lives(habit)

    assert was_awarded == True
    assert new_lives == 6


def test_check_and_award_weekly_lives_missing_last_award_field():
    """Test backward compatibility when lastLivesAward field is missing."""
    today = datetime.now().date()
    current_week_start = today - timedelta(days=today.weekday())
    previous_week_start = current_week_start - timedelta(days=7)

    # Habit without lastLivesAward field (backward compatible)
    habit = {
        "lives": 2,
        "completions": [
            {"date": (previous_week_start + timedelta(days=2)).isoformat(), "type": "check"}
        ]
    }

    new_lives, was_awarded = check_and_award_weekly_lives(habit)

    assert was_awarded == True
    assert new_lives == 3


if __name__ == "__main__":
    # Run all tests
    import inspect
    
    test_functions = [
        obj for name, obj in inspect.getmembers(sys.modules[__name__])
        if inspect.isfunction(obj) and name.startswith("test_")
    ]
    
    passed = 0
    failed = 0
    
    for test_func in test_functions:
        try:
            test_func()
            print(f"✓ {test_func.__name__}")
            passed += 1
        except AssertionError as e:
            print(f"✗ {test_func.__name__}: {e}")
            failed += 1
        except Exception as e:
            print(f"✗ {test_func.__name__}: {type(e).__name__}: {e}")
            failed += 1
    
    print(f"\n{passed} passed, {failed} failed")
    sys.exit(0 if failed == 0 else 1)
