<?php

namespace App\PaymentMethod\Repository;

use App\Auth\Entity\User;
use App\PaymentMethod\Entity\BankAccount;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

class BankAccountRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, BankAccount::class);
    }

    public function findByUuid(string $uuid): ?BankAccount
    {
        return $this->findOneBy(['uuid' => $uuid]);
    }

    /** @return BankAccount[] */
    public function findByEntity(string $entityType, int $entityId): array
    {
        return $this->createQueryBuilder('b')
            ->where('b.entityType = :type')->setParameter('type', $entityType)
            ->andWhere('b.entityId = :id')->setParameter('id', $entityId)
            ->orderBy('b.createdAt', 'DESC')
            ->getQuery()
            ->getResult();
    }

    /**
     * حساب‌های بی‌محیطِ همین کاربر — بازمانده‌های پیش از فاز ۶ که مالکشان چند محیط
     * داشت و انتسابشان حدس می‌خواست.
     *
     * عمداً DBAL است نه DQL: TenantFilter شرط تساوی می‌گذارد و NULL با هیچ مقداری
     * برابر نیست، پس این ردیف‌ها از DQL هرگز برنمی‌گردند. به‌جای فیلتر، محدودیت
     * مالکیت همین‌جا با user_id گذاشته شده.
     *
     * شکل خروجی باید با {@see BankAccount::toArray()} یکی بماند؛
     * PaymentMethodTenantTest همین را می‌سنجد.
     *
     * @return array<int, array<string, mixed>>
     */
    public function findUnassignedByUser(User $user): array
    {
        $rows = $this->getEntityManager()->getConnection()->fetchAllAssociative(
            'SELECT uuid, bank_name, card_number, account_number, shaba_number, is_active, created_at
             FROM bank_accounts
             WHERE user_id = ? AND (entity_type IS NULL OR entity_id IS NULL)
             ORDER BY created_at DESC',
            [$user->getId()],
        );

        return array_map(static fn (array $r) => [
            'uuid'           => $r['uuid'],
            'bank_name'      => $r['bank_name'],
            'card_number'    => $r['card_number'],
            'account_number' => $r['account_number'],
            'shaba_number'   => $r['shaba_number'],
            'is_active'      => (bool) $r['is_active'],
            'created_at'     => (int) $r['created_at'],
            'entity_type'    => null,
        ], $rows);
    }

    /**
     * انتساب یک حساب بی‌محیط به یک محیط. شرط‌های مالکیت و بی‌محیط بودن داخل خودِ
     * UPDATE‌اند تا دو درخواست هم‌زمان نتوانند یک حساب را به دو محیط بچسبانند.
     *
     * @return bool چیزی انتساب یافت؟ false یعنی یافت نشد یا از قبل محیط داشت.
     */
    public function assignEntity(string $uuid, User $user, string $entityType, int $entityId): bool
    {
        return $this->getEntityManager()->getConnection()->executeStatement(
            'UPDATE bank_accounts SET entity_type = ?, entity_id = ?, updated_at = ?
             WHERE uuid = ? AND user_id = ? AND (entity_type IS NULL OR entity_id IS NULL)',
            [$entityType, $entityId, time(), $uuid, $user->getId()],
        ) > 0;
    }

    /**
     * پس از نوشتنِ DBAL، نمونهٔ داخل identity map هنوز مقدار قدیمی را دارد و
     * findOneBy همان را برمی‌گرداند بی‌آنکه تازه‌اش کند. این متد صریحاً تازه می‌کند.
     */
    public function reloadByUuid(string $uuid): ?BankAccount
    {
        $account = $this->findByUuid($uuid);
        if ($account !== null) {
            $this->getEntityManager()->refresh($account);
        }

        return $account;
    }

    public function save(BankAccount $entity, bool $flush = true): void
    {
        $this->getEntityManager()->persist($entity);
        if ($flush) $this->getEntityManager()->flush();
    }
}
