<?php

declare(strict_types=1);

namespace App\Framework\Database\Repository;

use App\Framework\Database\EntityManager;

/**
 * Service für Batch Loading Operationen (Komposition statt Vererbung)
 */
final readonly class BatchLoader
{
    public function __construct(
        private EntityManager $entityManager
    ) {
    }

    /**
     * Findet Entities mit Batch Loading von Relations (verhindert N+1 Queries)
     *
     * @param string $entityClass
     * @param array<string, mixed> $criteria
     * @param array<string> $relations Relations die vorgeladen werden sollen
     * @param array<string, string>|null $orderBy
     * @param int|null $limit
     * @return array<int, object>
     */
    public function findWithRelations(
        string $entityClass,
        array $criteria = [],
        array $relations = [],
        ?array $orderBy = null,
        ?int $limit = null
    ): array {
        return $this->entityManager->findWithRelations(
            $entityClass,
            $criteria,
            $relations,
            $orderBy,
            $limit
        );
    }

    /**
     * Findet paginierte Entities mit optionalem Batch Loading
     *
     * @param string $entityClass
     * @param int $page Aktuelle Seite (1-basiert)
     * @param int $limit Items pro Seite
     * @param array<string, mixed> $criteria
     * @param array<string> $relations Relations die vorgeladen werden sollen
     * @param array<string, string>|null $orderBy
     * @return PaginatedResult
     */
    public function findPaginated(
        string $entityClass,
        int $page = 1,
        int $limit = 20,
        array $criteria = [],
        array $relations = [],
        ?array $orderBy = null
    ): PaginatedResult {
        if ($page < 1) {
            $page = 1;
        }

        if ($limit < 1) {
            $limit = 20;
        }

        // Berechne Offset
        $offset = ($page - 1) * $limit;

        // Hole Total Count für Pagination Info
        $totalItems = $this->countBy($entityClass, $criteria);

        // Hole die Items mit Batch Loading wenn Relations angegeben
        if (! empty($relations)) {
            // Verwende findWithRelations für Batch Loading
            $items = $this->entityManager->findWithRelations(
                $entityClass,
                $criteria,
                $relations,
                $orderBy,
                $limit
            );

            // EntityManager unterstützt kein Offset in findWithRelations,
            // daher müssen wir manuell slicen (TODO: Optimierung auf DB-Ebene)
            if ($offset > 0) {
                $allItems = $this->entityManager->findWithRelations(
                    $entityClass,
                    $criteria,
                    $relations,
                    $orderBy,
                    null
                );
                $items = array_slice($allItems, $offset, $limit);
            }
        } else {
            // Verwende normales findBy mit Limit/Offset
            $items = $this->entityManager->findBy($entityClass, $criteria, $orderBy, $limit);

            // Auch hier müssen wir manuell slicen
            if ($offset > 0) {
                $allItems = $this->entityManager->findBy($entityClass, $criteria, $orderBy);
                $items = array_slice($allItems, $offset, $limit);
            }
        }

        return PaginatedResult::fromQuery(
            items: $items,
            totalItems: $totalItems,
            page: $page,
            limit: $limit
        );
    }

    /**
     * Zählt Entities nach Kriterien
     *
     * @param string $entityClass
     * @param array<string, mixed> $criteria
     * @return int
     */
    public function countBy(string $entityClass, array $criteria = []): int
    {
        // TODO: Optimierung - sollte COUNT Query verwenden statt alle zu laden
        $items = $this->entityManager->findBy($entityClass, $criteria);

        return count($items);
    }

    /**
     * Batch-Save für mehrere Entities mit optimierter Performance
     *
     * @param array<int, object> $entities
     * @param int $batchSize Anzahl Entities pro Batch
     * @return array<int, object>
     */
    public function saveBatch(array $entities, int $batchSize = 100): array
    {
        $saved = [];

        // Verwende Transaktion für bessere Performance
        $this->entityManager->transaction(function () use ($entities, $batchSize, &$saved) {
            foreach (array_chunk($entities, $batchSize) as $batch) {
                $saved = array_merge($saved, $this->entityManager->saveAll(...$batch));
            }
        });

        return $saved;
    }

    /**
     * Batch-Delete für mehrere Entities
     *
     * @param array<int, object> $entities
     * @param int $batchSize Anzahl Entities pro Batch
     */
    public function deleteBatch(array $entities, int $batchSize = 100): void
    {
        $this->entityManager->transaction(function () use ($entities, $batchSize) {
            foreach (array_chunk($entities, $batchSize) as $batch) {
                foreach ($batch as $entity) {
                    $this->entityManager->delete($entity);
                }
            }
        });
    }
}
