<?php

namespace App\Tests\Patient;

use App\Auth\Entity\User;
use App\Doctor\Entity\Doctor;
use App\Patient\Entity\PatientRecord;
use App\Settlement\Entity\WalletTransaction;
use App\Tests\ApiTestCase;

/**
 * کیف پول مالِ **شخص** است و موجودی‌اش سراسری می‌ماند — ولی دفترِ تراکنش را کلینیک
 * هم می‌بیند، و بدون تفکیک، کلینیک A می‌خواند که بیمار در کلینیک B چه پرداختی کرده
 * و چه کسی ثبتش کرده.
 *
 * پس انتساب per-تراکنش است، نه مالکیت per-کیف‌پول: `recorded_entity_*` عمداً نامی
 * دارد که TenantFilter نمی‌شناسد، وگرنه محاسبهٔ موجودی هم per-محیط می‌شد و پول
 * بیمار را نصف نشان می‌داد.
 */
class PatientWalletTenantTest extends ApiTestCase
{
    /** @return array{0: User, 1: Doctor} */
    private function makeDoctor(string $name): array
    {
        $owner  = $this->createUser(['ROLE_DOCTOR']);
        $doctor = new Doctor($owner, $name);
        $this->em->persist($doctor);
        $this->em->flush();

        return [$owner, $doctor];
    }

    private function recordFor(Doctor $doctor, User $patient): PatientRecord
    {
        $record = new PatientRecord('doctor', $doctor->getId(), $patient, 'doctor', $doctor->getId());
        $this->em->persist($record);
        $this->em->flush();

        return $record;
    }

    private function charge(User $owner, PatientRecord $record, int $amount, string $description): void
    {
        $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/wallet/charge', $owner, [
            'amount_rials' => $amount,
            'description'  => $description,
        ]);
        self::assertSame(201, $this->responseCode(), 'شارژ کیف پول باید موفق باشد');
    }

    /** @return array<int, string> توضیحِ تراکنش‌هایی که این محیط می‌بیند */
    private function ledger(User $owner, PatientRecord $record): array
    {
        $res = $this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/wallet/transactions', $owner);
        self::assertSame(200, $this->responseCode());

        return array_column($res['data'], 'description');
    }

    private function balance(User $owner, PatientRecord $record): int
    {
        $res = $this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/wallet', $owner);
        self::assertSame(200, $this->responseCode());

        return $res['data']['balance_rials'];
    }

    /**
     * یک بیمار، دو محیط: هر محیط پرونده و شارژ خودش را دارد.
     *
     * @return array{0: User, 1: PatientRecord, 2: User, 3: PatientRecord}
     */
    private function sharedPatientInTwoEnvironments(): array
    {
        $patient = $this->createUser(['ROLE_USER']);

        [$ownerA, $doctorA] = $this->makeDoctor('دکتر الف');
        [$ownerB, $doctorB] = $this->makeDoctor('دکتر ب');

        $recordA = $this->recordFor($doctorA, $patient);
        $recordB = $this->recordFor($doctorB, $patient);

        $this->charge($ownerA, $recordA, 300_000, 'شارژ نزد الف');
        $this->charge($ownerB, $recordB, 500_000, 'شارژ نزد ب');

        return [$ownerA, $recordA, $ownerB, $recordB];
    }

    /** ✅ هر محیط تراکنش خودش را می‌بیند. */
    public function testEachEnvironmentSeesItsOwnEntries(): void
    {
        [$ownerA, $recordA, $ownerB, $recordB] = $this->sharedPatientInTwoEnvironments();

        self::assertSame(['شارژ نزد الف'], $this->ledger($ownerA, $recordA));
        self::assertSame(['شارژ نزد ب'], $this->ledger($ownerB, $recordB));
    }

    /** ❌ تراکنشِ محیط دیگر — همان نشتی — دیگر در دفتر نمی‌آید. */
    public function testOneEnvironmentCannotReadWhatThePatientPaidInAnother(): void
    {
        [$ownerA, $recordA] = $this->sharedPatientInTwoEnvironments();

        self::assertNotContains('شارژ نزد ب', $this->ledger($ownerA, $recordA));
    }

    /**
     * ⚠️ مرزی: موجودی عمداً سراسری می‌ماند. پول مالِ بیمار است و اگر per-محیط
     * می‌شد، همان بیمار در هر مطب نصف پولش را می‌دید.
     */
    public function testTheBalanceStaysGlobalEvenThoughTheLedgerIsScoped(): void
    {
        [$ownerA, $recordA, $ownerB, $recordB] = $this->sharedPatientInTwoEnvironments();

        self::assertSame(800_000, $this->balance($ownerA, $recordA));
        self::assertSame(800_000, $this->balance($ownerB, $recordB));
    }

    /**
     * ⚠️ مرزی: ردیف بی‌محیط (ثبت‌شده پیش از این تفکیک) در همهٔ محیط‌ها دیده می‌شود.
     * پنهان‌کردنش تاریخچهٔ موجودِ یک بیمار را ناپدید می‌کرد.
     */
    public function testUnattributedLegacyRowsRemainVisibleEverywhere(): void
    {
        [$ownerA, $recordA, $ownerB, $recordB] = $this->sharedPatientInTwoEnvironments();

        // کاربر بعد از درخواست‌های API از EntityManager جدا شده؛ دوباره از همین EM.
        $patient = $this->em->find(User::class, $recordA->getUser()->getId());
        $legacy  = new WalletTransaction($patient, 100_000, WalletTransaction::TYPE_CREDIT, 900_000);
        $legacy->setDescription('شارژ قدیمی بدون محیط');
        $this->em->persist($legacy);
        $this->em->flush();

        self::assertContains('شارژ قدیمی بدون محیط', $this->ledger($ownerA, $recordA));
        self::assertContains('شارژ قدیمی بدون محیط', $this->ledger($ownerB, $recordB));
    }

    /** ❌ خلاصهٔ کیف پول هم همان قاعده را دارد؛ فقط موجودی سراسری می‌ماند. */
    public function testTheWalletSummaryOnlyShowsThisEnvironmentsRecentEntries(): void
    {
        [$ownerA, $recordA] = $this->sharedPatientInTwoEnvironments();

        $res = $this->authJson('GET', '/api/v1/patient/' . $recordA->getUuid() . '/wallet', $ownerA);
        self::assertSame(200, $this->responseCode());

        self::assertSame(800_000, $res['data']['balance_rials'], 'موجودی سراسری است');
        self::assertSame(
            ['شارژ نزد الف'],
            array_column($res['data']['recent_transactions'], 'description'),
        );
    }

    /** پرداختِ مراجعه از کیف پول، محیطش را از پروندهٔ همان مراجعه می‌گیرد. */
    public function testAVisitPaidFromTheWalletIsAttributedToTheRecordsEnvironment(): void
    {
        [$ownerA, $recordA, $ownerB, $recordB] = $this->sharedPatientInTwoEnvironments();

        $session = $this->authJson('POST', '/api/v1/patient/' . $recordA->getUuid() . '/session', $ownerA, [
            'visit_price_rials' => 200_000,
        ]);
        self::assertSame(201, $this->responseCode());

        $this->authJson('POST', '/api/v1/session/' . $session['data']['uuid'] . '/payments', $ownerA, [
            'amount_rials' => 200_000,
            'method'       => 'wallet',
        ]);
        self::assertSame(201, $this->responseCode(), 'پرداخت از کیف پول باید ثبت شود');

        self::assertNotSame([], array_filter(
            $this->ledger($ownerA, $recordA),
            static fn (string $d) => str_starts_with($d, 'پرداخت سرویس'),
        ));
        self::assertSame([], array_filter(
            $this->ledger($ownerB, $recordB),
            static fn (string $d) => str_starts_with($d, 'پرداخت سرویس'),
        ), 'محیط دیگر نباید پرداختِ مراجعهٔ این محیط را ببیند');
    }
}
