<?php

namespace App\Tests\Payment;

use App\Clinic\Entity\Clinic;
use App\Doctor\Entity\Doctor;
use App\Payment\Entity\Payment;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Tenant\TenantFilter;
use App\Tests\ApiTestCase;
use Doctrine\ORM\EntityManagerInterface;

/**
 * پرداخت به محیطِ **گیرنده** تعلق دارد، نه به پرداخت‌کننده: نوبت → محیط همان نوبت،
 * اشتراک → محیطی که خریدار صاحبش است، شارژ پیامک → محیطِ همان کیف پول.
 *
 * بیمار در هیچ محیطی نیست، پس TenantFilter برایش خاموش می‌ماند و پرداخت خودش را
 * می‌بیند — همان دلیلی که فاز ۴ فیلتر را فقط روی «محیط انتخاب‌شده» روشن کرد.
 */
class PaymentTenantTest 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 makeDoctor(): Doctor
    {
        $doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر پرداخت');
        $this->em->persist($doctor);
        $this->em->flush();

        return $doctor;
    }

    private function makeClinic(): Clinic
    {
        $clinic = new Clinic($this->createUser(['ROLE_CLINIC']));
        $clinic->setName('کلینیک پرداخت');
        $this->em->persist($clinic);
        $this->em->flush();

        return $clinic;
    }

    /** پرداخت را از دیتابیس می‌خواند، نه از پاسخ — پاسخ محیط را برنمی‌گرداند. */
    private function storedPayment(string $uuid): Payment
    {
        $this->em->clear();
        $payment = $this->em->getRepository(Payment::class)->findOneBy(['uuid' => $uuid]);
        self::assertNotNull($payment, 'پرداخت باید ذخیره شده باشد');

        return $payment;
    }

    private function payForAppointment(?Clinic $clinic): Payment
    {
        $doctor  = $this->makeDoctor();
        $patient = $this->createUser(['ROLE_USER']);
        $start   = strtotime('+10 days') + random_int(0, 500_000) * 7;

        $appointment = $this->newAppointment($doctor, $patient, $start, $start + 900, $clinic);
        $this->em->persist($appointment);
        $this->em->flush();

        $res = $this->authJson('POST', '/api/v1/payment/appointment', $patient, [
            'appointment_uuid' => $appointment->getUuid(),
            'gateway'          => 'mellat',
        ]);
        self::assertSame(200, $this->responseCode(), 'شروع پرداخت نوبت باید موفق باشد');

        return $this->storedPayment($res['data']['payment_uuid']);
    }

    /** ✅ پرداخت نوبتِ یک کلینیک به همان کلینیک می‌نشیند. */
    public function testAppointmentPaymentBelongsToTheClinicOfTheAppointment(): void
    {
        $clinic  = $this->makeClinic();
        $payment = $this->payForAppointment($clinic);

        self::assertSame('clinic', $payment->getEntityType());
        self::assertSame($clinic->getId(), $payment->getEntityId());
    }

    /** ✅ نوبتِ مطب شخصی به خودِ پزشک. */
    public function testAppointmentPaymentOfAPersonalPracticeBelongsToTheDoctor(): void
    {
        $payment = $this->payForAppointment(null);

        self::assertSame('doctor', $payment->getEntityType());
    }

    /** ✅ اشتراک به محیطی که خریدار صاحبش است. */
    public function testSubscriptionPaymentBelongsToTheEnvironmentTheBuyerOwns(): void
    {
        $doctor = $this->makeDoctor();

        $res = $this->authJson('POST', '/api/v1/subscription-payment', $doctor->getUser(), [
            'gateway'      => 'mellat',
            'amount_rials' => 1_000_000,
        ]);
        self::assertSame(200, $this->responseCode());

        $payment = $this->storedPayment($res['data']['payment_uuid']);
        self::assertSame('doctor', $payment->getEntityType());
        self::assertSame($doctor->getId(), $payment->getEntityId());
    }

    /**
     * ❌ کاربری که نه پزشک است نه کلینیک، اشتراک برای هیچ محیطی نمی‌خرد. بدون این
     * گارد، ردیفی با محیطِ نامعتبر ساخته می‌شد یا flush بی‌پیام می‌شکست.
     */
    public function testSubscriptionPaymentWithoutAnOwnedEnvironmentIsRejected(): void
    {
        $res = $this->authJson('POST', '/api/v1/subscription-payment', $this->createUser(['ROLE_USER']), [
            'gateway'      => 'mellat',
            'amount_rials' => 1_000_000,
        ]);

        self::assertSame(422, $this->responseCode());
        self::assertSame(ErrorCodes::ERR_PAYMENT_004, $res['errors'][0]['code']);
    }

    /** ⚠️ مرزی: بیمار محیطی ندارد، پس فیلتر خاموش است و پرداخت خودش را می‌بیند. */
    public function testThePayingPatientStillSeesTheirOwnPayment(): void
    {
        $clinic  = $this->makeClinic();
        $payment = $this->payForAppointment($clinic);
        $patient = $payment->getUser();

        $res = $this->authJson('GET', '/api/v1/payment/' . $payment->getUuid(), $patient);

        self::assertSame(200, $this->responseCode(), 'بیمار باید پرداخت خودش را ببیند');
        self::assertSame($payment->getUuid(), $res['data']['uuid']);
    }

    /** ⚠️ محیط دیگر همان پرداخت را اصلاً نمی‌بیند — تور ایمنیِ TenantFilter. */
    public function testAnotherEnvironmentDoesNotSeeThePaymentAtAll(): void
    {
        $clinic  = $this->makeClinic();
        $payment = $this->payForAppointment($clinic);
        $uuid    = $payment->getUuid();

        $this->em->clear();
        $this->em()->getFilters()
            ->enable(TenantFilter::NAME)
            ->setParameter(TenantFilter::PARAM_TYPE, 'clinic', 'string')
            ->setParameter(TenantFilter::PARAM_ID, $clinic->getId() + 1000, 'integer');

        self::assertNull(
            $this->em->getRepository(Payment::class)->findOneBy(['uuid' => $uuid]),
            'پرداخت محیط دیگر نباید دیده شود',
        );
    }
}
