<?php

namespace Tests\Unit;

use App\Domain\Financial\Support\SubscriptionLine;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

/**
 * Reading hand-typed invoice text.
 *
 * Every description below is a real line from a production academy's invoices,
 * because the failures this guards against are all failures of reading real
 * Arabic that a receptionist typed, not of reading text a developer invented.
 */
class SubscriptionLineTest extends TestCase
{
    /** Names as they exist in that academy. */
    private const PROGRAMS = [
        'فريق 2011/2012',
        'اكاديمية 2017 -2018',
        'أكاديمية الساعة الأولى',
        'اكاديمية اطفال 2021 - 2022',
        'GK',
    ];

    private const PRODUCTS = [
        'شنطة ملابس كرة القدم',
        'قيد اشتراك فريق اتحاد الكرة',
    ];

    private function isSubscription(string $description): bool
    {
        return SubscriptionLine::isSubscription($description, self::PROGRAMS, self::PRODUCTS);
    }

    // ---- what counts as subscription --------------------------------------

    #[DataProvider('subscriptionLines')]
    public function test_training_money_is_recognised_as_subscription(string $description): void
    {
        $this->assertTrue($this->isSubscription($description), "[{$description}] should count as subscription");
    }

    public static function subscriptionLines(): array
    {
        return [
            'the monthly run' => ['اشتراك يوليو 2026'],
            'a renewal' => ['تجديد اشتراك: فريق 2011/2012'],
            'the programme name alone' => ['أكاديمية الساعة الأولى'],
            'a prorated join' => ['اكاديمية 2017 -2018 (متناسب: 10 من 30 يوم)'],
            'generic wording' => ['الاشتراك الشهري'],
            'half a month' => ['اشتراك نص شهر'],
            // "تجهيزي" (preparatory) contains the letters of "زي" (kit). A
            // substring match would file this player's training fee as merchandise.
            'a programme whose name contains the letters of "kit"' => ['اكاديمية تجهيزي  2019'],
        ];
    }

    #[DataProvider('nonSubscriptionLines')]
    public function test_money_for_something_other_than_training_is_excluded(string $description): void
    {
        $this->assertFalse($this->isSubscription($description), "[{$description}] must not count as subscription");
    }

    public static function nonSubscriptionLines(): array
    {
        return [
            'kit' => ['الزي'],
            'part of the kit' => ['جزء من الزي'],
            'a bag' => ['شنطة لبس'],
            'part of the bag' => ['جزء من الشنطة'],
            'the product, typed by hand' => ['شنطة ملابس كرة القدم'],
            'a registration instalment' => ['قسط القيد'],
            'a registration instalment with a name on it' => ['قسط قيد ياسين'],
            // Contains the word "اشتراك", and is still a federation fee.
            'the federation registration' => ['قيد اشتراك'],
            'registration settled in full' => ['قيد خالص (د.يحيى فارس)'],
            'the remainder of a registration' => ['أقساط متبقية من قيد اتحاد الكرة'],
            // Training is billed by the month, never in instalments, so a line
            // that only says "the first instalment" is paying off a card or a
            // kit. Counted as subscription it made a player who had paid 2,500
            // toward his registration read as having paid for July's training.
            'an instalment of something unnamed' => ['القسط الاول'],
            'a bag and an instalment on one line' => ['الشنطة و القسط الاول'],
        ];
    }

    public function test_an_instalment_that_names_the_programme_is_still_subscription(): void
    {
        // The academy's own vocabulary outranks the generic keywords: paying a
        // month's training in two halves is still training money.
        $this->assertTrue($this->isSubscription('قسط اشتراك: فريق 2011/2012'));
    }

    public function test_a_programme_whose_own_name_carries_an_excluded_word_still_counts(): void
    {
        // The academy's vocabulary outranks the generic keywords, so naming a
        // programme after the registration cannot erase its subscriptions.
        $this->assertTrue(
            SubscriptionLine::isSubscription('قيد الأولمبي', ['قيد الأولمبي'], self::PRODUCTS)
        );
    }

    public function test_an_unrecognised_line_is_still_read_as_subscription(): void
    {
        // The behaviour this replaced. An unknown line keeps counting the way it
        // always did rather than disappearing from a player's total unannounced.
        $this->assertTrue($this->isSubscription('مبلغ متأخر'));
    }

    // ---- which month the money is for -------------------------------------

    public function test_a_month_named_in_the_line_beats_the_date_it_was_typed(): void
    {
        $this->assertSame(
            ['2026-07'],
            SubscriptionLine::monthsCovered('اشتراك يوليو 2026', null, [], '2026-09-14')
        );
    }

    public function test_a_month_written_as_a_number_is_read(): void
    {
        $this->assertSame(
            ['2026-08'],
            SubscriptionLine::monthsCovered('اشتراك شهر 8', null, [], '2026-08-03')
        );
    }

    public function test_arabic_indic_digits_are_read_as_numbers(): void
    {
        $this->assertSame(
            ['2026-09'],
            SubscriptionLine::monthsCovered('اشتراك شهر ٩', null, [], '2026-09-03')
        );
    }

    public function test_a_year_in_a_programme_name_never_dates_the_subscription(): void
    {
        // "فريق 2011/2012" is an age group, not a date. Reading it as one would
        // move a 2026 subscription fifteen years into the past.
        $this->assertSame(
            ['2026-07'],
            SubscriptionLine::monthsCovered('اشتراك يوليو — فريق 2011/2012', null, [], '2026-07-02')
        );
    }

    public function test_a_bare_month_takes_the_year_that_puts_it_nearest_the_invoice(): void
    {
        // Typed on 2 January, "اشتراك ديسمبر" is the December just gone.
        $this->assertSame(
            ['2025-12'],
            SubscriptionLine::monthsCovered('اشتراك ديسمبر', null, [], '2026-01-02')
        );

        // Typed on 20 December, "اشتراك يناير" is the January about to start.
        $this->assertSame(
            ['2026-01'],
            SubscriptionLine::monthsCovered('اشتراك يناير', null, [], '2025-12-20')
        );
    }

    public function test_an_invoice_covering_two_months_names_both(): void
    {
        $this->assertSame(
            ['2026-07', '2026-08'],
            SubscriptionLine::monthsCovered('اشتراك يوليو وأغسطس', null, [], '2026-07-01')
        );
    }

    public function test_a_range_covers_every_month_between_its_ends(): void
    {
        $this->assertSame(
            ['2026-07', '2026-08', '2026-09'],
            SubscriptionLine::monthsCovered('اشتراك من يوليو إلى سبتمبر', null, [], '2026-07-01')
        );
    }

    public function test_the_month_recorded_on_the_invoice_outranks_the_text(): void
    {
        // The retroactive wizard writes the month it is catching up on. Nothing
        // beats a value written on purpose.
        $this->assertSame(
            ['2026-05'],
            SubscriptionLine::monthsCovered('اشتراك', null, ['month' => '2026-05'], '2026-09-01')
        );
    }

    public function test_the_notes_are_read_when_the_line_itself_says_nothing(): void
    {
        $this->assertSame(
            ['2026-06'],
            SubscriptionLine::monthsCovered('اشتراك', 'اشتراك الأكاديمية — يونيو', [], '2026-09-01')
        );
    }

    public function test_a_silent_line_falls_back_to_the_date_it_was_issued(): void
    {
        $this->assertSame(
            ['2026-08'],
            SubscriptionLine::monthsCovered('تجديد اشتراك: فريق 2015', null, [], '2026-08-02')
        );

        $this->assertSame(
            [],
            SubscriptionLine::explicitMonths('تجديد اشتراك: فريق 2015', null, [], '2026-08-02'),
            'Nothing on the invoice names a month, and the caller has to be able to tell.'
        );
    }

    public function test_an_iso_month_is_read_but_an_age_group_is_not(): void
    {
        $this->assertSame(
            ['2026-07'],
            SubscriptionLine::monthsCovered('اشتراك 2026-07', null, [], '2026-09-01')
        );

        $this->assertSame(
            ['2026-08'],
            SubscriptionLine::monthsCovered('اكاديمية 2012-2013', null, [], '2026-08-02'),
            '2012-2013 is an age group; reading it as a date would be nonsense.'
        );
    }
}
