<?php

namespace App\Repository;

use App\Entity\StockVehicle;
use App\Service\StockImporter;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

/**
 * @extends ServiceEntityRepository<StockVehicle>
 */
class StockVehicleRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, StockVehicle::class);
    }

    /**
     * Remplace l'intégralité de la photo du stock par les lignes importées.
     *
     * @param list<array{dateStock: ?\DateTimeImmutable, concessionId: string, type: string, plate: string, vin: string, localisation: string}> $rows
     * @param callable(int, int): void|null                                                                                                     $onProgress appelé par lot avec (enregistrés, total)
     *
     * @return int Nombre de véhicules enregistrés
     */
    public function replaceAll(array $rows, ?callable $onProgress = null): int
    {
        $em = $this->getEntityManager();
        $importedAt = new \DateTimeImmutable();

        // Transaction : si un persist/flush échoue, le vidage de la table est annulé
        // (sinon la photo du stock serait perdue).
        return $em->wrapInTransaction(function () use ($em, $rows, $importedAt, $onProgress): int {
            $this->createQueryBuilder('s')->delete()->getQuery()->execute();

            $total = \count($rows);
            $count = 0;
            foreach ($rows as $r) {
                $em->persist(new StockVehicle(
                    $r['dateStock'],
                    $r['concessionId'],
                    $r['type'],
                    $r['plate'],
                    $r['vin'],
                    $r['localisation'],
                    $importedAt,
                ));
                ++$count;
                // Flush par lots (toujours dans la transaction) : mémoire bornée
                // sur les gros fichiers et progression réelle pendant l'insertion.
                if (0 === $count % 250) {
                    $em->flush();
                    $em->clear();
                    if (null !== $onProgress) {
                        $onProgress($count, $total);
                    }
                }
            }

            $em->flush();

            return $count;
        });
    }

    /**
     * Lignes du stock pour les concessions données (IDs ; comparaison tolérante pour
     * les valeurs héritées). Les lignes sans concession sont conservées (non attribuables) ;
     * liste vide = tout le stock.
     *
     * Optionnellement filtré par VN/VO (sur la colonne TYPE).
     *
     * @param array<int, string> $concessionIds
     * @param 'VN'|'VO'|null      $vnVo
     *
     * @return list<array{dateStock: ?\DateTimeImmutable, concessionId: string, type: string, plate: string, vin: string, localisation: string}>
     */
    public function rowsForConcessions(array $concessionIds, ?string $vnVo = null): array
    {
        $wanted = array_map(StockImporter::normalize(...), $concessionIds);

        $rows = [];
        foreach ($this->findAll() as $vehicle) {
            $id = $vehicle->getConcessionId();
            if ([] !== $wanted && '' !== $id && !\in_array(StockImporter::normalize($id), $wanted, true)) {
                continue;
            }
            if (null !== $vnVo && StockImporter::classifyVnVo($vehicle->getType()) !== $vnVo) {
                continue;
            }
            $rows[] = $vehicle->toRow();
        }

        return $rows;
    }

    /**
     * État de la photo du stock pour Paramètres et la page de comparaison.
     *
     * @return array{count: int, concessions: int, dates: list<\DateTimeImmutable>, importedAt: ?\DateTimeImmutable}
     */
    public function summary(): array
    {
        $count = parent::count([]);
        if (0 === $count) {
            return ['count' => 0, 'concessions' => 0, 'dates' => [], 'importedAt' => null];
        }

        $em = $this->getEntityManager();
        $concessions = (int) $em->createQuery(
            'SELECT COUNT(DISTINCT s.concessionId) FROM App\Entity\StockVehicle s'
        )->getSingleScalarResult();
        $dates = $em->createQuery(
            'SELECT DISTINCT s.dateStock FROM App\Entity\StockVehicle s WHERE s.dateStock IS NOT NULL ORDER BY s.dateStock ASC'
        )->getSingleColumnResult();
        $importedAt = $em->createQuery(
            'SELECT MAX(s.importedAt) FROM App\Entity\StockVehicle s'
        )->getSingleScalarResult();

        return [
            'count' => $count,
            'concessions' => $concessions,
            'dates' => array_values(array_map(self::toDate(...), $dates)),
            'importedAt' => null !== $importedAt ? self::toDate($importedAt) : null,
        ];
    }

    /** Les hydratations scalaires renvoient selon les cas un objet date ou une chaîne SQL. */
    private static function toDate(mixed $v): \DateTimeImmutable
    {
        if ($v instanceof \DateTimeImmutable) {
            return $v;
        }
        if ($v instanceof \DateTimeInterface) {
            return \DateTimeImmutable::createFromInterface($v);
        }

        return new \DateTimeImmutable((string) $v);
    }
}
