<?php

namespace App\Tests\Patient;

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

/**
 * تسویه‌ی چندتکه‌ی مراجعه: POST /session/{uuid}/payments پرداخت جزئی ثبت می‌کند
 * (روش wallet از کیف پول کسر می‌شود) و PATCH /session/{uuid} تخفیف تسویه
 * (percent/fixed/حذف) را اعمال می‌کند. صفر شدن مانده → is_paid و paid_at.
 */
class SessionPaymentTest extends ApiTestCase
{
    /** @return array{0: \App\Auth\Entity\User, 1: PatientRecord, 2: \App\Auth\Entity\User} */
    private function recordFor(): array
    {
        $owner  = $this->createUser(['ROLE_DOCTOR']);
        $doctor = new Doctor($owner, 'دکتر');
        $this->em->persist($doctor);
        $this->em->flush();

        $patient = $this->createUser(['ROLE_USER']);
        $record  = new PatientRecord('doctor', $doctor->getId(), $patient, 'doctor', $doctor->getId());
        $this->em->persist($record);
        $this->em->flush();

        return [$owner, $record, $patient];
    }

    private function sessionFor(PatientRecord $record, int $finalPriceRials): PatientSession
    {
        $session = new PatientSession($record);
        $session->setFinalPriceRials($finalPriceRials);
        $this->em->persist($session);
        $this->em->flush();

        return $session;
    }

    // ── پرداخت جزئی ─────────────────────────────────────────────────────────

    public function testPartialPaymentReducesDebtButNotPaid(): void
    {
        [$owner, $record] = $this->recordFor();
        $session = $this->sessionFor($record, 500_000);

        $res = $this->authJson('POST', '/api/v1/session/' . $session->getUuid() . '/payments', $owner, [
            'method' => 'cash', 'amount_rials' => 200_000,
        ]);
        self::assertSame(201, $this->responseCode());
        self::assertFalse($res['data']['is_paid']);
        self::assertSame(200_000, $res['data']['paid_total_rials']);
        self::assertSame(300_000, $res['data']['patient_debt_rials']);
        self::assertCount(1, $res['data']['payments']);
        self::assertSame('cash', $res['data']['payments'][0]['method']);
        self::assertSame($owner->getMobileNumber(), $res['data']['payments'][0]['created_by_name']);
    }

    public function testFullSettlementViaMultiplePaymentsMarksPaid(): void
    {
        [$owner, $record] = $this->recordFor();
        $session = $this->sessionFor($record, 500_000);

        $this->authJson('POST', '/api/v1/session/' . $session->getUuid() . '/payments', $owner, [
            'method' => 'cash', 'amount_rials' => 200_000,
        ]);
        $res = $this->authJson('POST', '/api/v1/session/' . $session->getUuid() . '/payments', $owner, [
            'method' => 'card', 'amount_rials' => 300_000, 'paid_at' => 1_800_000_000,
        ]);

        self::assertSame(201, $this->responseCode());
        self::assertTrue($res['data']['is_paid']);
        self::assertSame('card', $res['data']['payment_method']);
        self::assertSame(1_800_000_000, $res['data']['paid_at']);
        self::assertSame(0, $res['data']['patient_debt_rials']);
        self::assertCount(2, $res['data']['payments']);
    }

    public function testWalletPartialPaymentDebitsWallet(): void
    {
        [$owner, $record, $patient] = $this->recordFor();
        $this->em->persist(new WalletTransaction($patient, 1_000_000, 'credit', 1_000_000));
        $this->em->flush();
        $session = $this->sessionFor($record, 400_000);

        $res = $this->authJson('POST', '/api/v1/session/' . $session->getUuid() . '/payments', $owner, [
            'method' => 'wallet', 'amount_rials' => 150_000,
        ]);
        self::assertSame(201, $this->responseCode());
        self::assertSame(250_000, $res['data']['patient_debt_rials']);

        $wallet = $this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/wallet', $owner);
        self::assertSame(850_000, $wallet['data']['balance_rials']);
        $debit = $wallet['data']['recent_transactions'][0];
        self::assertSame('debit', $debit['type']);
        self::assertSame('session:' . $session->getUuid(), $debit['reference']);
    }

    public function testWalletPaymentRejectedWhenInsufficient(): void
    {
        [$owner, $record, $patient] = $this->recordFor();
        $this->em->persist(new WalletTransaction($patient, 100_000, 'credit', 100_000));
        $this->em->flush();
        $session = $this->sessionFor($record, 400_000);

        $res = $this->authJson('POST', '/api/v1/session/' . $session->getUuid() . '/payments', $owner, [
            'method' => 'wallet', 'amount_rials' => 200_000,
        ]);
        self::assertSame(422, $this->responseCode());
        self::assertSame('ERR_WALLET_INSUFFICIENT', $res['errors'][0]['code']);

        // هیچ پرداختی ثبت نشد
        $sessions = $this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/sessions', $owner);
        self::assertSame(400_000, $sessions['data'][0]['patient_debt_rials']);
        self::assertCount(0, $sessions['data'][0]['payments']);
    }

    public function testPaymentExceedingDebtRejected(): void
    {
        [$owner, $record] = $this->recordFor();
        $session = $this->sessionFor($record, 300_000);

        $res = $this->authJson('POST', '/api/v1/session/' . $session->getUuid() . '/payments', $owner, [
            'method' => 'cash', 'amount_rials' => 400_000,
        ]);
        self::assertSame(422, $this->responseCode());
        self::assertSame('ERR_SESSION_PAYMENT_EXCEEDS', $res['errors'][0]['code']);
    }

    public function testInvalidMethodRejected(): void
    {
        [$owner, $record] = $this->recordFor();
        $session = $this->sessionFor($record, 300_000);

        $res = $this->authJson('POST', '/api/v1/session/' . $session->getUuid() . '/payments', $owner, [
            'method' => 'bitcoin', 'amount_rials' => 100_000,
        ]);
        self::assertSame(422, $this->responseCode());
        self::assertSame('ERR_SESSION_PAYMENT_INVALID', $res['errors'][0]['code']);
    }

    public function testZeroAmountRejected(): void
    {
        [$owner, $record] = $this->recordFor();
        $session = $this->sessionFor($record, 300_000);

        $res = $this->authJson('POST', '/api/v1/session/' . $session->getUuid() . '/payments', $owner, [
            'method' => 'cash', 'amount_rials' => 0,
        ]);
        self::assertSame(422, $this->responseCode());
        self::assertSame('ERR_SESSION_PAYMENT_INVALID', $res['errors'][0]['code']);
    }

    public function testSessionNotFoundReturns404(): void
    {
        [$owner] = $this->recordFor();

        $this->authJson('POST', '/api/v1/session/00000000-0000-0000-0000-000000000000/payments', $owner, [
            'method' => 'cash', 'amount_rials' => 100_000,
        ]);
        self::assertSame(404, $this->responseCode());
    }

    // ── تخفیف تسویه ─────────────────────────────────────────────────────────

    public function testPercentDiscountReducesDebt(): void
    {
        [$owner, $record] = $this->recordFor();
        $session = $this->sessionFor($record, 400_000);

        $res = $this->authJson('PATCH', '/api/v1/session/' . $session->getUuid(), $owner, [
            'discount_type' => 'percent', 'discount_value' => 25,
        ]);
        self::assertSame(200, $this->responseCode());
        self::assertSame('percent', $res['data']['discount_type']);
        self::assertSame(100_000, $res['data']['discount_rials']);

        $sessions = $this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/sessions', $owner);
        self::assertSame(300_000, $sessions['data'][0]['patient_debt_rials']);
    }

    public function testFixedDiscountThenRemove(): void
    {
        [$owner, $record] = $this->recordFor();
        $session = $this->sessionFor($record, 400_000);

        $res = $this->authJson('PATCH', '/api/v1/session/' . $session->getUuid(), $owner, [
            'discount_type' => 'fixed', 'discount_value' => 150_000,
        ]);
        self::assertSame(150_000, $res['data']['discount_rials']);

        // حذف تخفیف
        $res = $this->authJson('PATCH', '/api/v1/session/' . $session->getUuid(), $owner, [
            'discount_type' => null,
        ]);
        self::assertSame(200, $this->responseCode());
        self::assertNull($res['data']['discount_type']);
        self::assertSame(0, $res['data']['discount_rials']);
    }

    public function testDiscountOverLimitsRejected(): void
    {
        [$owner, $record] = $this->recordFor();
        $session = $this->sessionFor($record, 400_000);

        $res = $this->authJson('PATCH', '/api/v1/session/' . $session->getUuid(), $owner, [
            'discount_type' => 'percent', 'discount_value' => 150,
        ]);
        self::assertSame(422, $this->responseCode());
        self::assertSame('ERR_SESSION_DISCOUNT_INVALID', $res['errors'][0]['code']);

        $res = $this->authJson('PATCH', '/api/v1/session/' . $session->getUuid(), $owner, [
            'discount_type' => 'fixed', 'discount_value' => 500_000,
        ]);
        self::assertSame(422, $this->responseCode());
        self::assertSame('ERR_SESSION_DISCOUNT_INVALID', $res['errors'][0]['code']);
    }

    public function testFullPercentDiscountMarksPaid(): void
    {
        [$owner, $record] = $this->recordFor();
        $session = $this->sessionFor($record, 400_000);

        $res = $this->authJson('PATCH', '/api/v1/session/' . $session->getUuid(), $owner, [
            'discount_type' => 'percent', 'discount_value' => 100,
        ]);
        self::assertSame(200, $this->responseCode());
        self::assertTrue($res['data']['is_paid']);
        self::assertNotNull($res['data']['paid_at']);
    }

    public function testDiscountPlusPaymentSettles(): void
    {
        [$owner, $record] = $this->recordFor();
        $session = $this->sessionFor($record, 400_000);

        $this->authJson('PATCH', '/api/v1/session/' . $session->getUuid(), $owner, [
            'discount_type' => 'fixed', 'discount_value' => 100_000,
        ]);
        $res = $this->authJson('POST', '/api/v1/session/' . $session->getUuid() . '/payments', $owner, [
            'method' => 'pos', 'amount_rials' => 300_000,
        ]);
        self::assertSame(201, $this->responseCode());
        self::assertTrue($res['data']['is_paid']);
        self::assertSame('pos', $res['data']['payment_method']);
        self::assertSame(0, $res['data']['patient_debt_rials']);
    }

    public function testPaymentOnSettledSessionRejected(): void
    {
        [$owner, $record] = $this->recordFor();
        $session = $this->sessionFor($record, 200_000);

        $this->authJson('POST', '/api/v1/session/' . $session->getUuid() . '/payments', $owner, [
            'method' => 'cash', 'amount_rials' => 200_000,
        ]);
        self::assertSame(201, $this->responseCode());

        // مراجعه تسویه شده — هر پرداخت بعدی از مانده (صفر) بیشتر است
        $res = $this->authJson('POST', '/api/v1/session/' . $session->getUuid() . '/payments', $owner, [
            'method' => 'cash', 'amount_rials' => 1,
        ]);
        self::assertSame(422, $this->responseCode());
        self::assertSame('ERR_SESSION_PAYMENT_EXCEEDS', $res['errors'][0]['code']);
    }
}
