<?php

namespace App\Tests\PaymentMethod;

use App\Auth\Entity\User;
use App\Auth\Entity\UserActiveContext;
use App\Clinic\Entity\Clinic;
use App\Doctor\Entity\Doctor;
use App\PaymentMethod\Entity\BankAccount;
use App\PaymentMethod\Entity\Pos;
use App\Shared\Context\EntityContext;
use App\Tests\ApiTestCase;

/**
 * کارت و کارتخوان مالِ **محیط**اند نه کاربر. پزشکی که هم مطب شخصی دارد و هم
 * کلینیک، در هر محیط فقط کارت‌های همان محیط را می‌بیند.
 *
 * ردیف‌های بازمانده از پیش از فاز ۶ محیط تهی دارند — عمداً، چون هیچ ستونی نمی‌گفت
 * کارتِ کاربرِ چندمحیطی مال کدام محیط است و حدس زدنش یعنی پول به حساب اشتباه.
 * چنین ردیفی در هیچ محیطی «متعلق» نیست، ولی مالکش باید ببیندش و بتواند تعیینش کند.
 */
class PaymentMethodTenantTest extends ApiTestCase
{
    /** پزشکی که کلینیک هم دارد، با محیط فعالِ مشخص. */
    private function multiEnvironmentUser(): array
    {
        $user = $this->createUser(['ROLE_DOCTOR', 'ROLE_CLINIC']);

        $doctor = new Doctor($user, 'دکتر دو محیطه');
        $this->em->persist($doctor);

        $clinic = new Clinic($user);
        $clinic->setName('کلینیک همان شخص');
        $this->em->persist($clinic);
        $this->em->flush();

        return [$user, $doctor, $clinic];
    }

    private function switchTo(User $user, string $type, string $uuid): void
    {
        // هر درخواستِ API ممکن است EntityManager را پاک کند، پس کاربر دوباره از
        // همین EM گرفته می‌شود تا UserActiveContext به نمونهٔ جداشده وصل نشود.
        $managed  = $this->em->find(User::class, $user->getId());
        $existing = $this->em->getRepository(UserActiveContext::class)->findOneBy(['user' => $managed]);
        if ($existing !== null) {
            $this->em->remove($existing);
            $this->em->flush();
        }

        $this->em->persist(new UserActiveContext($managed, $uuid, $type));
        $this->em->flush();
    }

    private function createBankAccount(User $user, string $bankName): array
    {
        $res = $this->authJson('POST', '/api/v1/my/payment-methods/bank-accounts', $user, [
            'bank_name'      => $bankName,
            'account_number' => (string) random_int(1_000_000, 9_999_999),
        ]);
        self::assertSame(201, $this->responseCode());

        return $res['data'];
    }

    private function listBankAccounts(User $user): array
    {
        $res = $this->authJson('GET', '/api/v1/my/payment-methods/bank-accounts', $user);
        self::assertSame(200, $this->responseCode());

        return $res['data'];
    }

    /** ✅ همان شخص، دو محیط، دو دستهٔ جدا از کارت‌ها. */
    public function testTheSamePersonSeesDifferentCardsInEachEnvironment(): void
    {
        [$user, $doctor, $clinic] = $this->multiEnvironmentUser();

        $this->switchTo($user, EntityContext::TYPE_DOCTOR, $doctor->getUuid());
        $personal = $this->createBankAccount($user, 'کارت مطب');

        $this->switchTo($user, EntityContext::TYPE_CLINIC, $clinic->getUuid());
        $clinical = $this->createBankAccount($user, 'کارت کلینیک');

        self::assertSame(['کارت کلینیک'], array_column($this->listBankAccounts($user), 'bank_name'));

        $this->switchTo($user, EntityContext::TYPE_DOCTOR, $doctor->getUuid());
        self::assertSame(['کارت مطب'], array_column($this->listBankAccounts($user), 'bank_name'));

        self::assertNotSame($personal['uuid'], $clinical['uuid']);
    }

    /** ❌ کارتِ محیط دیگر حتی برای همان شخص قابل ویرایش نیست. */
    public function testACardOfTheOtherEnvironmentCannotBeEditedEvenByItsOwner(): void
    {
        [$user, $doctor, $clinic] = $this->multiEnvironmentUser();

        $this->switchTo($user, EntityContext::TYPE_DOCTOR, $doctor->getUuid());
        $personal = $this->createBankAccount($user, 'کارت مطب');

        $this->switchTo($user, EntityContext::TYPE_CLINIC, $clinic->getUuid());
        $this->authJson('PATCH', '/api/v1/my/payment-methods/bank-accounts/' . $personal['uuid'] . '/status', $user);

        self::assertSame(404, $this->responseCode());
    }

    /**
     * ⚠️ مرزی: کارتِ بی‌محیط (بازماندهٔ پیش از فاز ۶) در فهرست می‌آید با نشانهٔ
     * `entity_type: null`، ولی تا وقتی محیطش تعیین نشده قابل ویرایش نیست.
     */
    public function testAnUnassignedCardIsListedButNotEditable(): void
    {
        [$user, $doctor] = $this->multiEnvironmentUser();
        $this->switchTo($user, EntityContext::TYPE_DOCTOR, $doctor->getUuid());

        $orphan = new BankAccount($user, 'کارت بی‌محیط', '555000', '', '');
        $this->em->persist($orphan);
        $this->em->flush();

        $listed = $this->listBankAccounts($user);
        self::assertSame(['کارت بی‌محیط'], array_column($listed, 'bank_name'));
        self::assertNull($listed[0]['entity_type'], 'باید با نشانهٔ «محیط تعیین‌نشده» بیاید');

        $this->authJson('PATCH', '/api/v1/my/payment-methods/bank-accounts/' . $orphan->getUuid() . '/status', $user);
        self::assertSame(404, $this->responseCode(), 'تا تعیین محیط، ویرایش‌پذیر نیست');
    }

    /** ✅ انتساب، کارتِ بی‌محیط را به محیط فعال می‌چسباند و ویرایش‌پذیرش می‌کند. */
    public function testAssigningAnEnvironmentMakesTheCardUsable(): void
    {
        [$user, $doctor] = $this->multiEnvironmentUser();
        $this->switchTo($user, EntityContext::TYPE_DOCTOR, $doctor->getUuid());

        $orphan = new BankAccount($user, 'کارت بی‌محیط', '555000', '', '');
        $this->em->persist($orphan);
        $this->em->flush();
        $uuid = $orphan->getUuid();

        $res = $this->authJson('PATCH', "/api/v1/my/payment-methods/bank-accounts/$uuid/environment", $user);
        self::assertSame(200, $this->responseCode());
        self::assertSame('doctor', $res['data']['entity_type']);

        $this->authJson('PATCH', "/api/v1/my/payment-methods/bank-accounts/$uuid/status", $user);
        self::assertSame(200, $this->responseCode(), 'بعد از انتساب باید ویرایش‌پذیر باشد');
    }

    /** ❌ انتساب دوباره روی کارتی که محیط دارد، بی‌اثر است — نه ربودن کارت محیط دیگر. */
    public function testAssigningAnAlreadyAssignedCardIsRejected(): void
    {
        [$user, $doctor, $clinic] = $this->multiEnvironmentUser();

        $this->switchTo($user, EntityContext::TYPE_DOCTOR, $doctor->getUuid());
        $personal = $this->createBankAccount($user, 'کارت مطب');

        $this->switchTo($user, EntityContext::TYPE_CLINIC, $clinic->getUuid());
        $this->authJson(
            'PATCH',
            '/api/v1/my/payment-methods/bank-accounts/' . $personal['uuid'] . '/environment',
            $user,
        );

        self::assertSame(404, $this->responseCode());
    }

    /** ❌ کارتِ بی‌محیطِ شخص دیگر با انتساب هم به دست نمی‌آید. */
    public function testAnotherPersonsUnassignedCardCannotBeClaimed(): void
    {
        [$user, $doctor] = $this->multiEnvironmentUser();
        $this->switchTo($user, EntityContext::TYPE_DOCTOR, $doctor->getUuid());

        [$stranger] = $this->multiEnvironmentUser();
        $orphan     = new Pos($stranger, 'کارتخوان بیگانه', '900900');
        $this->em->persist($orphan);
        $this->em->flush();

        $this->authJson('PATCH', '/api/v1/my/payment-methods/pos/' . $orphan->getUuid() . '/environment', $user);

        self::assertSame(404, $this->responseCode());
    }

    /** شکل ردیفِ بی‌محیط باید دقیقاً همان شکل ردیفِ محیط‌دار باشد، وگرنه پنل می‌شکند. */
    public function testUnassignedRowsHaveTheSameShapeAsAssignedOnes(): void
    {
        [$user, $doctor] = $this->multiEnvironmentUser();
        $this->switchTo($user, EntityContext::TYPE_DOCTOR, $doctor->getUuid());

        $assigned = $this->createBankAccount($user, 'کارت محیط‌دار');

        $orphan = new BankAccount($user, 'کارت بی‌محیط', '555000', '', '');
        $this->em->persist($orphan);
        $this->em->flush();

        $rows = $this->listBankAccounts($user);
        self::assertCount(2, $rows);

        $keys = array_map(static fn (array $row) => array_keys($row), $rows);
        self::assertSame($keys[0], $keys[1], 'کلیدهای ردیف بی‌محیط با ردیف محیط‌دار یکی نیست');
        self::assertSame(array_keys($assigned), $keys[0]);
    }
}
