<?php

declare(strict_types=1);

namespace App\Tests\Module\Commercial\Domain\Entity;

use App\Module\Commercial\Domain\Entity\Bank;
use App\Module\Commercial\Domain\Entity\PaymentType;
use App\Module\Commercial\Domain\Entity\Supplier;
use App\Module\Commercial\Domain\Entity\SupplierRib;
use App\Shared\Domain\Contract\CategoryInterface;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\Validator\ValidatorInterface;

/**
 * Tests des contraintes inter-champs de l'entite Supplier portees par
 * Assert\Callback (decision figee ERP-89) : RG-2.10 (categorie de type
 * FOURNISSEUR), RG-2.07 (Virement -> banque), RG-2.08 (LCR -> >= 1 RIB).
 *
 * On valide l'entite avec le validator Symfony (mapping par attributs) et on
 * assert le propertyPath exact de chaque violation (contrat ERP-101 :
 * exploitable par extractApiViolations). Pas de base : les Callback ne touchent
 * que des champs en memoire (categories via un double CategoryInterface).
 *
 * @internal
 */
final class SupplierValidationTest extends TestCase
{
    private ValidatorInterface $validator;

    protected function setUp(): void
    {
        $this->validator = Validation::createValidatorBuilder()
            ->enableAttributeMapping()
            ->getValidator()
        ;
    }

    // === RG-2.10 : categories de type FOURNISSEUR ===

    public function testFournisseurCategoryIsAccepted(): void
    {
        $supplier = $this->validSupplier();

        self::assertSame([], $this->violationPaths($supplier));
    }

    public function testNonFournisseurCategoryIsRejectedOnCategoriesPath(): void
    {
        $supplier = new Supplier();
        $supplier->setCompanyName('Recycla SAS');
        $supplier->addCategory($this->category('CLIENT'));

        self::assertContains('categories', $this->violationPaths($supplier));
    }

    public function testMultiTypeCategoryContainingFournisseurIsAccepted(): void
    {
        // RG-2.10 sous ManyToMany : une categorie qui PORTE FOURNISSEUR (parmi
        // d'autres types) reste autorisee sur un fournisseur.
        $supplier = new Supplier();
        $supplier->setCompanyName('Recycla SAS');
        $supplier->addCategory($this->category('CLIENT', 'FOURNISSEUR'));

        self::assertNotContains('categories', $this->violationPaths($supplier));
    }

    // === RG-2.07 : Virement impose une banque ===

    public function testVirementWithoutBankIsRejectedOnBankPath(): void
    {
        $supplier = $this->validSupplier();
        $supplier->setPaymentType($this->paymentType('VIREMENT'));

        self::assertContains('bank', $this->violationPaths($supplier));
    }

    public function testVirementWithBankPasses(): void
    {
        $supplier = $this->validSupplier();
        $supplier->setPaymentType($this->paymentType('VIREMENT'));
        $supplier->setBank(new Bank());

        self::assertNotContains('bank', $this->violationPaths($supplier));
    }

    // === RG-2.08 : LCR impose au moins un RIB ===

    public function testLcrWithoutRibIsRejectedOnPaymentTypePath(): void
    {
        $supplier = $this->validSupplier();
        $supplier->setPaymentType($this->paymentType('LCR'));

        // Miroir client : la violation LCR -> >= 1 RIB est portee sur `paymentType`
        // (affichee sous le select « Type de règlement », `ribs` n'ayant pas de champ).
        self::assertContains('paymentType', $this->violationPaths($supplier));
    }

    public function testLcrWithRibPasses(): void
    {
        $supplier = $this->validSupplier();
        $supplier->setPaymentType($this->paymentType('LCR'));
        $supplier->addRib(new SupplierRib());

        self::assertNotContains('paymentType', $this->violationPaths($supplier));
    }

    public function testNeutralPaymentTypeRequiresNeitherBankNorRib(): void
    {
        // Un type de reglement neutre (ni VIREMENT ni LCR) n'exige ni banque ni RIB.
        $supplier = $this->validSupplier();
        $supplier->setPaymentType($this->paymentType('CHEQUE'));

        $paths = $this->violationPaths($supplier);
        self::assertNotContains('bank', $paths);
        self::assertNotContains('ribs', $paths);
    }

    /**
     * Fournisseur valide (nom + 1 categorie FOURNISSEUR), sans onglet
     * Comptabilite renseigne : sert de base aux tests RG-2.07/2.08.
     */
    private function validSupplier(): Supplier
    {
        $supplier = new Supplier();
        $supplier->setCompanyName('Recycla SAS');
        $supplier->addCategory($this->category('FOURNISSEUR'));

        return $supplier;
    }

    /**
     * @return list<string> propertyPaths des violations levees par le validator
     */
    private function violationPaths(Supplier $supplier): array
    {
        $paths = [];
        foreach ($this->validator->validate($supplier) as $violation) {
            $paths[] = $violation->getPropertyPath();
        }

        return $paths;
    }

    /**
     * Double minimal de CategoryInterface (pas d'acces base) PORTANT les codes de
     * type voulus — seul element regarde par validateCategoryType. Variadic pour
     * couvrir le cas multi-types (ManyToMany).
     *
     * @return list<string> n'est pas le type de retour : helper renvoyant un double
     */
    private function category(string ...$typeCodes): CategoryInterface
    {
        return new class(array_values($typeCodes)) implements CategoryInterface {
            /** @param list<string> $typeCodes */
            public function __construct(private readonly array $typeCodes) {}

            public function getId(): ?int
            {
                return 1;
            }

            public function getName(): ?string
            {
                return 'Categorie test';
            }

            public function getCode(): ?string
            {
                return 'TEST';
            }

            /** @return list<string> */
            public function getCategoryTypeCodes(): array
            {
                return $this->typeCodes;
            }
        };
    }

    private function paymentType(string $code): PaymentType
    {
        $type = new PaymentType();
        $type->setCode($code);
        $type->setLabel($code);

        return $type;
    }
}
