"""The home page's featured-savings selection, exactly as agreed (v2, v2.1)."""

from app.services.featured import SavingRecord, headline, pick_featured


def rec(pid, usd, pct, awarded=False, image=False, shops=3):
    return SavingRecord(pid, usd, pct, awarded, image, shops)


class TestChampions:
    def test_best_dollar_and_best_percent_always_show(self):
        """The page's promise is 'the best savings' -- no photo requirement,
        no award, nothing, may hide either champion."""
        records = [rec(1, 500, 0.05), rec(2, 5, 0.90)] + [
            rec(i, 50, 0.30, awarded=True, image=True) for i in range(3, 15)
        ]
        picks = pick_featured(records, total=8)
        assert 1 in picks and 2 in picks


class TestWinnerFloor:
    def test_at_least_two_winners_when_two_exist(self):
        records = [rec(i, 100 - i, 0.30) for i in range(1, 11)] + [
            rec(20, 8, 0.05, awarded=True), rec(21, 7, 0.04, awarded=True),
        ]
        picks = pick_featured(records, total=8)
        assert {20, 21} <= set(picks)

    def test_a_lone_winner_is_still_favored_in(self):
        records = [rec(i, 100 - i, 0.30) for i in range(1, 11)] + [
            rec(20, 8, 0.05, awarded=True)
        ]
        picks = pick_featured(records, total=8)
        assert 20 in picks
        assert len(picks) == 8

    def test_winners_without_savings_never_count(self):
        records = [rec(i, 100 - i, 0.30) for i in range(1, 11)] + [
            rec(20, 0, 0.0, awarded=True), rec(21, 0, 0.0, awarded=True),
        ]
        picks = pick_featured(records, total=8)
        assert 20 not in picks and 21 not in picks

    def test_more_than_two_winners_can_earn_their_way_in(self):
        """The floor is a floor: the award bonus lets strong winners exceed it."""
        records = [rec(i, 60, 0.25, awarded=True) for i in range(1, 6)] + [
            rec(i, 55, 0.22) for i in range(10, 20)
        ]
        picks = pick_featured(records, total=8)
        assert sum(1 for p in picks if p < 6) == 5


class TestImagePreference:
    def test_photo_wins_a_close_call(self):
        records = [rec(1, 50, 0.20, image=True), rec(2, 51, 0.20)] + [
            rec(i, 100, 0.5) for i in range(3, 9)
        ]
        picks = pick_featured(records, total=8)
        assert picks.index(1) < picks.index(2)

    def test_photo_never_buries_a_clearly_better_saving(self):
        records = [rec(1, 200, 0.50), rec(2, 40, 0.10, image=True)] + [
            rec(i, 30, 0.1, image=True) for i in range(3, 10)
        ]
        picks = pick_featured(records, total=4)
        assert picks[0] == 1


class TestShape:
    def test_no_repeats_and_respects_total(self):
        records = [rec(i, i * 3, i * 0.02, awarded=(i % 3 == 0), image=(i % 2 == 0))
                   for i in range(1, 30)]
        picks = pick_featured(records, total=8)
        assert len(picks) == 8 == len(set(picks))

    def test_empty_and_tiny_inputs(self):
        assert pick_featured([], total=8) == []
        assert pick_featured([rec(1, 5, 0.1)], total=8) == [1]

    def test_deterministic_on_ties(self):
        records = [rec(i, 10, 0.10) for i in range(1, 6)]
        assert pick_featured(records, total=3) == pick_featured(records, total=3) == [1, 2, 3]


class TestCorroboration:
    def test_an_extreme_two_shop_spread_is_not_featured(self):
        """$21 vs $77 at exactly two shops: as likely someone's pricing error
        as a bargain. Product pages still show it; the home page waits for a
        third shop to corroborate."""
        records = [rec(1, 56, 0.73, shops=2)] + [rec(i, 30, 0.3) for i in range(2, 10)]
        assert 1 not in pick_featured(records, total=8)

    def test_the_same_spread_with_three_shops_is_featured(self):
        records = [rec(1, 56, 0.73, shops=3)] + [rec(i, 30, 0.3) for i in range(2, 10)]
        assert 1 in pick_featured(records, total=8)

    def test_a_modest_two_shop_spread_is_fine(self):
        records = [rec(1, 90, 0.45, shops=2)] + [rec(i, 30, 0.3) for i in range(2, 10)]
        assert 1 in pick_featured(records, total=8)


class TestHeadline:
    """v2.1 (rian, 11 Sep): an award winner with a photo takes the big card whenever
    it can without looking bought. The pages below are staging's own, from the
    simulation over 438 airport sets that set the guard."""

    def page(self, *cards):
        return [rec(i, usd, pct, awarded=aw, image=img) for i, (usd, pct, aw, img) in enumerate(cards, start=1)]

    def test_a_winner_with_a_photo_leads_when_it_sits_with_the_pack(self):
        # $12 at 33% under one outlier ($75): the rest of the page saves about as much.
        page = self.page((75, 0.21, False, False), (21, 0.33, False, False), (14, 0.25, False, False),
                         (12.33, 0.33, True, True), (10, 0.34, False, False), (8, 0.24, False, False))
        assert headline(page).product_id == 4

    def test_one_outlier_may_tower_over_it(self):
        # A $2,004 bottle alone does not veto the headline.
        page = self.page((2004, 0.26, False, False), (33, 0.75, False, False), (43.47, 0.37, True, True),
                         (32, 0.54, False, False), (29, 0.34, False, False))
        assert headline(page).product_id == 3

    def test_two_cards_dwarfing_it_keep_the_top_pick(self):
        """The case that set the guard: a $10 headline above $65 and $57 cards
        reads as a paid placement."""
        page = self.page((65, 0.42, False, False), (57, 0.46, False, False), (10, 0.27, True, True),
                         (8, 0.34, False, False), (4, 0.09, False, False))
        assert headline(page).product_id == 1

    def test_an_award_alone_or_a_photo_alone_is_not_enough(self):
        page = self.page((40, 0.30, False, False), (38, 0.29, True, False), (37, 0.28, False, True))
        assert headline(page).product_id == 1

    def test_between_two_candidates_the_better_saving_leads(self):
        page = self.page((50, 0.30, False, False), (30, 0.20, True, True), (35, 0.40, True, True))
        assert headline(page).product_id == 3

    def test_pick_featured_puts_the_headline_first_and_keeps_the_rest_in_order(self):
        records = [rec(1, 65, 0.42), rec(2, 30, 0.35), rec(3, 28, 0.33), rec(4, 25, 0.40, awarded=True, image=True),
                   rec(5, 20, 0.30), rec(6, 18, 0.28)]
        picks = pick_featured(records, total=6)
        assert picks[0] == 4
        assert sorted(picks) == [1, 2, 3, 4, 5, 6]
        without = [p for p in picks if p != 4]
        assert without == [p for p in pick_featured([r for r in records if r.product_id != 4], total=5)]
