<?php

namespace App\Representation\Controller;

use App\Appointment\Entity\Appointment;
use App\Auth\Entity\User;
use App\Clinic\Entity\Clinic;
use App\Doctor\Entity\Doctor;
use App\Settlement\Entity\FinancialBreakdown;
use App\Specialty\Entity\Specialty;
use App\Representation\Repository\RepresentationRepository;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Service\InputValidator;
use App\Shared\Controller\BaseController;
use Doctrine\ORM\EntityManagerInterface;
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;

/**
 * اکشن‌های پنل نماینده: افزودن پزشک/کلینیک، نوبت‌های پزشکانِ زیرمجموعه، و پروفایل نماینده‌ی جاری.
 * هر اکشن برای ROLE_REPRESENTATION (و ROLE_ADMIN) باز است؛ مالکیت همیشه از کاربر جاری تعیین می‌شود.
 */
#[OA\Tag(name: 'Representations')]
#[IsGranted('ROLE_REPRESENTATION')]
class RepresentationActionController extends BaseController
{
    public function __construct(
        private readonly EntityManagerInterface $em,
        private readonly RepresentationRepository $representationRepo,
        private readonly \App\Settlement\Repository\SettlementRepository $settlementRepo,
        private readonly \App\Subscription\Service\SubscriptionService $subscriptionService,
        private readonly \App\Sms\Service\SmsService $smsService,
        private readonly \App\Config\Repository\SiteConfigRepository $configRepo,
        private readonly \App\Shared\Service\ApiIrService $apiIr,
    ) {}

    #[OA\Get(
        path: '/api/v1/representation/me',
        summary: 'پروفایل نماینده‌ی کاربر جاری',
        security: [['bearerAuth' => []]],
        responses: [
            new OA\Response(response: 200, description: 'پروفایل نماینده'),
            new OA\Response(response: 404, description: 'کاربر جاری نماینده نیست'),
        ]
    )]
    #[Route('/api/v1/representation/me', methods: ['GET'])]
    public function me(#[CurrentUser] User $user): JsonResponse
    {
        $rep = $this->representationRepo->findByUser($user);
        if ($rep === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'نماینده‌ای برای این کاربر یافت نشد', 404);
        }

        return $this->success(['data' => $this->repProfile($rep, $user)]);
    }

    /** خروجی پروفایل نماینده + وضعیت هویت کاربر. */
    private function repProfile(\App\Representation\Entity\Representation $rep, User $user): array
    {
        return $rep->toArray() + [
            'national_code'          => $user->getNationalCode(),
            'national_code_verified' => $user->isNationalCodeVerified(),
        ];
    }

    #[OA\Post(
        path: '/api/v1/representation/verify-national-code',
        summary: 'تأیید کد ملی نماینده با استعلام شاهکار',
        security: [['bearerAuth' => []]],
        requestBody: new OA\RequestBody(
            required: true,
            content: new OA\JsonContent(
                required: ['national_code'],
                properties: [new OA\Property(property: 'national_code', type: 'string', example: '0012345678')]
            )
        ),
        responses: [
            new OA\Response(response: 200, description: 'کد ملی تأیید شد'),
            new OA\Response(response: 422, description: 'کد ملی نامعتبر یا متعلق به این موبایل نیست'),
            new OA\Response(response: 502, description: 'خطا در استعلام'),
            new OA\Response(response: 503, description: 'سرویس استعلام پیکربندی نشده'),
        ]
    )]
    #[Route('/api/v1/representation/verify-national-code', methods: ['POST'])]
    public function verifyNationalCode(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $rep = $this->representationRepo->findByUser($user);
        if ($rep === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'نماینده‌ای برای این کاربر یافت نشد', 404);
        }

        $data         = json_decode($request->getContent(), true) ?? [];
        $nationalCode = preg_replace('/\D/', '', (string) ($data['national_code'] ?? ''));

        if (strlen($nationalCode) !== 10) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'کد ملی باید ۱۰ رقم باشد', 422, 'national_code');
        }

        if (!$this->apiIr->shahkarMatch($nationalCode, $user->getMobileNumber())) {
            return $this->error(ErrorCodes::ERR_NATIONAL_CODE_MISMATCH, ErrorCodes::message(ErrorCodes::ERR_NATIONAL_CODE_MISMATCH), 422, 'national_code');
        }

        $user->setNationalCode($nationalCode);
        $user->setNationalCodeVerified(true);
        $this->em->flush();

        return $this->success(['data' => $this->repProfile($rep, $user)]);
    }

    #[OA\Post(
        path: '/api/v1/representation/iban',
        summary: 'افزودن شماره شبا (با تطبیق مالکیت IbanMatch)',
        security: [['bearerAuth' => []]],
        requestBody: new OA\RequestBody(
            required: true,
            content: new OA\JsonContent(
                required: ['iban', 'birth_date'],
                properties: [
                    new OA\Property(property: 'iban', type: 'string', example: 'IR000000000000000000000000'),
                    new OA\Property(property: 'birth_date', type: 'string', example: '1370/01/01', description: 'تاریخ تولد شمسی — فقط برای استعلام، ذخیره نمی‌شود'),
                ]
            )
        ),
        responses: [
            new OA\Response(response: 200, description: 'شبا اضافه شد'),
            new OA\Response(response: 409, description: 'کد ملی تأیید نشده یا سقف ۲ شبا'),
            new OA\Response(response: 422, description: 'شبا/تاریخ تولد نامعتبر یا شبا متعلق به نماینده نیست'),
        ]
    )]
    #[Route('/api/v1/representation/iban', methods: ['POST'])]
    public function addIban(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $rep = $this->representationRepo->findByUser($user);
        if ($rep === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'نماینده‌ای برای این کاربر یافت نشد', 404);
        }

        if (!$user->isNationalCodeVerified()) {
            return $this->error(ErrorCodes::ERR_NATIONAL_CODE_UNVERIFIED, ErrorCodes::message(ErrorCodes::ERR_NATIONAL_CODE_UNVERIFIED), 409);
        }
        if (count($rep->getIbans()) >= 2) {
            return $this->error(ErrorCodes::ERR_IBAN_LIMIT, ErrorCodes::message(ErrorCodes::ERR_IBAN_LIMIT), 409);
        }

        $data = json_decode($request->getContent(), true) ?? [];
        $iban = $this->normalizeIban((string) ($data['iban'] ?? ''));

        if ($iban === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'شماره شبا نامعتبر است', 422, 'iban');
        }

        // تاریخ تولد فقط برای استعلام IbanMatch لازم است و ذخیره نمی‌شود.
        $birthDate = $this->normalizeBirthDate((string) ($data['birth_date'] ?? ''));
        if ($birthDate === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'تاریخ تولد نامعتبر است (نمونه: 1370/01/01)', 422, 'birth_date');
        }

        $match = $this->apiIr->ibanMatch($iban, (string) $user->getNationalCode(), $birthDate);
        if (!$match['matched']) {
            return $this->error(ErrorCodes::ERR_IBAN_MISMATCH, ErrorCodes::message(ErrorCodes::ERR_IBAN_MISMATCH), 422, 'iban');
        }

        $rep->addIban([
            'iban'       => $iban,
            'bank_name'  => $match['bank_name'],
            'owner_name' => $match['owner_name'] ?? $rep->getFullName(),
            'verified'   => true,
        ]);
        $this->em->flush();

        return $this->success(['data' => $this->repProfile($rep, $user)], 201);
    }

    #[OA\Delete(
        path: '/api/v1/representation/iban/{id}',
        summary: 'حذف یک شماره شبا',
        security: [['bearerAuth' => []]],
        parameters: [new OA\Parameter(name: 'id', in: 'path', required: true, schema: new OA\Schema(type: 'string'))],
        responses: [new OA\Response(response: 200, description: 'شبا حذف شد')]
    )]
    #[Route('/api/v1/representation/iban/{id}', methods: ['DELETE'])]
    public function removeIban(string $id, #[CurrentUser] User $user): JsonResponse
    {
        $rep = $this->representationRepo->findByUser($user);
        if ($rep === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'نماینده‌ای برای این کاربر یافت نشد', 404);
        }

        $rep->removeIban($id);
        $this->em->flush();

        return $this->success(['data' => $this->repProfile($rep, $user)]);
    }

    /** نرمال‌سازی و اعتبارسنجی شکلی شبا. خروجی null یعنی نامعتبر. */
    private function normalizeIban(string $raw): ?string
    {
        $iban = strtoupper(preg_replace('/\s+/', '', $raw));
        if (!str_starts_with($iban, 'IR')) {
            $iban = 'IR' . ltrim($iban, 'irIR');
        }
        return preg_match('/^IR\d{24}$/', $iban) === 1 ? $iban : null;
    }

    /**
     * نرمال‌سازی تاریخ تولد شمسی به فرمت Y/m/d (نمونه: 1370/01/01).
     * ارقام فارسی، جداکننده‌های - یا / و طول ناقص پذیرفته و یکدست می‌شوند. خروجی null یعنی نامعتبر.
     */
    private function normalizeBirthDate(string $raw): ?string
    {
        $digits = strtr(trim($raw), ['۰'=>'0','۱'=>'1','۲'=>'2','۳'=>'3','۴'=>'4','۵'=>'5','۶'=>'6','۷'=>'7','۸'=>'8','۹'=>'9']);
        $parts  = preg_split('/[\/\-.]/', $digits);
        if (count($parts) !== 3) {
            return null;
        }
        [$y, $m, $d] = $parts;
        if (!ctype_digit($y) || !ctype_digit($m) || !ctype_digit($d)) {
            return null;
        }
        $y = (int) $y; $m = (int) $m; $d = (int) $d;
        if ($y < 1280 || $y > 1450 || $m < 1 || $m > 12 || $d < 1 || $d > 31) {
            return null;
        }
        return sprintf('%04d/%02d/%02d', $y, $m, $d);
    }

    #[OA\Post(
        path: '/api/v1/representation/doctor',
        summary: 'افزودن پزشک توسط نماینده',
        security: [['bearerAuth' => []]],
        requestBody: new OA\RequestBody(
            required: true,
            content: new OA\JsonContent(
                required: ['mobile', 'name'],
                properties: [
                    new OA\Property(property: 'mobile', type: 'string'),
                    new OA\Property(property: 'name', type: 'string'),
                    new OA\Property(property: 'gender', type: 'string', nullable: true),
                    new OA\Property(property: 'degree', type: 'string', nullable: true),
                    new OA\Property(property: 'medical_system_code', type: 'string', nullable: true),
                    new OA\Property(property: 'activity_time', type: 'integer', nullable: true, description: 'Unix timestamp (seconds) of career start date; basis for years-of-experience'),
                    new OA\Property(property: 'specialties', type: 'array', items: new OA\Items(type: 'integer'), nullable: true),
                ]
            )
        ),
        responses: [
            new OA\Response(response: 201, description: 'پزشک ساخته شد'),
            new OA\Response(response: 409, description: 'این کاربر قبلاً پزشک است'),
            new OA\Response(response: 422, description: 'فیلد الزامی وارد نشده'),
        ]
    )]
    #[Route('/api/v1/representation/doctor', methods: ['POST'])]
    public function createDoctor(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $data   = json_decode($request->getContent(), true) ?? [];
        $mobile = InputValidator::toEnglishDigits(trim((string) ($data['mobile'] ?? '')));
        $name   = trim((string) ($data['name'] ?? ''));

        if ($mobile === '' || $name === '') {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'موبایل و نام الزامی هستند', 422);
        }
        if (!InputValidator::isValidIranMobile($mobile)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'شماره موبایل نامعتبر است', 422, 'mobile');
        }

        $doctorUser = $this->em->getRepository(User::class)->findOneBy(['mobileNumber' => $mobile]);
        if (!$doctorUser) {
            $doctorUser = new User($mobile);
            $doctorUser->setRealName($name);
            $doctorUser->setPasswordHash(password_hash(bin2hex(random_bytes(8)), PASSWORD_BCRYPT));
            $this->em->persist($doctorUser);
        }

        if ($this->em->getRepository(Doctor::class)->findOneBy(['user' => $doctorUser]) !== null) {
            return $this->error(ErrorCodes::ERR_CONFLICT_001, 'این کاربر قبلاً پروفایل پزشک دارد', 409);
        }

        $doctor = new Doctor($doctorUser, $name);
        if (!empty($data['gender']))              $doctor->setGender($data['gender']);
        if (!empty($data['degree']))              $doctor->setDegree($data['degree']);
        if (!empty($data['medical_system_code'])) $doctor->setMedicalSystemCode($data['medical_system_code']);
        if (!empty($data['info']))                $doctor->setInfo($data['info']);
        if (!empty($data['mobile_number']))       $doctor->setMobileNumber($data['mobile_number']);
        if (!empty($data['activity_time']))       $doctor->setActivityTime((int) $data['activity_time']);

        if (!empty($data['specialties']) && is_array($data['specialties'])) {
            foreach ($data['specialties'] as $id) {
                $s = $this->em->getRepository(Specialty::class)->find((int) $id);
                if ($s !== null) $doctor->getSpecialties()->add($s);
            }
        }

        $rep = $this->representationRepo->findByUser($user);
        if ($rep !== null) {
            $doctor->setRepresentationId($rep->getId());
        }

        $roles = $doctorUser->getRoles();
        if (!in_array('ROLE_DOCTOR', $roles, true)) {
            $roles[] = 'ROLE_DOCTOR';
            $doctorUser->setRoles(array_values(array_unique($roles)));
        }

        $this->em->persist($doctor);
        $this->em->flush();

        // پیام خوش‌آمد به پزشک
        $siteName = $this->configRepo->get('site_name') ?: 'کلینیک‌پرو';
        $this->smsService->dispatchTemplate(
            \App\Sms\Entity\SmsLog::TAG_WELCOME,
            $mobile,
            ['name' => $name, 'site' => $siteName],
        );

        return $this->success(['uuid' => $doctor->getUuid()], 201);
    }

    #[OA\Post(
        path: '/api/v1/representation/clinic',
        summary: 'افزودن کلینیک توسط نماینده',
        security: [['bearerAuth' => []]],
        requestBody: new OA\RequestBody(
            required: true,
            content: new OA\JsonContent(
                required: ['owner_mobile', 'name'],
                properties: [
                    new OA\Property(property: 'owner_mobile', type: 'string'),
                    new OA\Property(property: 'name', type: 'string'),
                    new OA\Property(property: 'telephone', type: 'string', nullable: true),
                    new OA\Property(property: 'address', type: 'string', nullable: true),
                ]
            )
        ),
        responses: [
            new OA\Response(response: 200, description: 'کلینیک ساخته شد'),
            new OA\Response(response: 422, description: 'فیلد الزامی وارد نشده'),
        ]
    )]
    #[Route('/api/v1/representation/clinic', methods: ['POST'])]
    public function createClinic(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $data   = json_decode($request->getContent(), true) ?? [];
        $mobile = InputValidator::toEnglishDigits(trim((string) ($data['owner_mobile'] ?? '')));
        $name   = trim((string) ($data['name'] ?? ''));

        if ($mobile === '') {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'شماره موبایل الزامی است', 422);
        }
        if (!InputValidator::isValidIranMobile($mobile)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'شماره موبایل نامعتبر است', 422, 'owner_mobile');
        }
        if ($name === '') {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'نام کلینیک الزامی است', 422);
        }

        $ownerUser = $this->em->getRepository(User::class)->findOneBy(['mobileNumber' => $mobile]);
        if (!$ownerUser) {
            $ownerUser = new User($mobile);
            $this->em->persist($ownerUser);
        }

        $roles = $ownerUser->getRoles();
        if (!in_array('ROLE_CLINIC', $roles, true)) {
            $roles[] = 'ROLE_CLINIC';
            $ownerUser->setRoles(array_values(array_unique($roles)));
        }

        $clinic = new Clinic($ownerUser);
        $clinic->setName($name);
        if (!empty($data['telephone'])) $clinic->setTelephone($data['telephone']);
        if (!empty($data['address']))   $clinic->setAddress($data['address']);
        if (!empty($data['info']))      $clinic->setInfo($data['info']);

        $rep = $this->representationRepo->findByUser($user);
        if ($rep !== null) {
            $clinic->setRepresentationId($rep->getId());
        }

        $this->em->persist($clinic);
        $this->em->flush();

        // پیام خوش‌آمد به مالک کلینیک
        $siteName = $this->configRepo->get('site_name') ?: 'کلینیک‌پرو';
        $this->smsService->dispatchTemplate(
            \App\Sms\Entity\SmsLog::TAG_WELCOME,
            $mobile,
            ['name' => $name, 'site' => $siteName],
        );

        return $this->success([
            'uuid'      => $clinic->getUuid(),
            'name'      => $clinic->getName(),
            'is_active' => $clinic->isActive(),
        ]);
    }

    #[OA\Get(
        path: '/api/v1/representation/doctors',
        summary: 'پزشکانِ ثبت‌شده توسط نماینده‌ی جاری (paginated، شکلِ یکسان با /admin/doctors)',
        security: [['bearerAuth' => []]],
        parameters: [
            new OA\Parameter(name: 'page', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 1)),
            new OA\Parameter(name: 'limit', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 15)),
            new OA\Parameter(name: 'search', in: 'query', required: false, schema: new OA\Schema(type: 'string')),
        ],
        responses: [new OA\Response(response: 200, description: 'لیست پزشکان نماینده')]
    )]
    #[Route('/api/v1/representation/doctors', methods: ['GET'])]
    public function doctors(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $rep = $this->representationRepo->findByUser($user);
        if ($rep === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'نماینده‌ای برای این کاربر یافت نشد', 404);
        }

        $page   = max(1, (int) $request->query->get('page', 1));
        $limit  = min(100, max(1, (int) $request->query->get('limit', 15)));
        $search = trim((string) $request->query->get('search', ''));

        $qb = $this->em->createQueryBuilder()
            ->select('d.uuid, d.id, d.name, d.gender, d.degree, d.medicalSystemCode, d.mobileNumber, d.images, d.doctorRate, d.activeDoctorAppointment, d.createdAt')
            ->from(Doctor::class, 'd')
            ->where('d.representationId = :repId')
            ->setParameter('repId', $rep->getId())
            ->orderBy('d.createdAt', 'DESC');

        if ($search !== '') {
            $qb->andWhere('d.name LIKE :s OR d.mobileNumber LIKE :s')->setParameter('s', '%' . $search . '%');
        }

        $total = (clone $qb)->select('COUNT(d.id)')->getQuery()->getSingleScalarResult();
        $rows  = $qb->setFirstResult(($page - 1) * $limit)->setMaxResults($limit)
                    ->getQuery()->getArrayResult();

        $items = array_map(fn(array $d) => [
            'uuid'          => $d['uuid'],
            'id'            => (int) $d['id'],
            'name'          => $d['name'],
            'gender'        => $d['gender'],
            'degree'        => $d['degree'],
            'medical_code'  => $d['medicalSystemCode'],
            'mobile'        => $d['mobileNumber'],
            'email'         => null,
            'is_active'     => (bool) $d['activeDoctorAppointment'],
            'rate'          => (float) $d['doctorRate'],
            'specialties'   => [],
            'profile_image' => !empty($d['images']) ? ($d['images'][0]['url'] ?? null) : null,
            'created_at'    => date('c', (int) $d['createdAt']),
        ], $rows);

        return $this->paginated($items, (int) $total, $page, $limit);
    }

    #[OA\Get(
        path: '/api/v1/representation/clinics',
        summary: 'کلینیک‌های ثبت‌شده توسط نماینده‌ی جاری (paginated، شکلِ یکسان با /admin/clinics)',
        security: [['bearerAuth' => []]],
        parameters: [
            new OA\Parameter(name: 'page', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 1)),
            new OA\Parameter(name: 'limit', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 15)),
            new OA\Parameter(name: 'search', in: 'query', required: false, schema: new OA\Schema(type: 'string')),
        ],
        responses: [new OA\Response(response: 200, description: 'لیست کلینیک‌های نماینده')]
    )]
    #[Route('/api/v1/representation/clinics', methods: ['GET'])]
    public function clinics(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $rep = $this->representationRepo->findByUser($user);
        if ($rep === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'نماینده‌ای برای این کاربر یافت نشد', 404);
        }

        $page   = max(1, (int) $request->query->get('page', 1));
        $limit  = min(100, max(1, (int) $request->query->get('limit', 15)));
        $search = trim((string) $request->query->get('search', ''));

        $qb = $this->em->createQueryBuilder()
            ->select('c.uuid, c.id, c.name, c.telephone, c.clinicLogo, c.isActive, c.createdAt')
            ->from(Clinic::class, 'c')
            ->where('c.representationId = :repId')
            ->setParameter('repId', $rep->getId())
            ->orderBy('c.createdAt', 'DESC');

        if ($search !== '') {
            $qb->andWhere('c.name LIKE :s OR c.telephone LIKE :s')->setParameter('s', '%' . $search . '%');
        }

        $total = (clone $qb)->select('COUNT(c.id)')->getQuery()->getSingleScalarResult();
        $rows  = $qb->setFirstResult(($page - 1) * $limit)->setMaxResults($limit)
                    ->getQuery()->getArrayResult();

        $items = array_map(fn(array $c) => [
            'uuid'         => $c['uuid'],
            'id'           => (int) $c['id'],
            'name'         => $c['name'],
            'telephone'    => $c['telephone'],
            'logo'         => $c['clinicLogo'],
            'clinic_logo'  => $c['clinicLogo'],
            'is_active'    => (bool) $c['isActive'],
            'doctors_count'=> 0,
            'created_at'   => date('c', (int) $c['createdAt']),
        ], $rows);

        return $this->paginated($items, (int) $total, $page, $limit);
    }

    #[OA\Get(
        path: '/api/v1/representation/appointments',
        summary: 'نوبت‌های پزشکانِ زیرمجموعه‌ی نماینده‌ی جاری (paginated)',
        security: [['bearerAuth' => []]],
        parameters: [
            new OA\Parameter(name: 'page', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 1)),
            new OA\Parameter(name: 'limit', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 15)),
            new OA\Parameter(name: 'status', in: 'query', required: false, schema: new OA\Schema(type: 'string')),
            new OA\Parameter(name: 'date', in: 'query', required: false, schema: new OA\Schema(type: 'string', format: 'date')),
            new OA\Parameter(name: 'search', in: 'query', required: false, schema: new OA\Schema(type: 'string')),
        ],
        responses: [new OA\Response(response: 200, description: 'لیست نوبت‌ها')]
    )]
    #[Route('/api/v1/representation/appointments', methods: ['GET'])]
    public function appointments(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $rep = $this->representationRepo->findByUser($user);
        if ($rep === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'نماینده‌ای برای این کاربر یافت نشد', 404);
        }

        $page   = max(1, (int) $request->query->get('page', 1));
        $limit  = min(500, max(1, (int) $request->query->get('limit', 15)));
        $search = trim((string) $request->query->get('search', ''));
        $status = trim((string) $request->query->get('status', ''));
        $date   = trim((string) $request->query->get('date', ''));

        $qb = $this->em->createQueryBuilder()
            ->select(
                'a.uuid, a.slotStart, a.slotEnd, a.status, a.createdAt, a.version',
                'd.uuid as doctor_uuid, d.name as doctor_name',
                'u.mobileNumber as patient_mobile, u.realName as patient_name',
            )
            ->from(Appointment::class, 'a')
            ->join('a.doctor', 'd')
            ->join('a.user', 'u')
            ->where('d.representationId = :repId')
            ->setParameter('repId', $rep->getId())
            ->orderBy('a.slotStart', 'ASC');

        if ($search !== '') {
            $qb->andWhere('u.mobileNumber LIKE :s OR d.name LIKE :s OR u.realName LIKE :s')
               ->setParameter('s', '%' . $search . '%');
        }
        if ($status !== '') {
            $qb->andWhere('a.status = :status')->setParameter('status', $status);
        }
        if ($date !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
            $dayStart = (int) strtotime($date . ' 00:00:00');
            $dayEnd   = (int) strtotime($date . ' 23:59:59');
            $qb->andWhere('a.slotStart >= :dayStart AND a.slotStart <= :dayEnd')
               ->setParameter('dayStart', $dayStart)
               ->setParameter('dayEnd', $dayEnd);
        }

        $total = (clone $qb)->select('COUNT(a.id)')->getQuery()->getSingleScalarResult();

        $rows = $qb->setFirstResult(($page - 1) * $limit)->setMaxResults($limit)
                   ->getQuery()->getArrayResult();

        $items = array_map(fn(array $a) => [
            'uuid'             => $a['uuid'],
            'patient_name'     => $a['patient_name'] ?? '',
            'patient_mobile'   => $a['patient_mobile'],
            'doctor_uuid'      => $a['doctor_uuid'],
            'doctor_name'      => $a['doctor_name'],
            'slot_start'       => (int) $a['slotStart'],
            'slot_end'         => (int) $a['slotEnd'],
            'appointment_date' => date('Y-m-d', (int) $a['slotStart']),
            'appointment_time' => date('H:i', (int) $a['slotStart']),
            'end_time'         => date('H:i', (int) $a['slotEnd']),
            'status'           => $a['status'],
            'created_at'       => (int) $a['createdAt'],
        ], $rows);

        return $this->paginated($items, (int) $total, $page, $limit);
    }

    #[OA\Get(
        path: '/api/v1/representation/doctors/stats',
        summary: 'آمار پزشکانِ ثبت‌شده توسط نماینده‌ی جاری (شکلِ یکسان با /admin/doctors/stats)',
        security: [['bearerAuth' => []]],
        responses: [new OA\Response(response: 200, description: 'آمار پزشکان نماینده')]
    )]
    #[Route('/api/v1/representation/doctors/stats', methods: ['GET'])]
    public function doctorStats(#[CurrentUser] User $user): JsonResponse
    {
        $rep = $this->representationRepo->findByUser($user);
        if ($rep === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'نماینده‌ای برای این کاربر یافت نشد', 404);
        }

        $conn  = $this->em->getConnection();
        $repId = $rep->getId();

        $total  = (int) $conn->fetchOne('SELECT COUNT(*) FROM doctors WHERE representation_id = ?', [$repId]);
        $active = (int) $conn->fetchOne('SELECT COUNT(*) FROM doctors WHERE representation_id = ? AND active_doctor_appointment = 1', [$repId]);
        $male   = (int) $conn->fetchOne("SELECT COUNT(*) FROM doctors WHERE representation_id = ? AND gender IN ('man','male')", [$repId]);
        $female = (int) $conn->fetchOne("SELECT COUNT(*) FROM doctors WHERE representation_id = ? AND gender IN ('woman','female')", [$repId]);

        return $this->success([
            'total'    => $total,
            'active'   => $active,
            'inactive' => $total - $active,
            'male'     => $male,
            'female'   => $female,
        ]);
    }

    #[OA\Post(
        path: '/api/v1/representation/doctors/{uuid}/status',
        summary: 'فعال/غیرفعال کردن پزشکِ زیرمجموعه‌ی نماینده‌ی جاری',
        security: [['bearerAuth' => []]],
        parameters: [new OA\Parameter(name: 'uuid', in: 'path', required: true, schema: new OA\Schema(type: 'string'))],
        responses: [
            new OA\Response(response: 200, description: 'وضعیت جدید پزشک'),
            new OA\Response(response: 404, description: 'پزشک یافت نشد یا متعلق به این نماینده نیست'),
        ]
    )]
    #[Route('/api/v1/representation/doctors/{uuid}/status', methods: ['POST'])]
    public function toggleDoctorStatus(string $uuid, #[CurrentUser] User $user): JsonResponse
    {
        $rep = $this->representationRepo->findByUser($user);
        if ($rep === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'نماینده‌ای برای این کاربر یافت نشد', 404);
        }

        $doctor = $this->em->getRepository(Doctor::class)->findOneBy(['uuid' => $uuid]);
        if ($doctor === null || $doctor->getRepresentationId() !== $rep->getId()) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'پزشک یافت نشد', 404);
        }

        $doctor->setActiveDoctorAppointment(!$doctor->isActiveDoctorAppointment());
        $this->em->flush();

        return $this->success(['is_active' => $doctor->isActiveDoctorAppointment()]);
    }

    // ── Dashboard / Finance ───────────────────────────────────────────────────

    #[OA\Get(
        path: '/api/v1/representation/dashboard/summary',
        summary: 'خلاصه‌ی داشبورد نماینده‌ی جاری: آمار نوبت و درآمد (امروز/هفته/ماه/کل)',
        security: [['bearerAuth' => []]],
        responses: [new OA\Response(response: 200, description: 'خلاصه‌ی داشبورد')]
    )]
    #[Route('/api/v1/representation/dashboard/summary', methods: ['GET'])]
    public function dashboardSummary(#[CurrentUser] User $user): JsonResponse
    {
        $rep = $this->representationRepo->findByUser($user);
        if ($rep === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'نماینده‌ای برای این کاربر یافت نشد', 404);
        }

        $repId = $rep->getId();
        $now   = time();
        $today = (int) strtotime('today');
        $week  = $now - 7 * 86400;
        $month = $now - 30 * 86400;

        $apptCount = fn(?int $start): int => (int) $this->em->createQuery(
            'SELECT COUNT(a.id) FROM App\Appointment\Entity\Appointment a
             JOIN a.doctor d WHERE d.representationId = :repId' . ($start !== null ? ' AND a.createdAt >= :start' : '')
        )->setParameters($start !== null ? ['repId' => $repId, 'start' => $start] : ['repId' => $repId])
         ->getSingleScalarResult();

        $income = fn(?int $start): int => (int) ($this->em->createQuery(
            'SELECT SUM(b.representationShareRials) FROM App\Settlement\Entity\FinancialBreakdown b
             WHERE b.representationId = :repId' . ($start !== null ? ' AND b.createdAt >= :start' : '')
        )->setParameters($start !== null ? ['repId' => $repId, 'start' => $start] : ['repId' => $repId])
         ->getSingleScalarResult() ?? 0);

        return $this->success([
            'appointments' => [
                'today' => $apptCount($today),
                'week'  => $apptCount($week),
                'month' => $apptCount($month),
                'total' => $apptCount(null),
            ],
            'income' => [
                'today'          => $income($today),
                'week'           => $income($week),
                'month'          => $income($month),
                'total'          => $income(null),
                'settlable_rials'=> $this->settlementRepo->getWalletBalance($user),
                'settled_rials'  => $this->settlementRepo->sumByStatus($user, ['paid']),
                'pending_rials'  => $this->settlementRepo->sumByStatus($user, ['pending', 'approved']),
            ],
        ]);
    }

    #[OA\Get(
        path: '/api/v1/representation/doctors/performance',
        summary: 'عملکرد پزشکانِ نماینده‌ی جاری (نوبت‌ها + درآمد + وضعیت اشتراک)',
        security: [['bearerAuth' => []]],
        parameters: [
            new OA\Parameter(name: 'page', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 1)),
            new OA\Parameter(name: 'limit', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 15)),
        ],
        responses: [new OA\Response(response: 200, description: 'عملکرد پزشکان')]
    )]
    #[Route('/api/v1/representation/doctors/performance', methods: ['GET'])]
    public function doctorsPerformance(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $rep = $this->representationRepo->findByUser($user);
        if ($rep === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'نماینده‌ای برای این کاربر یافت نشد', 404);
        }

        $page  = max(1, (int) $request->query->get('page', 1));
        $limit = min(100, max(1, (int) $request->query->get('limit', 15)));
        $repId = $rep->getId();

        $qb = $this->em->createQueryBuilder()
            ->select('d.id, d.uuid, d.name')
            ->from(Doctor::class, 'd')
            ->where('d.representationId = :repId')
            ->setParameter('repId', $repId)
            ->orderBy('d.createdAt', 'DESC');

        $total = (clone $qb)->select('COUNT(d.id)')->getQuery()->getSingleScalarResult();
        $rows  = $qb->setFirstResult(($page - 1) * $limit)->setMaxResults($limit)
                    ->getQuery()->getArrayResult();

        $now   = time();
        $today = (int) strtotime('today');
        $week  = $now - 7 * 86400;
        $month = $now - 30 * 86400;

        $apptCount = fn(int $doctorId, ?int $start): int => (int) $this->em->createQuery(
            'SELECT COUNT(a.id) FROM App\Appointment\Entity\Appointment a
             WHERE a.doctor = :d' . ($start !== null ? ' AND a.createdAt >= :start' : '')
        )->setParameters($start !== null ? ['d' => $doctorId, 'start' => $start] : ['d' => $doctorId])
         ->getSingleScalarResult();

        $items = array_map(function (array $d) use ($apptCount, $today, $week, $month): array {
            $doctorId = (int) $d['id'];
            $income = (int) ($this->em->createQuery(
                'SELECT SUM(b.representationShareRials) FROM App\Settlement\Entity\FinancialBreakdown b
                 WHERE b.doctorId = :d'
            )->setParameter('d', $doctorId)->getSingleScalarResult() ?? 0);

            $sub    = $this->subscriptionService->getActiveSubscription('doctor', $doctorId);
            $status = $sub === null ? 'none' : 'active';

            return [
                'uuid' => $d['uuid'],
                'name' => $d['name'],
                'appointments' => [
                    'today' => $apptCount($doctorId, $today),
                    'week'  => $apptCount($doctorId, $week),
                    'month' => $apptCount($doctorId, $month),
                    'total' => $apptCount($doctorId, null),
                ],
                'representation_income_rials' => $income,
                'subscription_status'         => $status,
            ];
        }, $rows);

        return $this->paginated($items, (int) $total, $page, $limit);
    }

    #[OA\Get(
        path: '/api/v1/representation/finance/report',
        summary: 'گزارش مالی نماینده‌ی جاری بر اساس بازه (ردیف‌های FinancialBreakdown)',
        security: [['bearerAuth' => []]],
        parameters: [
            new OA\Parameter(name: 'page', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 1)),
            new OA\Parameter(name: 'limit', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 15)),
            new OA\Parameter(name: 'from', in: 'query', required: false, schema: new OA\Schema(type: 'integer', description: 'Unix timestamp')),
            new OA\Parameter(name: 'to', in: 'query', required: false, schema: new OA\Schema(type: 'integer', description: 'Unix timestamp')),
        ],
        responses: [new OA\Response(response: 200, description: 'گزارش مالی نماینده')]
    )]
    #[Route('/api/v1/representation/finance/report', methods: ['GET'])]
    public function financeReport(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $rep = $this->representationRepo->findByUser($user);
        if ($rep === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'نماینده‌ای برای این کاربر یافت نشد', 404);
        }

        $page  = max(1, (int) $request->query->get('page', 1));
        $limit = min(100, max(1, (int) $request->query->get('limit', 15)));
        $from  = $request->query->get('from');
        $to    = $request->query->get('to');

        $qb = $this->em->createQueryBuilder()
            ->select(
                'b.uuid, b.grossRials, b.taxRials, b.smsFeeRials, b.commissionPercent,
                 b.representationShareRials, b.createdAt,
                 a.uuid as appointment_uuid, doc.name as doctor_name'
            )
            ->from(FinancialBreakdown::class, 'b')
            ->join('b.payment', 'p')
            ->leftJoin('p.appointment', 'a')
            ->leftJoin('a.doctor', 'doc')
            ->where('b.representationId = :repId')
            ->setParameter('repId', $rep->getId())
            ->orderBy('b.createdAt', 'DESC');

        if ($from !== null && $from !== '') {
            $qb->andWhere('b.createdAt >= :from')->setParameter('from', (int) $from);
        }
        if ($to !== null && $to !== '') {
            $qb->andWhere('b.createdAt <= :to')->setParameter('to', (int) $to);
        }

        $total = (clone $qb)->select('COUNT(b.uuid)')->getQuery()->getSingleScalarResult();
        $rows  = $qb->setFirstResult(($page - 1) * $limit)->setMaxResults($limit)
                    ->getQuery()->getArrayResult();

        $items = array_map(fn(array $b) => [
            'uuid'                       => $b['uuid'],
            'appointment_uuid'           => $b['appointment_uuid'] ?? null,
            'doctor_name'                => $b['doctor_name'] ?? null,
            'gross_rials'                => (int) $b['grossRials'],
            'tax_rials'                  => (int) $b['taxRials'],
            'sms_fee_rials'              => (int) $b['smsFeeRials'],
            'commission_percent'         => (float) $b['commissionPercent'],
            'representation_share_rials' => (int) $b['representationShareRials'],
            'created_at'                 => date('c', (int) $b['createdAt']),
        ], $rows);

        return $this->paginated($items, (int) $total, $page, $limit);
    }
}
