<?php

namespace Tests\Unit;

use Tests\TestCase;

/**
 * 测试召回礼包（gift_id=305）核心逻辑：tier 计算、分布比例、天数计算
 *
 * @see PayRechargeController::getRecallGiftTiers()
 * @see PayRechargeController::vipInactiveGiftSevenDays()
 * @see PayRechargeController::claimVipInactiveGiftDayReward()
 */
class RecallGiftLogicTest extends TestCase
{
    /**
     * 可用档位：9, 19, 29, 39, 49, 59, 79, 99
     * 初档 70%，中档 85%，高档 100%
     */

    /** @test */
    public function it_selects_first_three_tiers_when_avg_below_minimum()
    {
        // avgRecharge < 9 → 初档=9, 中档=19, 高档=29
        $tiers = $this->getRecallGiftTiers(5);
        $this->assertCount(3, $tiers);
        $this->assertEquals(9, $tiers[0]['amount']);
        $this->assertEquals(19, $tiers[1]['amount']);
        $this->assertEquals(29, $tiers[2]['amount']);
        $this->assertEquals(70, $tiers[0]['bonus_percent']);
        $this->assertEquals(85, $tiers[1]['bonus_percent']);
        $this->assertEquals(100, $tiers[2]['bonus_percent']);
    }

    /** @test */
    public function it_selects_tiers_based_on_average_recharge()
    {
        // avgRecharge = 15，初档应为第一个 >= 15 的档位 = 19
        $tiers = $this->getRecallGiftTiers(15);
        $this->assertCount(3, $tiers);
        $this->assertEquals(19, $tiers[0]['amount']);
        $this->assertEquals(29, $tiers[1]['amount']);
        $this->assertEquals(39, $tiers[2]['amount']);
    }

    /** @test */
    public function it_selects_last_three_tiers_when_avg_above_maximum()
    {
        // avgRecharge = 100 > 99，取最后三位：59, 79, 99
        $tiers = $this->getRecallGiftTiers(100);
        $this->assertCount(3, $tiers);
        $this->assertEquals(59, $tiers[0]['amount']);
        $this->assertEquals(79, $tiers[1]['amount']);
        $this->assertEquals(99, $tiers[2]['amount']);
        $this->assertEquals(70, $tiers[0]['bonus_percent']); // 初档
        $this->assertEquals(85, $tiers[1]['bonus_percent']); // 中档
        $this->assertEquals(100, $tiers[2]['bonus_percent']); // 高档
    }

    /** @test */
    public function it_handles_exact_boundary_values()
    {
        // avgRecharge = 9，恰好等于初档
        $tiers = $this->getRecallGiftTiers(9);
        $this->assertEquals(9, $tiers[0]['amount']);

        // avgRecharge = 29，初档应为 29
        $tiers = $this->getRecallGiftTiers(29);
        $this->assertEquals(29, $tiers[0]['amount']);
        $this->assertEquals(39, $tiers[1]['amount']);
        $this->assertEquals(49, $tiers[2]['amount']);
    }

    /** @test */
    public function it_handles_avg_between_tiers()
    {
        // avgRecharge = 25，第一个 >= 25 的是 29
        $tiers = $this->getRecallGiftTiers(25);
        $this->assertEquals(29, $tiers[0]['amount']);
        $this->assertEquals(39, $tiers[1]['amount']);
        $this->assertEquals(49, $tiers[2]['amount']);
    }

    /** @test */
    public function distribution_sums_to_100_percent()
    {
        $distribution = [7, 9, 11, 13, 14, 21, 25];
        $this->assertEquals(100, array_sum($distribution));
    }

    /** @test */
    public function daily_amounts_match_distribution()
    {
        // 如果总奖励 = $10.00，按比例分布
        $totalBonus = 10.00;
        $distribution = [7, 9, 11, 13, 14, 21, 25];

        $dayAmounts = [];
        for ($i = 0; $i < 7; $i++) {
            $dayAmounts[] = round($totalBonus * $distribution[$i] / 100, 2);
        }

        $this->assertEquals(0.70, $dayAmounts[0]); // Day 1: 7%
        $this->assertEquals(0.90, $dayAmounts[1]); // Day 2: 9%
        $this->assertEquals(1.10, $dayAmounts[2]); // Day 3: 11%
        $this->assertEquals(1.30, $dayAmounts[3]); // Day 4: 13%
        $this->assertEquals(1.40, $dayAmounts[4]); // Day 5: 14%
        $this->assertEquals(2.10, $dayAmounts[5]); // Day 6: 21%
        $this->assertEquals(2.50, $dayAmounts[6]); // Day 7: 25%
        // 总和应等于 $10.00（允许浮点误差）
        $this->assertEquals(10.00, round(array_sum($dayAmounts), 2));
    }

    /** @test */
    public function daily_amounts_match_distribution_realistic_bonus()
    {
        // payAmt=19, bonusPercent=70% → totalBonus = 19 * 0.70 = 13.30
        $payAmt = 19.00;
        $bonusPercent = 70;
        $totalBonus = round($payAmt * $bonusPercent / 100, 2);
        $this->assertEquals(13.30, $totalBonus);

        $distribution = [7, 9, 11, 13, 14, 21, 25];
        $dayAmounts = [];
        for ($i = 0; $i < 7; $i++) {
            $dayAmounts[] = round($totalBonus * $distribution[$i] / 100, 2);
        }
        $this->assertEquals(0.93, $dayAmounts[0]); // 13.30 * 7% = 0.931
        $this->assertEquals(1.20, $dayAmounts[1]); // 13.30 * 9% = 1.197
        $this->assertEquals(1.46, $dayAmounts[2]); // 13.30 * 11% = 1.463
        $this->assertEquals(1.73, $dayAmounts[3]); // 13.30 * 13% = 1.729
        $this->assertEquals(1.86, $dayAmounts[4]); // 13.30 * 14% = 1.862
        $this->assertEquals(2.79, $dayAmounts[5]); // 13.30 * 21% = 2.793
        $this->assertEquals(3.33, $dayAmounts[6]); // 13.30 * 25% = 3.325
    }

    /** @test */
    public function inactive_days_calculation_is_correct()
    {
        // 模拟：最后一次充值在 7 天前
        $lastDate = date('Y-m-d', strtotime('-7 days'));
        $today = date('Y-m-d');
        $diffDays = (strtotime($today) - strtotime($lastDate)) / 86400;
        $inactiveDays = max(0, (int)$diffDays - 1);

        // 7天前充值 → diffDays=7 → inactiveDays=6
        // 但需要连续7天未充值，所以 lastPay 应是8天前
        $this->assertEquals(6, $inactiveDays);

        // 8天前充值 → diffDays=8 → inactiveDays=7（满足条件）
        $lastDate = date('Y-m-d', strtotime('-8 days'));
        $diffDays = (strtotime($today) - strtotime($lastDate)) / 86400;
        $inactiveDays = max(0, (int)$diffDays - 1);
        $this->assertEquals(7, $inactiveDays);
    }

    /** @test */
    public function day_offset_from_recharge_to_signin()
    {
        // 如果充值在 2026-05-14
        $createdAt = '2026-05-14';
        
        // 签到第1天 = 充值次日 = 2026-05-15
        $day1Target = date('Ymd', strtotime($createdAt . ' +1 days'));
        $this->assertEquals('20260515', $day1Target);
        
        // 签到第7天 = 充值 + 7天 = 2026-05-21
        $day7Target = date('Ymd', strtotime($createdAt . ' +7 days'));
        $this->assertEquals('20260521', $day7Target);
    }

    /** @test */
    public function days_passed_calculation()
    {
        // 充值当天
        $createdAt = '2026-05-14';
        $today = '2026-05-14';
        $daysPassed = floor((strtotime($today) - strtotime($createdAt)) / 86400);
        $this->assertEquals(0, $daysPassed); // 还不能签到

        // 充值次日
        $today = '2026-05-15';
        $daysPassed = floor((strtotime($today) - strtotime($createdAt)) / 86400);
        $this->assertEquals(1, $daysPassed); // 可以签到第1天

        // 充值7天后
        $today = '2026-05-21';
        $daysPassed = floor((strtotime($today) - strtotime($createdAt)) / 86400);
        $this->assertEquals(7, $daysPassed); // 可以签到第7天

        // 充值8天后（过期）
        $today = '2026-05-22';
        $daysPassed = floor((strtotime($today) - strtotime($createdAt)) / 86400);
        $this->assertEquals(8, $daysPassed); // 超过7天，过期
    }

    /** @test */
    public function claim_day_mapping()
    {
        // 按实际签到次数确定天数，不再依赖自然日
        // 已签到0次 → day=1 (第1次签到) → dayIndex=0 → 7%
        $claimedDaysCount = 0;
        $day = $claimedDaysCount + 1;
        $dayIndex = $day - 1; // 0
        $distribution = [7, 9, 11, 13, 14, 21, 25];
        $this->assertEquals(0, $dayIndex);
        $this->assertEquals(7, $distribution[$dayIndex]);

        // 已签到1次 → day=2 (第2次签到) → dayIndex=1 → 9%
        $claimedDaysCount = 1;
        $day = $claimedDaysCount + 1;
        $dayIndex = $day - 1; // 1
        $this->assertEquals(1, $dayIndex);
        $this->assertEquals(9, $distribution[$dayIndex]);

        // 已签到3次 → day=4 (第4次签到) → dayIndex=3 → 13%
        $claimedDaysCount = 3;
        $day = $claimedDaysCount + 1;
        $dayIndex = $day - 1; // 3
        $this->assertEquals(3, $dayIndex);
        $this->assertEquals(13, $distribution[$dayIndex]);

        // 已签到6次 → day=7 (第7次签到) → dayIndex=6 → 25%
        $claimedDaysCount = 6;
        $day = $claimedDaysCount + 1;
        $dayIndex = $day - 1; // 6
        $this->assertEquals(6, $dayIndex);
        $this->assertEquals(25, $distribution[$dayIndex]);
    }

    /** @test */
    public function claimed_count_determines_next_day()
    {
        // 签到次数直接影响下一次能签第几天

        // 已签到0次，下次签第1天
        $claimedMask = 0; // 0b0000000
        $claimedCount = 0;
        for ($i = 0; $i < 7; $i++) {
            if ($claimedMask & (1 << $i)) {
                $claimedCount++;
            }
        }
        $nextDay = $claimedCount + 1;
        $this->assertEquals(1, $nextDay);

        // 已签到2次（第1、2天），下次签第3天
        $claimedMask = (1 << 0) | (1 << 1); // 0b0000011 = 3
        $claimedCount = 0;
        for ($i = 0; $i < 7; $i++) {
            if ($claimedMask & (1 << $i)) {
                $claimedCount++;
            }
        }
        $nextDay = $claimedCount + 1;
        $this->assertEquals(3, $nextDay);
        $this->assertEquals(2, $claimedCount);

        // 已签到7次，所有签完
        $claimedMask = (1 << 7) - 1; // 0b1111111 = 127
        $claimedCount = 0;
        for ($i = 0; $i < 7; $i++) {
            if ($claimedMask & (1 << $i)) {
                $claimedCount++;
            }
        }
        $nextDay = $claimedCount + 1;
        $this->assertEquals(7, $claimedCount);
        $this->assertEquals(8, $nextDay); // > 7，不能再签
    }

    /** @test */
    public function only_one_claim_per_day()
    {
        // 如果今天已经签过，不能再次签到
        $lastClaimDate = '2026-05-15';
        $todayDate = '2026-05-15';
        $this->assertTrue($lastClaimDate === $todayDate); // 今天已签

        $lastClaimDate = '2026-05-14';
        $todayDate = '2026-05-15';
        $this->assertFalse($lastClaimDate === $todayDate); // 今天未签
    }

    /** @test */
    public function claimed_bits_match_seven_days_view()
    {
        // sevenDays 用 bit0 表示 day1，bit1 表示 day2 ...
        // claim 中用 dayIndex = day-1，也映射到 bit0=day1, bit1=day2

        // 模拟已领取 day1, day2, day3
        $claimedMask = (1 << 0) | (1 << 1) | (1 << 2); // 0b0000111 = 7

        $this->assertTrue((bool)($claimedMask & (1 << 0))); // day1 已领
        $this->assertTrue((bool)($claimedMask & (1 << 1))); // day2 已领
        $this->assertTrue((bool)($claimedMask & (1 << 2))); // day3 已领
        $this->assertFalse((bool)($claimedMask & (1 << 3))); // day4 未领
        $this->assertFalse((bool)($claimedMask & (1 << 6))); // day7 未领

        // 所有 7 天都已领完
        $allClaimedMask = (1 << 7) - 1; // 0b1111111 = 127
        $this->assertEquals(127, $allClaimedMask);

        $isAllClaimed = ($claimedMask & $allClaimedMask) == $allClaimedMask;
        $this->assertFalse($isAllClaimed); // 只领了3天

        $fullMask = 0b1111111;
        $this->assertTrue(($fullMask & $allClaimedMask) == $allClaimedMask);
    }

    /**
     * 复制控制器中的 getRecallGiftTiers 逻辑用于纯逻辑测试
     */
    private function getRecallGiftTiers($avgRecharge)
    {
        $amounts = [9, 19, 29, 39, 49, 59, 79, 99];
        $bonusPercents = [70, 85, 100];

        $index = count($amounts);
        foreach ($amounts as $i => $amt) {
            if ($avgRecharge <= $amt) {
                $index = $i;
                break;
            }
        }
        if ($index > count($amounts) - 3) {
            $index = count($amounts) - 3;
        }

        $tiers = [];
        for ($i = 0; $i < 3; $i++) {
            $tiers[] = [
                'amount'        => $amounts[$index + $i],
                'bonus_percent' => $bonusPercents[$i],
            ];
        }

        return $tiers;
    }
}
