<?php

namespace Tests\Unit;

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

/**
 * Recognising the registration card in whatever a receptionist typed.
 *
 * Every description below is a real line from OC-Sport's invoices. The roster
 * counted none of them: it looked only for lines carrying the product's morph,
 * so nine players who had each paid an instalment of the federation card were
 * shown as never having bought one.
 */
class BundledProductLineTest extends TestCase
{
    private const PROGRAMS = [
        'فريق 2011/2012',
        'فريق 2015',
        'اكاديمية 2017 -2018',
        'أكاديمية الساعة الأولى',
    ];

    private const PRODUCT = 'قيد اشتراك فريق اتحاد الكرة';

    private function paysForCard(string $description): bool
    {
        return BundledProductLine::matches(
            $description,
            self::PRODUCT,
            BundledProductLine::identifyingWords(self::PRODUCT, self::PROGRAMS)
        );
    }

    public function test_the_words_that_identify_the_product_exclude_the_ones_every_line_uses(): void
    {
        // اشتراك heads half the subscription lines in the same ledger and فريق
        // is how the programmes are named — neither can identify a product.
        $this->assertSame(
            ['قيد', 'اتحاد'],
            BundledProductLine::identifyingWords(self::PRODUCT, self::PROGRAMS)
        );
    }

    #[DataProvider('registrationLines')]
    public function test_money_typed_for_the_registration_is_recognised(string $description): void
    {
        $this->assertTrue($this->paysForCard($description), "[{$description}] should count toward the card");
    }

    public static function registrationLines(): array
    {
        return [
            'the product typed out' => ['قيد اشتراك فريق اتحاد الكرة'],
            'the word alone' => ['القيد'],
            'an instalment' => ['قسط القيد'],
            'an instalment, spelled loosely' => ['قسط قيد'],
            'an instalment with the child on it' => ['قسط قيد ياسين'],
            'the first instalment' => ['القسط الاول من القيد'],
            'the first instalment, with hamza' => ['القسط الأول من القيد'],
            'what is left of it' => ['أقساط متبقية من قيد اتحاد الكرة'],
            'settled in full' => ['قيد خالص (د.يحيى فارس)'],
            'the fee, described as a subscription' => ['قيد اشتراك'],
        ];
    }

    #[DataProvider('otherLines')]
    public function test_training_and_other_goods_are_not_counted_as_the_registration(string $description): void
    {
        $this->assertFalse($this->paysForCard($description), "[{$description}] must not count toward the card");
    }

    public static function otherLines(): array
    {
        return [
            'a month of training' => ['اشتراك سبتمبر 2026: فريق 2011'],
            'a renewal' => ['تجديد اشتراك: فريق 2015'],
            'the programme alone' => ['أكاديمية الساعة الأولى'],
            'the kit' => ['الزي'],
            'a bag' => ['شنطة لبس'],
            // Says nothing about what is being paid off. Only the invoice it
            // sits on can answer that, which is the caller's job.
            'a bare instalment' => ['القسط الاول'],
            'nothing at all' => [''],
        ];
    }

    #[DataProvider('bareInstalments')]
    public function test_an_instalment_that_names_nothing_is_flagged_as_such(string $description): void
    {
        $this->assertTrue(BundledProductLine::isBareInstalment($description));
    }

    public static function bareInstalments(): array
    {
        return [
            'the first instalment' => ['القسط الاول'],
            'a bag and the first instalment' => ['الشنطة و القسط الاول'],
            'instalments' => ['أقساط متبقية'],
        ];
    }

    public function test_a_month_of_training_is_not_a_bare_instalment(): void
    {
        $this->assertFalse(BundledProductLine::isBareInstalment('اشتراك سبتمبر 2026: فريق 2011'));
    }

    public function test_a_line_naming_a_programme_is_recognised_as_training(): void
    {
        $this->assertTrue(BundledProductLine::namesProgram('اشتراك سبتمبر 2026: فريق 2015', self::PROGRAMS));
        $this->assertFalse(BundledProductLine::namesProgram('القسط الاول', self::PROGRAMS));
    }

    public function test_a_product_named_after_a_programme_cannot_claim_its_subscriptions(): void
    {
        // Every word of this product's name is also a programme's, so it has no
        // identifying word left and only its full name can match it. A month's
        // training must never be counted as the product.
        $product = 'فريق 2015';
        $words = BundledProductLine::identifyingWords($product, self::PROGRAMS);

        $this->assertSame([], $words);
        $this->assertFalse(BundledProductLine::matches('اشتراك سبتمبر 2026: فريق 2015', $product, $words));
        $this->assertTrue(BundledProductLine::matches('فريق 2015', $product, $words));
    }
}
