"""Tests for app.services.feedback_analyzer."""
from datetime import date, timedelta
from uuid import uuid4

import pytest

from app.models import (
    DenialReason,
    FamilyProfile,
    FamilyMember,
    FamilyMemberRole,
    Feedback,
    MealPlan,
    MealPlanItem,
    MealPlanItemStatus,
    MealType,
    NeverSuggest,
    Recipe,
    WeeklyRun,
)
from app.services.feedback_analyzer import FeedbackAnalyzer


@pytest.fixture()
def make_family(db):
    def _make():
        fp = FamilyProfile(
            id=uuid4(),
            name="TestFamily",
            household_size=2,
            adult_count=2,
            child_count=0,
            pending_approval_policy="approve",
        )
        db.add(fp)
        db.flush()
        return fp
    return _make


@pytest.fixture()
def make_member(db, make_family):
    def _make(family=None):
        fp = family or make_family()
        m = FamilyMember(
            id=uuid4(),
            family_profile_id=fp.id,
            name="Alice",
            role=FamilyMemberRole.ADULT,
        )
        db.add(m)
        db.flush()
        return m
    return _make


@pytest.fixture()
def make_recipe(db):
    def _make(**kw):
        r = Recipe(
            id=uuid4(),
            name=kw.get("name", "Test Recipe"),
            servings=kw.get("servings", 4),
            ingredients=[{"name": "ing", "qty": 1, "unit": "cup"}],
            instructions=["cook"],
            cuisine_tags=kw.get("cuisine_tags", []),
            protein_type=kw.get("protein_type", None),
        )
        db.add(r)
        db.flush()
        return r
    return _make


@pytest.fixture()
def make_meal_plan(db, make_family):
    def _make(family=None, week_start=None):
        week = week_start or date.today()
        mp = MealPlan(
            id=uuid4(),
            family_profile_id=(family or make_family()).id,
            week_start_date=week,
        )
        db.add(mp)
        db.flush()
        return mp
    return _make


@pytest.fixture()
def make_item(db, make_meal_plan, make_recipe):
    def _make(meal_plan=None, recipe=None, status=MealPlanItemStatus.pending, day_of_week=1):
        mp = meal_plan or make_meal_plan()
        r = recipe or make_recipe()
        item = MealPlanItem(
            id=uuid4(),
            meal_plan_id=mp.id,
            recipe_id=r.id,
            day_of_week=day_of_week,
            meal_type=MealType.DINNER,
            approval_status=status,
        )
        db.add(item)
        db.flush()
        return item
    return _make


class TestFeedbackAnalyzer:
    def test_insufficient_feedback(self, db, make_family):
        family = make_family()
        analyzer = FeedbackAnalyzer(lookback_weeks=4)
        today = date.today()
        result = analyzer.analyze(db, family.id, today=today)
        assert result.total_feedback_count == 0
        assert result.confidence == 0.0
        assert result.discovery_queries == []

    def test_positive_cuisine_signal(self, db, make_family, make_member, make_recipe, make_meal_plan, make_item):
        family = make_family()
        member = make_member(family=family)
        recipe = make_recipe(cuisine_tags=["mexican"], protein_type="chicken")
        mp = make_meal_plan(family=family)
        item = make_item(meal_plan=mp, recipe=recipe)

        # 3 feedbacks, avg rating 4 (>= threshold, >= 2 samples)
        for _ in range(3):
            f = Feedback(
                id=uuid4(),
                family_profile_id=family.id,
                meal_plan_item_id=item.id,
                rating=4,
            )
            db.add(f)
        db.flush()

        analyzer = FeedbackAnalyzer(lookback_weeks=4)
        result = analyzer.analyze(db, family.id)
        assert result.total_feedback_count == 3
        assert result.confidence == 1.0

        pos = result.positive_signals
        assert len(pos) == 2  # cuisine + protein
        assert any(s.type == "prefer_cuisine" and s.value == "mexican" for s in pos)
        assert any(s.type == "prefer_protein" and s.value == "chicken" for s in pos)

    def test_never_suggest_blocks_recipe(self, db, make_family, make_member, make_recipe, make_meal_plan, make_item):
        family = make_family()
        member = make_member(family=family)
        recipe = make_recipe(cuisine_tags=["indian"], protein_type="lamb")
        mp = make_meal_plan(family=family)
        item = make_item(meal_plan=mp, recipe=recipe)

        # Deny with never-suggest
        f = Feedback(
            id=uuid4(),
            family_profile_id=family.id,
            meal_plan_item_id=item.id,
            rating=1,
            never_suggest=True,
            denial_reason=DenialReason.DISLIKED_INGREDIENT,
        )
        db.add(f)
        db.flush()

        ns = NeverSuggest(
            id=uuid4(),
            family_profile_id=family.id,
            recipe_id=recipe.id,
        )
        db.add(ns)
        db.flush()

        analyzer = FeedbackAnalyzer(lookback_weeks=4)
        result = analyzer.analyze(db, family.id)
        pos = result.positive_signals
        # 1 feedback < min, therefore no positive signals
        assert len(pos) == 0

    def test_denial_reason_aggregated(self, db, make_family, make_member, make_recipe, make_meal_plan, make_item):
        family = make_family()
        member = make_member(family=family)
        recipe = make_recipe()
        mp = make_meal_plan(family=family)
        item = make_item(meal_plan=mp, recipe=recipe)

        for _ in range(3):
            f = Feedback(
                id=uuid4(),
                family_profile_id=family.id,
                meal_plan_item_id=item.id,
                rating=2,
                denial_reason=DenialReason.TOO_EXPENSIVE,
            )
            db.add(f)
        db.flush()

        analyzer = FeedbackAnalyzer(lookback_weeks=4)
        result = analyzer.analyze(db, family.id)
        negatives = result.negative_signals
        assert any(n.type == "denial_too_expensive" for n in negatives)
        assert negatives[0].count == 3

    def test_discovery_queries_capped(self, db, make_family, make_member, make_recipe, make_meal_plan, make_item):
        family = make_family()
        member = make_member(family=family)
        cuisines = ["mexican", "italian", "chinese"]
        proteins = ["chicken", "beef", "shrimp"]
        for i, (c, p) in enumerate(zip(cuisines, proteins)):
            recipe = make_recipe(name=f"R{i}", cuisine_tags=[c], protein_type=p)
            mp = make_meal_plan(family=family, week_start=date.today() - timedelta(weeks=i))
            item = make_item(meal_plan=mp, recipe=recipe)
            for _ in range(2):
                db.add(Feedback(
                    id=uuid4(),
                    family_profile_id=family.id,
                    meal_plan_item_id=item.id,
                    rating=5,
                ))
        db.flush()

        analyzer = FeedbackAnalyzer(lookback_weeks=4)
        result = analyzer.analyze(db, family.id)
        queries = result.discovery_queries
        assert len(queries) <= 5
        # At least one cuisine+protein cross query
        assert any(" " in q for q in queries)

    def test_top_rated_sorted(self, db, make_family, make_member, make_recipe, make_meal_plan, make_item):
        family = make_family()
        member = make_member(family=family)
        r1 = make_recipe(name="Awesome Dish")
        r2 = make_recipe(name="Meh Dish")
        mp = make_meal_plan(family=family)
        item1 = make_item(meal_plan=mp, recipe=r1)
        item2 = make_item(meal_plan=mp, recipe=r2, day_of_week=2)

        db.add(Feedback(id=uuid4(), family_profile_id=family.id, meal_plan_item_id=item1.id, rating=5))
        db.add(Feedback(id=uuid4(), family_profile_id=family.id, meal_plan_item_id=item1.id, rating=5))
        db.add(Feedback(id=uuid4(), family_profile_id=family.id, meal_plan_item_id=item2.id, rating=3))
        db.flush()

        analyzer = FeedbackAnalyzer(lookback_weeks=4)
        result = analyzer.analyze(db, family.id)
        assert result.top_rated_recipe_names[0] == "Awesome Dish"

