<?php

namespace App\Package\Controller;

use App\Auth\Entity\User;
use App\Branch\Service\BranchResolver;
use App\Package\Entity\PatientPackage;
use App\Package\Entity\SessionCreditLedger;
use App\Package\Repository\PackageRepository;
use App\Package\Repository\PatientPackageRepository;
use App\Package\Service\CreditLedgerService;
use App\Package\Service\PackageSalesService;
use App\Patient\Entity\PatientRecord;
use App\Patient\Repository\PatientRecordRepository;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
use App\Shared\Exception\AppException;
use App\Shared\Tenant\TenantOwnershipChecker;
use OpenApi\Attributes as OA;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[OA\Tag(name: 'Package')]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
class PatientPackageController extends BaseController
{
    public function __construct(
        private readonly PackageRepository        $packages,
        private readonly PatientPackageRepository $patientPackages,
        private readonly PatientRecordRepository  $patients,
        private readonly PackageSalesService      $sales,
        private readonly CreditLedgerService      $ledger,
        private readonly BranchResolver           $branches,
        private readonly TenantOwnershipChecker   $ownership,
    ) {}

    #[Route('/api/v1/patient/{uuid}/package', name: 'patient_package_sell', methods: ['POST'])]
    public function sell(#[CurrentUser] User $user, string $uuid, Request $request): JsonResponse
    {
        $patient = $this->requirePatient($user, $uuid);
        $data    = json_decode($request->getContent(), true);

        if (!is_array($data) || !is_string($data['package_uuid'] ?? null)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'شناسهٔ پکیج الزامی است', 422, 'package_uuid');
        }

        $package = $this->packages->findByUuid($data['package_uuid']);
        [$entityType, $entityId] = $this->branches->pair($user);

        if ($package === null || !$this->ownership->belongsToPair($entityType, $entityId, $package)) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'پکیج یافت نشد', 404);
        }

        $sold = $this->sales->sell(
            $package,
            $patient,
            $user,
            is_numeric($data['price_paid_rials'] ?? null) ? (int) $data['price_paid_rials'] : null,
        );

        return $this->success($sold->toArray($this->ledger->balance($sold)), 201);
    }

    #[Route('/api/v1/patient/{uuid}/packages', name: 'patient_package_index', methods: ['GET'])]
    public function index(#[CurrentUser] User $user, string $uuid): JsonResponse
    {
        $patient = $this->requirePatient($user, $uuid);

        return $this->success(array_map(
            fn (PatientPackage $p): array => $p->toArray($this->ledger->balance($p)),
            $this->patientPackages->findForPatient($patient),
        ));
    }

    /**
     * دفتر تراکنش‌ها با ماندهٔ تجمعی.
     *
     * ماندهٔ تجمعی اینجا محاسبه می‌شود نه ذخیره — و همین به کاربر نشان می‌دهد عدد
     * از کجا آمده.
     */
    #[Route('/api/v1/patient-package/{uuid}/ledger', name: 'patient_package_ledger', methods: ['GET'])]
    public function ledger(#[CurrentUser] User $user, string $uuid): JsonResponse
    {
        $package = $this->requirePatientPackage($user, $uuid);

        $running = 0;
        $rows    = [];

        foreach ($this->ledger->history($package) as $row) {
            $running += $row->getDelta();
            $rows[]   = $row->toArray() + ['running_balance' => $running];
        }

        return $this->success([
            'package' => $package->toArray($running),
            'rows'    => $rows,
        ]);
    }

    /** اصلاح دستی — فقط پزشک یا صاحب کلینیک، و همیشه با دلیل. */
    #[Route('/api/v1/patient-package/{uuid}/adjust', name: 'patient_package_adjust', methods: ['POST'])]
    public function adjust(#[CurrentUser] User $user, string $uuid, Request $request): JsonResponse
    {
        $this->assertMayCorrectCredit();

        $package = $this->requirePatientPackage($user, $uuid);
        $data    = json_decode($request->getContent(), true);

        if (!is_array($data) || !is_numeric($data['delta'] ?? null) || (int) $data['delta'] === 0) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'مقدار اصلاح باید عددی غیر صفر باشد', 422, 'delta');
        }

        if (!is_string($data['reason'] ?? null) || trim($data['reason']) === '') {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'دلیل اصلاح الزامی است', 422, 'reason');
        }

        $delta = (int) $data['delta'];

        // اصلاحی که مانده را منفی کند یعنی دفتر دروغ بگوید.
        if ($this->ledger->balance($package) + $delta < 0) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'مانده نمی‌تواند منفی شود', 422, 'delta');
        }

        $this->ledger->record(
            $package,
            SessionCreditLedger::KIND_ADJUSTMENT,
            $delta,
            reason: trim($data['reason']),
            by: $user,
        );

        return $this->success($package->toArray($this->ledger->balance($package)), 201);
    }

    /** ابطال دستی — ماندهٔ باقی‌مانده با یک ردیف `expiry` صفر می‌شود. */
    #[Route('/api/v1/patient-package/{uuid}/expire', name: 'patient_package_expire', methods: ['POST'])]
    public function expire(#[CurrentUser] User $user, string $uuid, Request $request): JsonResponse
    {
        $this->assertMayCorrectCredit();

        $package = $this->requirePatientPackage($user, $uuid);
        $balance = $this->ledger->balance($package);

        if ($balance <= 0) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'این پکیج ماندهٔ قابل ابطال ندارد', 422);
        }

        $data   = json_decode($request->getContent(), true);
        $reason = is_array($data) && is_string($data['reason'] ?? null) && trim($data['reason']) !== ''
            ? trim($data['reason'])
            : 'ابطال دستی پکیج';

        $this->ledger->record($package, SessionCreditLedger::KIND_EXPIRY, -$balance, reason: $reason, by: $user);

        return $this->success($package->toArray($this->ledger->balance($package)));
    }

    /**
     * اصلاح دستی اعتبار کارِ صاحب محیط است، نه منشی: ردیف `adjustment` تنها راهی است
     * که می‌شود بدون نوبت، اعتبار ساخت.
     */
    private function assertMayCorrectCredit(): void
    {
        foreach (['ROLE_DOCTOR', 'ROLE_CLINIC', 'ROLE_ADMIN'] as $role) {
            if ($this->isGranted($role)) {
                return;
            }
        }

        throw new AppException(ErrorCodes::ERR_FORBIDDEN_001, 'اصلاح اعتبار در اختیار شما نیست', 403);
    }

    private function requirePatient(User $user, string $uuid): PatientRecord
    {
        $patient = $this->patients->findOneBy(['uuid' => $uuid]);
        [$entityType, $entityId] = $this->branches->pair($user);

        if ($patient === null
            || $patient->getEntityType() !== $entityType
            || $patient->getEntityId() !== $entityId
        ) {
            throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, 'بیمار یافت نشد', 404);
        }

        return $patient;
    }

    private function requirePatientPackage(User $user, string $uuid): PatientPackage
    {
        $package = $this->patientPackages->findByUuid($uuid);
        [$entityType, $entityId] = $this->branches->pair($user);

        if ($package === null || !$this->ownership->belongsToPair($entityType, $entityId, $package)) {
            throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, 'پکیج بیمار یافت نشد', 404);
        }

        return $package;
    }
}
