<?php

namespace App\Tests\Appointment;

use App\Appointment\Entity\Appointment;
use App\Auth\Entity\User;
use App\Clinic\Entity\Clinic;
use App\Doctor\Entity\Doctor;
use App\Patient\Entity\PatientRecord;
use App\Patient\Entity\PatientSession;
use App\Tests\ApiTestCase;

/**
 * «ثبت‌شده» → «قطعی»: POST /api/v1/appointment/{uuid}/confirm.
 *
 * یک عملِ اتمیک — وضعیت نوبت، پروندهٔ همان محیط با سرویس‌های نوبت، و پرداخت کامل یا
 * جزئی روی همان مراجعه.
 */
class AppointmentConfirmFlowTest extends ApiTestCase
{
    private function makeDoctor(string $name = 'دکتر تست'): Doctor
    {
        $user   = $this->createUser(['ROLE_USER', 'ROLE_DOCTOR']);
        $doctor = new Doctor($user, $name);
        $doctor->setMobileNumber($user->getMobileNumber());
        $this->em->persist($doctor);
        $this->em->flush();

        return $doctor;
    }

    /** @return array{0: User, 1: Clinic} */
    private function makeClinicWith(Doctor ...$doctors): array
    {
        $owner  = $this->createUser(['ROLE_USER', 'ROLE_CLINIC']);
        $clinic = new Clinic($owner);
        $clinic->setName('کلینیک تست');
        foreach ($doctors as $d) {
            $clinic->getDoctors()->add($d);
        }
        $this->em->persist($clinic);
        $this->em->flush();

        return [$owner, $clinic];
    }

    private function makeAppointment(
        Doctor $doctor,
        ?Clinic $clinic = null,
        ?User $patient = null,
        int $visitPriceRials = 5_000_000,
    ): Appointment {
        $patient ??= $this->createUser();
        // اسلات یکتا به‌ازای هر نوبت: db_test بین اجراها پاک نمی‌شود.
        $start = strtotime('+30 days') + random_int(0, 500_000) * 7;

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

        return $appointment;
    }

    private function reload(Appointment $a): Appointment
    {
        $this->em->clear();

        return $this->em->getRepository(Appointment::class)->find($a->getId());
    }

    // ── ساخت پنلی «ثبت‌شده» است، نه قطعی ─────────────────────────────────────

    public function testPanelBookingIsCreatedPending(): void
    {
        $doctor = $this->makeDoctor();
        $start  = strtotime('+40 days') + random_int(0, 500_000) * 7;

        $res = $this->authJson('POST', '/api/v1/my/appointment', $doctor->getUser(), [
            'doctor_uuid'           => $doctor->getUuid(),
            'slot_start'            => $start,
            'slot_end'              => $start + 1_800,
            'patient_mobile'        => '09' . str_pad((string) random_int(0, 999_999_999), 9, '0', STR_PAD_LEFT),
            'patient_name'          => 'بیمار تست',
            'patient_national_code' => '00' . str_pad((string) random_int(0, 99_999_999), 8, '0', STR_PAD_LEFT),
        ]);

        self::assertSame(201, $this->responseCode());
        self::assertSame(Appointment::STATUS_PENDING, $res['data']['status']);
    }

    // ── قطعی‌کردن: بدون پرداخت / جزئی / کامل ─────────────────────────────────

    public function testConfirmWithoutPaymentMovesToConfirmedAndOpensSession(): void
    {
        $doctor      = $this->makeDoctor();
        $appointment = $this->makeAppointment($doctor);

        $res = $this->authJson('POST', "/api/v1/appointment/{$appointment->getUuid()}/confirm", $doctor->getUser(), [
            'version' => $appointment->getVersion(),
        ]);

        self::assertSame(200, $this->responseCode());
        self::assertSame(Appointment::STATUS_CONFIRMED, $res['data']['appointment']['status']);
        self::assertSame(5_000_000, $res['data']['session']['final_price_rials']);
        self::assertSame(0, $res['data']['session']['paid_total_rials']);
        self::assertSame(5_000_000, $res['data']['session']['remaining_rials']);
        self::assertFalse($res['data']['session']['is_paid']);
    }

    public function testConfirmWithPartialPaymentLeavesRemainder(): void
    {
        $doctor      = $this->makeDoctor();
        $appointment = $this->makeAppointment($doctor);

        $res = $this->authJson('POST', "/api/v1/appointment/{$appointment->getUuid()}/confirm", $doctor->getUser(), [
            'version'  => $appointment->getVersion(),
            'payments' => [['method' => 'cash', 'amount_rials' => 2_000_000]],
        ]);

        self::assertSame(200, $this->responseCode());
        self::assertSame(2_000_000, $res['data']['session']['paid_total_rials']);
        self::assertSame(3_000_000, $res['data']['session']['remaining_rials']);
        self::assertFalse($res['data']['session']['is_paid']);
    }

    public function testConfirmWithFullPaymentSettlesSession(): void
    {
        $doctor      = $this->makeDoctor();
        $appointment = $this->makeAppointment($doctor);

        $res = $this->authJson('POST', "/api/v1/appointment/{$appointment->getUuid()}/confirm", $doctor->getUser(), [
            'version'  => $appointment->getVersion(),
            'payments' => [['method' => 'pos', 'amount_rials' => 5_000_000]],
        ]);

        self::assertSame(200, $this->responseCode());
        self::assertSame(0, $res['data']['session']['remaining_rials']);
        self::assertTrue($res['data']['session']['is_paid']);
    }

    public function testConfirmAcceptsSeveralPaymentRows(): void
    {
        $doctor      = $this->makeDoctor();
        $appointment = $this->makeAppointment($doctor);

        $res = $this->authJson('POST', "/api/v1/appointment/{$appointment->getUuid()}/confirm", $doctor->getUser(), [
            'version'  => $appointment->getVersion(),
            'payments' => [
                ['method' => 'cash', 'amount_rials' => 1_000_000],
                ['method' => 'pos',  'amount_rials' => 4_000_000],
            ],
        ]);

        self::assertSame(200, $this->responseCode());
        self::assertSame(5_000_000, $res['data']['session']['paid_total_rials']);
        self::assertTrue($res['data']['session']['is_paid']);
    }

    public function testConfirmPersistsPerPaymentMethodDetails(): void
    {
        $doctor      = $this->makeDoctor();
        $appointment = $this->makeAppointment($doctor);

        $res = $this->authJson('POST', "/api/v1/appointment/{$appointment->getUuid()}/confirm", $doctor->getUser(), [
            'version'  => $appointment->getVersion(),
            'payments' => [
                ['method' => 'pos', 'amount_rials' => 3_000_000, 'payment_method_uuid' => 'pos-uuid-1', 'reference' => 'TRX-42'],
                ['method' => 'cash', 'amount_rials' => 2_000_000],
            ],
        ]);
        self::assertSame(200, $this->responseCode());

        $session  = $this->em->getRepository(PatientSession::class)->findOneBy(['uuid' => $res['data']['session']['uuid']]);
        $payments = $this->em->getRepository(\App\Patient\Entity\SessionPayment::class)->findBy(['session' => $session]);
        self::assertCount(2, $payments);

        $pos  = array_values(array_filter($payments, fn($p) => $p->getMethod() === 'pos'))[0];
        $cash = array_values(array_filter($payments, fn($p) => $p->getMethod() === 'cash'))[0];

        self::assertSame('pos-uuid-1', $pos->getPaymentMethodUuid());
        self::assertSame('TRX-42', $pos->getReference());
        self::assertNull($cash->getPaymentMethodUuid(), 'روش نقدی جزئیاتِ روش ندارد');
        self::assertNull($cash->getReference());
    }

    // ── پرونده: استفادهٔ مجدد یا ساخت ────────────────────────────────────────

    public function testConfirmReusesExistingRecordOfSameDoctor(): void
    {
        $doctor  = $this->makeDoctor();
        $patient = $this->createUser();

        $first = $this->makeAppointment($doctor, null, $patient);
        $this->authJson('POST', "/api/v1/appointment/{$first->getUuid()}/confirm", $doctor->getUser(), [
            'version' => $first->getVersion(),
        ]);
        self::assertSame(200, $this->responseCode());

        $second = $this->makeAppointment($doctor, null, $patient);
        $this->authJson('POST', "/api/v1/appointment/{$second->getUuid()}/confirm", $doctor->getUser(), [
            'version' => $second->getVersion(),
        ]);
        self::assertSame(200, $this->responseCode());

        $records = $this->em->getRepository(PatientRecord::class)->findBy([
            'entityType' => 'doctor',
            'entityId'   => $doctor->getId(),
            'user'       => $patient,
        ]);

        self::assertCount(1, $records, 'پروندهٔ همان پزشک دوباره ساخته نمی‌شود');

        $sessions = $this->em->getRepository(PatientSession::class)->findBy(['record' => $records[0]]);
        self::assertCount(2, $sessions, 'هر نوبت مراجعهٔ خودش را دارد');
    }

    public function testConfirmInClinicFilesUnderClinicRecord(): void
    {
        $doctor           = $this->makeDoctor();
        [$owner, $clinic] = $this->makeClinicWith($doctor);
        $patient          = $this->createUser();
        $appointment      = $this->makeAppointment($doctor, $clinic, $patient);

        $this->authJson('POST', "/api/v1/appointment/{$appointment->getUuid()}/confirm", $owner, [
            'version' => $appointment->getVersion(),
        ]);
        self::assertSame(200, $this->responseCode());

        $clinicRecord = $this->em->getRepository(PatientRecord::class)->findOneBy([
            'entityType' => 'clinic',
            'entityId'   => $clinic->getId(),
            'user'       => $patient,
        ]);
        $doctorRecord = $this->em->getRepository(PatientRecord::class)->findOneBy([
            'entityType' => 'doctor',
            'entityId'   => $doctor->getId(),
            'user'       => $patient,
        ]);

        self::assertNotNull($clinicRecord, 'نوبت کلینیکی در پروندهٔ کلینیک می‌نشیند');
        self::assertNull($doctorRecord, 'و در مطب شخصی پزشک پروندهٔ موازی نمی‌سازد');
    }

    // ── خطاها ────────────────────────────────────────────────────────────────

    public function testPaymentAboveTotalIsRejectedAndNothingIsCommitted(): void
    {
        $doctor      = $this->makeDoctor();
        $appointment = $this->makeAppointment($doctor);

        $this->authJson('POST', "/api/v1/appointment/{$appointment->getUuid()}/confirm", $doctor->getUser(), [
            'version'  => $appointment->getVersion(),
            'payments' => [['method' => 'cash', 'amount_rials' => 9_000_000]],
        ]);

        self::assertSame(422, $this->responseCode());
        self::assertSame(Appointment::STATUS_PENDING, $this->reload($appointment)->getStatus(), 'تراکنش برگشته');
    }

    public function testUnknownPaymentMethodIs422(): void
    {
        $doctor      = $this->makeDoctor();
        $appointment = $this->makeAppointment($doctor);

        $this->authJson('POST', "/api/v1/appointment/{$appointment->getUuid()}/confirm", $doctor->getUser(), [
            'version'  => $appointment->getVersion(),
            'payments' => [['method' => 'bitcoin', 'amount_rials' => 1_000]],
        ]);

        self::assertSame(422, $this->responseCode());
        self::assertSame(Appointment::STATUS_PENDING, $this->reload($appointment)->getStatus());
    }

    public function testStaleVersionIs409(): void
    {
        $doctor      = $this->makeDoctor();
        $appointment = $this->makeAppointment($doctor);

        $this->authJson('POST', "/api/v1/appointment/{$appointment->getUuid()}/confirm", $doctor->getUser(), [
            'version' => $appointment->getVersion() + 5,
        ]);

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

    public function testConfirmingAnAlreadyConfirmedAppointmentIs422(): void
    {
        $doctor      = $this->makeDoctor();
        $appointment = $this->makeAppointment($doctor);

        $this->authJson('POST', "/api/v1/appointment/{$appointment->getUuid()}/confirm", $doctor->getUser(), [
            'version' => $appointment->getVersion(),
        ]);
        self::assertSame(200, $this->responseCode());

        $fresh = $this->reload($appointment);
        $this->authJson('POST', "/api/v1/appointment/{$fresh->getUuid()}/confirm", $doctor->getUser(), [
            'version' => $fresh->getVersion(),
        ]);

        self::assertSame(422, $this->responseCode(), 'confirmed → confirmed گذار مجاز نیست');
    }

    public function testStrangerCannotConfirm(): void
    {
        $doctor      = $this->makeDoctor();
        $appointment = $this->makeAppointment($doctor);

        $this->authJson('POST', "/api/v1/appointment/{$appointment->getUuid()}/confirm", $this->createUser(), [
            'version' => $appointment->getVersion(),
        ]);

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

    public function testDetailExposesServicePricesForTheModal(): void
    {
        $doctor      = $this->makeDoctor();
        $appointment = $this->makeAppointment($doctor);

        $res = $this->authJson('GET', "/api/v1/appointment/{$appointment->getUuid()}", $doctor->getUser());

        self::assertSame(200, $this->responseCode());
        self::assertSame(5_000_000, $res['data']['data']['visit_price_rials']);
        self::assertArrayHasKey('service_items', $res['data']['data']);
    }
}
