from datetime import date, timedelta
from decimal import Decimal
from uuid import uuid4

from app.services.planner.config import PlannerConfig
from app.services.planner.score import (
    score_recipes,
    time_bonus,
    recency_bonus,
    normalize_savings,
)
from app.services.planner.types import RecipeCost


_CFG = PlannerConfig()


def test_time_bonus_capped_at_ideal():
    assert time_bonus(20, _CFG) == 1.0
    assert time_bonus(25, _CFG) == 1.0


def test_time_bonus_decays_to_zero_at_full():
    assert time_bonus(45, _CFG) == 0.0


def test_time_bonus_linear_midpoint():
    # 35 min is halfway between 25 and 45
    assert abs(time_bonus(35, _CFG) - 0.5) < 1e-6


def test_recency_bonus_full_when_long_ago():
    today = date(2026, 5, 5)
    long_ago = today - timedelta(weeks=20)
    assert recency_bonus(long_ago, today, _CFG) == 1.0


def test_recency_bonus_zero_when_just_eligible():
    # right at recency_weeks boundary → 0
    today = date(2026, 5, 5)
    cutoff = today - timedelta(weeks=_CFG.recency_weeks)
    assert recency_bonus(cutoff, today, _CFG) == 0.0


def test_recency_bonus_full_when_never_cooked():
    assert recency_bonus(None, date(2026, 5, 5), _CFG) == 1.0


def test_normalize_savings_minmax():
    out = normalize_savings([Decimal("0"), Decimal("5"), Decimal("10")])
    assert out == [0.0, 0.5, 1.0]


def test_normalize_savings_uniform_returns_zeros():
    out = normalize_savings([Decimal("3"), Decimal("3"), Decimal("3")])
    assert out == [0.0, 0.0, 0.0]


def test_score_recipes_orders_by_combined_score():
    recipes = [
        {
            "id": uuid4(),
            "name": "low_savings",
            "prep_time_minutes": 10,
            "cook_time_minutes": 30,
            "protein_type": "chicken",
            "cuisine_tags": ["american"],
        },
        {
            "id": uuid4(),
            "name": "high_savings",
            "prep_time_minutes": 10,
            "cook_time_minutes": 15,  # also lower time
            "protein_type": "beef",
            "cuisine_tags": ["mexican"],
        },
    ]
    costs = {
        recipes[0]["id"]: RecipeCost(
            recipe_id=recipes[0]["id"],
            total_cost=Decimal("10"),
            total_savings=Decimal("1"),
            sale_ingredient_count=1,
            matched_ingredient_count=4,
            total_ingredient_count=4,
            pantry_hit_count=0,
            line_items=[],
        ),
        recipes[1]["id"]: RecipeCost(
            recipe_id=recipes[1]["id"],
            total_cost=Decimal("12"),
            total_savings=Decimal("8"),
            sale_ingredient_count=3,
            matched_ingredient_count=4,
            total_ingredient_count=4,
            pantry_hit_count=2,
            line_items=[],
        ),
    }
    scored = score_recipes(
        recipes=recipes,
        recipe_costs=costs,
        last_cooked_at={},
        config=_CFG,
        today=date(2026, 5, 5),
    )
    assert scored[0].recipe_id == recipes[1]["id"]  # high_savings first
