<?php

namespace App\Appointment\Controller;

use App\Appointment\Entity\Appointment;
use App\Shared\Constant\ErrorCodes;
use App\Appointment\Repository\AppointmentRepository;
use App\Appointment\Repository\SlotTakenException;
use App\Appointment\Service\SlotCalculatorService;
use App\Auth\Entity\User;
use App\Auth\Repository\UserActiveContextRepository;
use App\Clinic\Repository\ClinicRepository;
use App\Doctor\Entity\Doctor;
use App\Doctor\Repository\DoctorRepository;
use App\Secretary\Entity\DoctorSecretary;
use App\Secretary\Repository\DoctorSecretaryRepository;
use App\Shared\Controller\BaseController;
use Doctrine\ORM\EntityManagerInterface;
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;
use OpenApi\Attributes as OA;

#[OA\Tag(name: 'My Appointments')]
class MyAppointmentsController extends BaseController
{
    public function __construct(
        private readonly EntityManagerInterface        $em,
        private readonly AppointmentRepository         $appointmentRepo,
        private readonly DoctorRepository              $doctorRepo,
        private readonly ClinicRepository              $clinicRepo,
        private readonly DoctorSecretaryRepository     $secretaryRepo,
        private readonly UserActiveContextRepository   $contextRepo,
        private readonly SlotCalculatorService         $slotCalculator,
    ) {}

    #[Route('/api/v1/my/appointment', methods: ['POST'])]
    #[IsGranted('IS_AUTHENTICATED_FULLY')]
    public function createAppointment(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $roles = $user->getRoles();
        $allowed = ['ROLE_DOCTOR', 'ROLE_CLINIC', 'ROLE_SECRETARY', 'ROLE_ADMIN'];
        if (!array_intersect($allowed, $roles)) {
            return $this->error(ErrorCodes::FORBIDDEN, 'دسترسی ندارید', 403);
        }

        $data        = json_decode($request->getContent(), true) ?? [];
        $doctorUuid  = trim($data['doctor_uuid']    ?? '');
        $slotStart   = (int) ($data['slot_start']   ?? 0);
        $slotEnd     = (int) ($data['slot_end']      ?? 0);
        $mobile      = trim($data['patient_mobile'] ?? '');
        $patientName = trim($data['patient_name']   ?? '');

        if (empty($doctorUuid) || $slotStart <= 0 || $slotEnd <= $slotStart || empty($mobile) || empty($patientName)) {
            return $this->error(ErrorCodes::VALIDATION, 'همه فیلدها الزامی است', 422);
        }

        if ($slotStart < time()) {
            return $this->error(ErrorCodes::SLOT_PAST, 'زمان این اسلات گذشته است', 422);
        }

        $doctor = $this->doctorRepo->findByUuid($doctorUuid);
        if (!$doctor) return $this->error(ErrorCodes::DOCTOR_NOT_FOUND, 'پزشک یافت نشد', 404);

        if (!$this->canBookForDoctor($user, $doctor)) {
            return $this->error(ErrorCodes::FORBIDDEN, 'برای این پزشک مجاز به ثبت نوبت نیستید', 403);
        }

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

        $appointment = new Appointment($doctor, $patient, $slotStart, $slotEnd);
        if (!empty($data['note'])) $appointment->setNote($data['note']);
        $locationId = $this->slotCalculator->resolveSlotLocationId($doctor, $slotStart);
        if ($locationId !== null) $appointment->setAddressId($locationId);

        try {
            $this->appointmentRepo->bookAtomically($appointment);
        } catch (SlotTakenException) {
            return $this->error(ErrorCodes::SLOT_TAKEN, 'این نوبت قبلاً رزرو شده است', 409);
        }

        return $this->success([
            'uuid'       => $appointment->getUuid(),
            'slot_start' => $slotStart,
            'slot_end'   => $slotEnd,
            'status'     => $appointment->getStatus(),
        ], 201);
    }

    #[Route('/api/v1/my/appointments', methods: ['GET'])]
    #[IsGranted('IS_AUTHENTICATED_FULLY')]
    public function myAppointments(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $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', ''));
        $doctorUuid = trim((string) $request->query->get('doctor_uuid', ''));

        $qb = $this->em->createQueryBuilder()
            ->select(
                'DISTINCT 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')
            ->orderBy('a.slotStart', 'ASC');

        $roles = $user->getRoles();

        if (in_array('ROLE_ADMIN', $roles, true)) {
            // Admin sees all
        } elseif (in_array('ROLE_CLINIC', $roles, true)) {
            $clinic = $this->clinicRepo->findByUser($user);
            if ($clinic === null) {
                return $this->paginated([], 0, $page, $limit);
            }
            $qb->join('App\Clinic\Entity\Clinic', 'c', 'WITH', 'd MEMBER OF c.doctors')
               ->andWhere('c = :clinic')
               ->setParameter('clinic', $clinic);
        } elseif (in_array('ROLE_DOCTOR', $roles, true)) {
            $doctor = $this->doctorRepo->findByUser($user);
            if ($doctor === null) {
                return $this->paginated([], 0, $page, $limit);
            }
            $qb->andWhere('a.doctor = :doctor')
               ->setParameter('doctor', $doctor);
        } elseif (in_array('ROLE_SECRETARY', $roles, true)) {
            $filter = $this->resolveSecretaryFilter($user);
            if ($filter === null) {
                return $this->paginated([], 0, $page, $limit);
            }
            [$filterType, $filterValue, $canView] = $filter;
            if (!$canView) {
                return $this->paginated([], 0, $page, $limit);
            }
            if ($filterType === 'clinic') {
                $qb->join('App\Clinic\Entity\Clinic', 'c', 'WITH', 'd MEMBER OF c.doctors')
                   ->andWhere('c = :clinic')
                   ->setParameter('clinic', $filterValue);
            } else {
                $qb->andWhere('a.doctor = :doctor')
                   ->setParameter('doctor', $filterValue);
            }
        } else {
            // Plain patient: only their own appointments.
            $qb->andWhere('a.user = :patient')
               ->setParameter('patient', $user);
        }

        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);
        }
        if ($doctorUuid !== '') {
            $qb->andWhere('d.uuid = :doctorUuid')->setParameter('doctorUuid', $doctorUuid);
        }

        $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'],
            'version'          => (int) $a['version'],
            'created_at'       => date('c', (int) $a['createdAt']),
        ], $rows);

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

    #[Route('/api/v1/my/appointments/today-stats', methods: ['GET'])]
    #[IsGranted('IS_AUTHENTICATED_FULLY')]
    public function todayStats(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $date = trim((string) $request->query->get('date', date('Y-m-d')));
        if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
            $date = date('Y-m-d');
        }
        $dayStart = (int) strtotime($date . ' 00:00:00');
        $dayEnd   = (int) strtotime($date . ' 23:59:59');

        $qb = $this->em->createQueryBuilder()
            ->select('a.status, COUNT(a.id) AS cnt')
            ->from(Appointment::class, 'a')
            ->join('a.doctor', 'd')
            ->where('a.slotStart >= :dayStart AND a.slotStart <= :dayEnd')
            ->setParameter('dayStart', $dayStart)
            ->setParameter('dayEnd', $dayEnd)
            ->groupBy('a.status');

        $roles = $user->getRoles();
        if (in_array('ROLE_CLINIC', $roles, true)) {
            $clinic = $this->clinicRepo->findByUser($user);
            if ($clinic) {
                $qb->join('App\Clinic\Entity\Clinic', 'c', 'WITH', 'd MEMBER OF c.doctors')
                   ->andWhere('c = :clinic')
                   ->setParameter('clinic', $clinic);
            }
        } elseif (in_array('ROLE_DOCTOR', $roles, true)) {
            $doctor = $this->doctorRepo->findByUser($user);
            if ($doctor) {
                $qb->andWhere('a.doctor = :doctor')->setParameter('doctor', $doctor);
            }
        } elseif (in_array('ROLE_SECRETARY', $roles, true)) {
            $filter = $this->resolveSecretaryFilter($user);
            if ($filter !== null) {
                [$filterType, $filterValue] = $filter;
                if ($filterType === 'clinic') {
                    $qb->join('App\Clinic\Entity\Clinic', 'c', 'WITH', 'd MEMBER OF c.doctors')
                       ->andWhere('c = :clinic')
                       ->setParameter('clinic', $filterValue);
                } else {
                    $qb->andWhere('a.doctor = :doctor')->setParameter('doctor', $filterValue);
                }
            }
        }

        $rows = $qb->getQuery()->getArrayResult();
        $byStatus = [];
        foreach ($rows as $row) {
            $byStatus[$row['status']] = (int) $row['cnt'];
        }

        $total     = array_sum($byStatus);
        $completed = ($byStatus['completed'] ?? 0);
        $cancelled = ($byStatus['cancelled_by_doctor'] ?? 0)
                   + ($byStatus['cancelled_by_user'] ?? 0)
                   + ($byStatus['no_show'] ?? 0)
                   + ($byStatus['expired'] ?? 0);
        $waiting   = $total - $completed - $cancelled;

        return $this->success([
            'total'     => $total,
            'completed' => $completed,
            'waiting'   => max(0, $waiting),
            'cancelled' => $cancelled,
        ]);
    }

    /**
     * Whether the acting user is allowed to book onto this doctor's calendar.
     * The role gate alone is not enough: a doctor/clinic/secretary must be
     * scoped to the target doctor, otherwise any staff user could book onto
     * any doctor's calendar by passing an arbitrary doctor_uuid.
     */
    private function canBookForDoctor(User $user, Doctor $doctor): bool
    {
        $roles = $user->getRoles();

        if (in_array('ROLE_ADMIN', $roles, true)) {
            return true;
        }

        if (in_array('ROLE_CLINIC', $roles, true)) {
            $clinic = $this->clinicRepo->findByUser($user);
            if ($clinic !== null && $clinic->getDoctors()->contains($doctor)) {
                return true;
            }
        }

        if (in_array('ROLE_DOCTOR', $roles, true)) {
            $own = $this->doctorRepo->findByUser($user);
            if ($own !== null && $own->getId() === $doctor->getId()) {
                return true;
            }
        }

        if (in_array('ROLE_SECRETARY', $roles, true) && $this->secretaryCanBookForDoctor($user, $doctor)) {
            return true;
        }

        return false;
    }

    private function secretaryCanBookForDoctor(User $user, Doctor $doctor): bool
    {
        $dbUuid = $this->contextRepo->findByUser($user)?->getDbUuid();
        if ($dbUuid === null) {
            return false;
        }

        $clinic = $this->clinicRepo->findByUuid($dbUuid);
        if ($clinic !== null) {
            if (!$clinic->getDoctors()->contains($doctor)) {
                return false;
            }
            $rel = $this->secretaryRepo->findActiveBySecretaryForClinic($user, $clinic);
            return $rel !== null && (bool) ($rel->getPermissions()['resources']['appointments']['create'] ?? false);
        }

        $scopeDoctor = $this->doctorRepo->findByUuid($dbUuid);
        if ($scopeDoctor !== null && $scopeDoctor->getId() === $doctor->getId()) {
            $rel = $this->secretaryRepo->findActiveBySecretaryForDoctor($user, $scopeDoctor);
            return $rel !== null && (bool) ($rel->getPermissions()['resources']['appointments']['create'] ?? false);
        }

        return false;
    }

    /**
     * تعیین فیلتر نوبت‌ها برای منشی بر اساس scope فعال:
     * Returns [type, entity, canView] یا null اگر رابطه‌ای پیدا نشد.
     * type: 'clinic' | 'doctor'
     * entity: Clinic | Doctor
     */
    private function resolveSecretaryFilter(User $user): ?array
    {
        $activeCtx = $this->contextRepo->findByUser($user);
        $dbUuid    = $activeCtx?->getDbUuid();

        if ($dbUuid === null) {
            return null;
        }

        // بررسی scope کلینیک
        $clinic = $this->clinicRepo->findByUuid($dbUuid);
        if ($clinic !== null) {
            $rel = $this->secretaryRepo->findActiveBySecretaryForClinic($user, $clinic);
            if ($rel === null) return null;
            $canView = (bool) ($rel->getPermissions()['resources']['appointments']['view'] ?? false);
            return ['clinic', $clinic, $canView];
        }

        // بررسی scope مطب شخصی
        $doctor = $this->doctorRepo->findByUuid($dbUuid);
        if ($doctor !== null) {
            $rel = $this->secretaryRepo->findActiveBySecretaryForDoctor($user, $doctor);
            if ($rel === null) return null;
            $canView = (bool) ($rel->getPermissions()['resources']['appointments']['view'] ?? false);
            return ['doctor', $doctor, $canView];
        }

        return null;
    }
}
