<?php

declare(strict_types=1);

namespace App\Module\Core\Infrastructure\DataFixtures;

use App\Module\Core\Domain\Entity\Role;
use App\Module\Core\Domain\Entity\User;
use App\Module\Core\Domain\Repository\RoleRepositoryInterface;
use App\Module\Core\Domain\Security\SystemRoles;
use App\Module\Sites\Infrastructure\DataFixtures\SitesFixtures;
use App\Shared\Domain\Contract\SiteInterface;
use App\Shared\Domain\Contract\SiteProviderInterface;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Persistence\ObjectManager;
use RuntimeException;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;

/**
 * Fixtures de base du module Core : 3 utilisateurs (1 admin + 2 standards)
 * rattaches aux roles systeme RBAC seedes par la migration Version20260414150034,
 * puis (ticket 2 module Sites) rattaches a au moins un site avec un currentSite
 * coherent.
 *
 * Note : le purger Doctrine execute avant load() supprime l'ensemble des
 * entites managees, ce qui inclut la table role. On re-seede donc les roles
 * systeme de maniere idempotente avant de rattacher les utilisateurs, afin
 * que le workflow "make db-reset && make fixtures" reste one-shot.
 *
 * Idempotence complete (y compris `doctrine:fixtures:load --append`, sans
 * purge) : roles via ensureSystemRole, utilisateurs via ensureUser (lookup par
 * username). Rejouer la fixture ne cree donc aucun doublon ni violation
 * d'unicite de username.
 *
 * Dependance explicite a SitesFixtures (ticket 2) : les 3 sites Chatellerault,
 * Saint-Jean et Pommevic doivent etre presents en base avant d'etre rattaches
 * aux users. L'inversion volontaire de l'ordre (AppFixtures ← SitesFixtures)
 * casse l'independance declaree au ticket 1 : c'est un couplage assume car
 * apres ticket 2 le modele metier exprime un besoin legitime de rattachement.
 */
class AppFixtures extends Fixture implements DependentFixtureInterface
{
    public function __construct(
        private readonly UserPasswordHasherInterface $passwordHasher,
        private readonly RoleRepositoryInterface $roleRepository,
        private readonly SiteProviderInterface $siteProvider,
    ) {}

    /**
     * @return array<int, class-string>
     */
    public function getDependencies(): array
    {
        // SitesFixtures doit tourner AVANT AppFixtures pour que les sites
        // soient disponibles au rattachement des users ci-dessous.
        return [SitesFixtures::class];
    }

    public function load(ObjectManager $manager): void
    {
        $adminRole = $this->ensureSystemRole(
            $manager,
            SystemRoles::ADMIN_CODE,
            'Administrateur',
            'Role administrateur - bypass complet via is_admin',
        );
        $userRole = $this->ensureSystemRole(
            $manager,
            SystemRoles::USER_CODE,
            'Utilisateur',
            'Role de base sans permission specifique',
        );

        // Recupere les 3 sites seedes par SitesFixtures. Si absents, c'est
        // une misconfiguration (fixture hors purge ou dependance ignoree) :
        // on fail fort avec un message explicite plutot que de continuer
        // avec des users orphelins de site.
        $chatellerault = $this->requireSite('Chatellerault');
        $saintJean     = $this->requireSite('Saint-Jean');
        $pommevic      = $this->requireSite('Pommevic');

        $admin = $this->ensureUser($manager, 'admin');
        $admin->setIsAdmin(true);
        $admin->setPassword($this->passwordHasher->hashPassword($admin, 'admin'));
        $admin->addRbacRole($adminRole);
        // Admin rattache aux 3 sites pour faciliter le dev / les tests manuels.
        $admin->addSite($chatellerault);
        $admin->addSite($saintJean);
        $admin->addSite($pommevic);
        $admin->setCurrentSite($chatellerault);
        $manager->persist($admin);

        $alice = $this->ensureUser($manager, 'alice');
        $alice->setPassword($this->passwordHasher->hashPassword($alice, 'alice'));
        $alice->addRbacRole($userRole);
        // Alice : un seul site, site courant = ce site.
        $alice->addSite($chatellerault);
        $alice->setCurrentSite($chatellerault);
        $manager->persist($alice);

        $bob = $this->ensureUser($manager, 'bob');
        $bob->setPassword($this->passwordHasher->hashPassword($bob, 'bob'));
        $bob->addRbacRole($userRole);
        // Bob : site different de Alice, pour prouver le filtrage par site
        // dans les futurs tests (ticket 4 outillage SiteAware).
        $bob->addSite($saintJean);
        $bob->setCurrentSite($saintJean);
        $manager->persist($bob);

        $manager->flush();
    }

    /**
     * Retourne le role systeme correspondant au code donne, en le creant
     * s'il n'existe pas encore (le purger Doctrine a pu vider la table role).
     *
     * La description est recopiee depuis la migration RBAC pour que les
     * deux chemins (migration prod, fixtures dev) produisent un etat
     * identique.
     */
    private function ensureSystemRole(
        ObjectManager $manager,
        string $code,
        string $label,
        string $description,
    ): Role {
        $role = $this->roleRepository->findByCode($code);

        if (null !== $role) {
            return $role;
        }

        $role = new Role($code, $label, isSystem: true, description: $description);
        $manager->persist($role);

        return $role;
    }

    /**
     * Retourne l'utilisateur correspondant au username, en le creant s'il
     * n'existe pas encore. Rend la fixture idempotente y compris en
     * `doctrine:fixtures:load --append` (sans purge) : sans ce lookup, recreer
     * « admin » / « alice » / « bob » violerait l'unicite de username. Meme
     * esprit que ensureSystemRole ci-dessus et RbacDemoFixtures::ensureDemoUsers.
     */
    private function ensureUser(ObjectManager $manager, string $username): User
    {
        $user = $manager->getRepository(User::class)->findOneBy(['username' => $username]);

        if (null !== $user) {
            return $user;
        }

        $user = new User();
        $user->setUsername($username);

        return $user;
    }

    private function requireSite(string $name): SiteInterface
    {
        $site = $this->siteProvider->findByName($name);

        if (null === $site) {
            throw new RuntimeException(sprintf(
                'SitesFixtures doit avoir seede le site "%s" avant le chargement des users. '
                .'Verifier que SitesFixtures est bien en dependance de AppFixtures.',
                $name,
            ));
        }

        return $site;
    }
}
