<?php

namespace Tests\Unit;

use App\Domain\Pricing\Models\PricingRule;
use App\Domain\Pricing\Services\PricingService;
use Carbon\Carbon;
use Tests\TestCase;

/**
 * The participant context the discount rules are judged against.
 *
 * Carbon 3 signs its differences: `$a->diffIn*($b)` answers `$b − $a`. The
 * engine asked `now()->diffInYears($birthday)`, which for anyone already born
 * is negative, and the damage ran in both directions at once — a rule with a
 * `min` (the twelve-month loyalty discount) stopped matching a single member,
 * while a rule with a `max` (the juniors discount, `max: 6`) matched the whole
 * academy, because −34 is comfortably under six.
 *
 * These pin the arithmetic and the two rules it broke.
 */
class PricingContextAgeTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        Carbon::setTestNow('2026-09-03');
    }

    protected function tearDown(): void
    {
        Carbon::setTestNow();
        parent::tearDown();
    }

    // ---- the arithmetic ---------------------------------------------------

    public function test_age_is_counted_forward_from_the_birthday(): void
    {
        $this->assertSame(12, $this->years(Carbon::parse('2014-05-01')));
        $this->assertSame(36, $this->years(Carbon::parse('1990-01-01')));
    }

    public function test_a_birthday_that_has_not_arrived_this_year_is_not_rounded_up(): void
    {
        // Born 2014-12-31: still 11 on 2026-09-03, not 12.
        $this->assertSame(11, $this->years(Carbon::parse('2014-12-31')));
    }

    public function test_membership_months_are_counted_forward_from_joining(): void
    {
        $this->assertSame(20, $this->months(Carbon::parse('2025-01-01')));
        $this->assertSame(0, $this->months(Carbon::parse('2026-09-01')));
    }

    public function test_a_missing_date_has_no_age_rather_than_a_wrong_one(): void
    {
        $this->assertNull($this->years(null));
        $this->assertNull($this->years(''));
    }

    public function test_a_future_date_is_no_age_rather_than_a_negative_one(): void
    {
        $this->assertSame(0, $this->years(Carbon::parse('2030-01-01')));
        $this->assertSame(0, $this->months(Carbon::parse('2030-01-01')));
    }

    public function test_a_date_string_is_read_as_well_as_a_carbon(): void
    {
        $this->assertSame(12, $this->years('2014-05-01'));
    }

    // ---- the rules it broke -----------------------------------------------

    public function test_an_adult_does_not_qualify_for_the_juniors_discount(): void
    {
        $rule = new PricingRule(['rule_type' => 'age', 'conditions' => ['max' => 6]]);

        $this->assertFalse($this->evaluate($rule, ['age' => $this->years(Carbon::parse('1990-01-01'))]));
        $this->assertTrue($this->evaluate($rule, ['age' => $this->years(Carbon::parse('2021-01-01'))]));
    }

    public function test_a_member_of_two_years_qualifies_for_the_loyalty_discount(): void
    {
        $rule = new PricingRule(['rule_type' => 'loyalty', 'conditions' => ['min' => 12]]);

        $joined = ['membership_duration_months' => $this->months(Carbon::parse('2024-08-01'))];
        $this->assertTrue($this->evaluate($rule, $joined));

        $newcomer = ['membership_duration_months' => $this->months(Carbon::parse('2026-08-01'))];
        $this->assertFalse($this->evaluate($rule, $newcomer));
    }

    // ---- helpers ----------------------------------------------------------

    private function years(mixed $moment): ?int
    {
        return $this->invokePrivate('completedYearsSince', $moment);
    }

    private function months(mixed $moment): ?int
    {
        return $this->invokePrivate('completedMonthsSince', $moment);
    }

    private function invokePrivate(string $method, mixed ...$args): mixed
    {
        $reflected = new \ReflectionMethod(PricingService::class, $method);
        $reflected->setAccessible(true);

        return $reflected->invoke(app(PricingService::class), ...$args);
    }

    private function evaluate(PricingRule $rule, array $context): bool
    {
        return $this->invokePrivate('evaluateConditions', $rule, $context);
    }
}
