<?php

namespace App\Tests\Settlement;

use App\Auth\Entity\User;
use App\Doctor\Entity\Doctor;
use App\Payment\Entity\Payment;
use App\Secretary\Entity\SecretaryEarning;
use App\Secretary\Repository\SecretaryEarningRepository;
use App\Settlement\Entity\FinancialBreakdown;
use App\Settlement\Entity\WalletTransaction;
use App\Settlement\Repository\WalletTransactionRepository;
use App\Shared\Tenant\TenantFilter;
use App\Tests\ApiTestCase;
use Doctrine\ORM\EntityManagerInterface;

/**
 * زنجیرهٔ مالی ستون محیط ندارد و آن را از `payments` به ارث می‌برد:
 *
 *     Payment ─┬─ PaymentLog (payment_id اسکالر)
 *              └─ FinancialBreakdown ── SecretaryEarning
 *
 * تضمین فقط تا جایی است که کوئری به ریشه لنگر بزند؛ `reportFor` این کار را با
 * join('b.payment','p') می‌کند و همان join است که TenantFilter رویش می‌نشیند.
 *
 * کیف پول عمداً بیرون این زنجیره است: مالِ شخص است نه محیط، و همین‌جا پین می‌شود
 * تا اگر روزی کسی ستون محیط رویش گذاشت، این تصمیم دوباره دیده شود.
 */
class FinancialChainTenantTest extends ApiTestCase
{
    private function em(): EntityManagerInterface
    {
        return static::getContainer()->get(EntityManagerInterface::class);
    }

    protected function tearDown(): void
    {
        $filters = $this->em()->getFilters();
        if ($filters->isEnabled(TenantFilter::NAME)) {
            $filters->disable(TenantFilter::NAME);
        }

        parent::tearDown();
    }

    private function enableFilterFor(string $type, int $id): void
    {
        $this->em()->getFilters()
            ->enable(TenantFilter::NAME)
            ->setParameter(TenantFilter::PARAM_TYPE, $type, 'string')
            ->setParameter(TenantFilter::PARAM_ID, $id, 'integer');
    }

    private function makeDoctor(): Doctor
    {
        $doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر زنجیره');
        $this->em->persist($doctor);
        $this->em->flush();

        return $doctor;
    }

    /** یک سهم منشی از پرداختِ محیطِ داده‌شده، با کل زنجیره‌اش. */
    private function earningFor(Doctor $doctor, User $secretary): SecretaryEarning
    {
        $payment = new Payment($this->createUser(), 1_000_000, 'mock', Payment::TYPE_APPOINTMENT);
        $payment->assignTenantPair('doctor', $doctor->getId());
        $this->em->persist($payment);

        $breakdown = new FinancialBreakdown(
            $payment, FinancialBreakdown::SOURCE_APPOINTMENT, $secretary,
            1_000_000, 0, '0.00', 0, 1_000_000, '10.00', 100_000, 900_000, null, null, null,
        );
        $this->em->persist($breakdown);

        $earning = new SecretaryEarning($breakdown, $secretary, 'rel-' . bin2hex(random_bytes(4)), 5.0, 50_000);
        $this->em->persist($earning);
        $this->em->flush();

        return $earning;
    }

    /** @return string[] */
    private function reportedUuids(User $secretary): array
    {
        $repo   = static::getContainer()->get(SecretaryEarningRepository::class);
        $report = $repo->reportFor($secretary, 1, 50);

        return array_column($report['items'] ?? [], 'uuid');
    }

    /** ✅ سهمِ محیط خودی از راه لنگر به پرداخت دیده می‌شود. */
    public function testAnEarningIsVisibleInsideItsOwnEnvironment(): void
    {
        $doctor    = $this->makeDoctor();
        $secretary = $this->createUser(['ROLE_SECRETARY']);
        $earning   = $this->earningFor($doctor, $secretary);

        $this->em->clear();
        $this->enableFilterFor('doctor', $doctor->getId());

        self::assertContains($earning->getUuid(), $this->reportedUuids($secretary));
    }

    /**
     * ❌ همان سهم در محیط دیگر ناپدید می‌شود — نه به‌خاطر شرط دستی، بلکه چون
     * کوئری به `payments` لنگر زده و فیلتر روی همان join نشسته است.
     */
    public function testTheSameEarningDisappearsInAnotherEnvironment(): void
    {
        $doctor    = $this->makeDoctor();
        $secretary = $this->createUser(['ROLE_SECRETARY']);
        $earning   = $this->earningFor($doctor, $secretary);

        $this->em->clear();
        $this->enableFilterFor('doctor', $doctor->getId() + 1000);

        self::assertNotContains($earning->getUuid(), $this->reportedUuids($secretary));
    }

    /**
     * ⚠️ مرزی: کیف پول شخص محیط ندارد و در هر محیطی دیده می‌شود. این نشتی نیست،
     * تصمیم است — موجودی از مجموع credit−debitِ همان کاربر مشتق می‌شود و تفکیکش
     * به محیط، خودِ موجودی را بی‌معنا می‌کند.
     */
    public function testTheUserWalletStaysGlobalAcrossEnvironments(): void
    {
        $patient = $this->createUser(['ROLE_USER']);
        $txn     = new WalletTransaction($patient, 500_000, WalletTransaction::TYPE_CREDIT, 500_000);
        $this->em->persist($txn);
        $this->em->flush();
        $uuid = $txn->getUuid();

        $this->em->clear();
        $this->enableFilterFor('clinic', 987_654);

        $repo = static::getContainer()->get(WalletTransactionRepository::class);
        self::assertNotNull(
            $repo->findOneBy(['uuid' => $uuid]),
            'کیف پول شخص نباید به محیط قفل شود',
        );
    }
}
