<?php

/*
 * This file is part of the Serenity package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CpCreation\VitiCore\Cart\Repository;

use CpCreation\VitiCore\Cart\Model\Invoice;
use CpCreation\VitiCore\Repository\BaseRepository;
use Doctrine\Persistence\ManagerRegistry;
use Knp\Component\Pager\Pagination\PaginationInterface;

class InvoiceRepository extends BaseRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Invoice::class);
    }

    public function findLastCredit(): ?Invoice
    {
        [$startOfYear, $startOfNextYear] = $this->createYearBoundaries();

        $qb = $this->createQueryBuilder('c')
            ->setMaxResults(1)
            ->andWhere('c.numberCredit IS NOT NULL')
            ->andWhere('c.credit = :credit')
            ->andWhere('c.creditAt >= :startOfYear')
            ->andWhere('c.creditAt < :startOfNextYear')
            ->orderBy('c.numberCredit', 'DESC')
            ->setParameter('credit', true)
            ->setParameter('startOfYear', $startOfYear)
            ->setParameter('startOfNextYear', $startOfNextYear);

        return $qb->getQuery()->getOneOrNullResult();
    }

    public function findLastDebit(): ?Invoice
    {
        $qb = $this->createQueryBuilder('c')
            ->setMaxResults(1)
            ->andWhere('c.numberDebit IS NOT NULL')
            ->andWhere('c.year = :year')
            ->orderBy('c.numberDebit', 'DESC')
            ->setParameter('year', $this->getCurrentYear());

        return $qb->getQuery()->getOneOrNullResult();
    }

    public function getNextCreditNumber(): int
    {
        return ($this->findLastCredit()?->getNumberCredit() ?? 0) + 1;
    }

    public function isLatestCredit(Invoice $invoice): bool
    {
        if (!$invoice->isCredit() || null === $invoice->getNumberCredit()) {
            return false;
        }

        $creditAt = $invoice->getCreditAt();
        [$startOfYear, $startOfNextYear] = $this->createYearBoundaries(
            null !== $creditAt ? (int) $creditAt->format('Y') : null
        );
        $lastCredit = $this->createQueryBuilder('c')
            ->setMaxResults(1)
            ->andWhere('c.numberCredit IS NOT NULL')
            ->andWhere('c.credit = :credit')
            ->andWhere('c.creditAt >= :startOfYear')
            ->andWhere('c.creditAt < :startOfNextYear')
            ->orderBy('c.numberCredit', 'DESC')
            ->setParameter('credit', true)
            ->setParameter('startOfYear', $startOfYear)
            ->setParameter('startOfNextYear', $startOfNextYear)
            ->getQuery()
            ->getOneOrNullResult();

        return null !== $lastCredit && $lastCredit->getId()->equals($invoice->getId());
    }

    public function getNextDebitNumber(): int
    {
        return ($this->findLastDebit()?->getNumberDebit() ?? 0) + 1;
    }

    /**
     * @return list<Invoice>
     */
    public function findAdminFiltered(
        ?string $search = null,
        string $type = 'all',
        ?\DateTimeImmutable $dateFrom = null,
        ?\DateTimeImmutable $dateTo = null,
    ): array {
        $qb = $this->createQueryBuilder('invoice')
            ->leftJoin('invoice.cart', 'cart')
            ->addSelect('cart')
            ->leftJoin('cart.cartCheckout', 'checkout')
            ->addSelect('checkout')
            ->leftJoin('cart.invoiceAddress', 'invoice_address')
            ->addSelect('invoice_address')
            ->leftJoin('cart.deliveryAddress', 'delivery_address')
            ->addSelect('delivery_address')
            ->leftJoin('cart.user', 'user')
            ->addSelect('user')
            ->orderBy('invoice.updatedAt', 'DESC')
            ->addOrderBy('invoice.createdAt', 'DESC');

        if ('credit' === $type) {
            $qb->andWhere('invoice.credit = TRUE');
        }

        if (null !== $search) {
            $searchTerm = '%'.mb_strtolower($search).'%';
            $matches = $qb->expr()->orX(
                'LOWER(COALESCE(invoice_address.company, \'\')) LIKE :searchTerm',
                'LOWER(COALESCE(invoice_address.firstname, \'\')) LIKE :searchTerm',
                'LOWER(COALESCE(invoice_address.lastname, \'\')) LIKE :searchTerm',
                'LOWER(COALESCE(invoice_address.email, \'\')) LIKE :searchTerm',
                'LOWER(COALESCE(delivery_address.company, \'\')) LIKE :searchTerm',
                'LOWER(COALESCE(delivery_address.firstname, \'\')) LIKE :searchTerm',
                'LOWER(COALESCE(delivery_address.lastname, \'\')) LIKE :searchTerm',
                'LOWER(COALESCE(delivery_address.email, \'\')) LIKE :searchTerm',
                'LOWER(COALESCE(user.email, \'\')) LIKE :searchTerm',
                'LOWER(COALESCE(cart.email, \'\')) LIKE :searchTerm',
                'LOWER(COALESCE(cart.token, \'\')) LIKE :searchTerm'
            );

            $parsedNumber = $this->parseInvoiceNumberSearch($search);
            if (null !== $parsedNumber) {
                $numberMatch = '(invoice.numberDebit = :invoiceNumber OR invoice.numberCredit = :invoiceNumber)';
                if (null !== $parsedNumber['year']) {
                    $numberMatch = '('.$numberMatch.' AND invoice.year = :invoiceYear)';
                    $qb->setParameter('invoiceYear', $parsedNumber['year']);
                }
                $matches->add($numberMatch);
                $qb->setParameter('invoiceNumber', $parsedNumber['number']);
            }

            $qb
                ->andWhere($matches)
                ->setParameter('searchTerm', $searchTerm);
        }

        if (null !== $dateFrom) {
            $qb
                ->andWhere('COALESCE(invoice.creditAt, checkout.payedAt, invoice.createdAt) >= :dateFrom')
                ->setParameter('dateFrom', $dateFrom);
        }

        if (null !== $dateTo) {
            $qb
                ->andWhere('COALESCE(invoice.creditAt, checkout.payedAt, invoice.createdAt) < :dateTo')
                ->setParameter('dateTo', $dateTo);
        }

        return $qb->getQuery()->getResult();
    }

    public function findCartsPagination(int $page, int $limit = 5): PaginationInterface
    {
        $qb = $this
            ->createQueryBuilder('i')
            ->orderBy('i.createdAt', 'DESC');

        return $this->pagination($qb->getQuery(), $page, $limit);
    }

    /**
     * @return array{0: \DateTimeImmutable, 1: \DateTimeImmutable}
     */
    private function createYearBoundaries(?int $year = null): array
    {
        $resolvedYear = $year ?? $this->getCurrentYear();
        $startOfYear = new \DateTimeImmutable(sprintf('%d-01-01 00:00:00', $resolvedYear));

        return [$startOfYear, $startOfYear->modify('+1 year')];
    }

    private function getCurrentYear(): int
    {
        return (int) (new \DateTimeImmutable())->format('Y');
    }

    /**
     * @return array{year: ?int, number: int}|null
     */
    private function parseInvoiceNumberSearch(string $value): ?array
    {
        $normalized = mb_strtoupper(mb_trim($value));
        $normalized = preg_replace('/[-\\s\\/]+/u', '-', $normalized) ?? $normalized;
        $normalized = trim($normalized, '-');

        if ('' === $normalized) {
            return null;
        }

        $segments = array_values(array_filter(
            explode('-', $normalized),
            static fn (string $segment): bool => '' !== $segment
        ));
        $numericSegments = array_values(array_filter(
            $segments,
            static fn (string $segment): bool => ctype_digit($segment)
        ));

        if ([] === $numericSegments) {
            return null;
        }

        $number = (int) end($numericSegments);
        if ($number <= 0) {
            return null;
        }

        $year = null;
        if (\count($numericSegments) >= 2) {
            $yearCandidate = $numericSegments[\count($numericSegments) - 2];

            if (4 === mb_strlen($yearCandidate)) {
                $year = (int) $yearCandidate;
            }
        }

        return [
            'year' => $year,
            'number' => $number,
        ];
    }
}
