<?php

namespace App\Patient\Controller;

use App\Auth\Entity\User;
use App\Auth\Repository\UserActiveContextRepository;
use App\Auth\Repository\UserRepository;
use App\Clinic\Repository\ClinicRepository;
use App\Doctor\Repository\DoctorRepository;
use App\Patient\Entity\PatientRecord;
use App\Secretary\Repository\DoctorSecretaryRepository;
use App\Patient\Repository\PatientRecordRepository;
use App\Patient\Repository\PatientSessionRepository;
use App\Patient\Service\PatientService;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
use App\Shared\Exception\AppException;
use App\Subscription\Service\SubscriptionService;
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: 'Patients')]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
class PatientController extends BaseController
{
    public function __construct(
        private readonly PatientRecordRepository     $recordRepo,
        private readonly PatientSessionRepository    $sessionRepo,
        private readonly PatientService              $patientService,
        private readonly SubscriptionService         $subscriptionService,
        private readonly UserRepository              $userRepo,
        private readonly DoctorRepository            $doctorRepo,
        private readonly ClinicRepository            $clinicRepo,
        private readonly DoctorSecretaryRepository   $secretaryRepo,
        private readonly UserActiveContextRepository $contextRepo,
        private readonly \App\UserProfile\Repository\UserProfileRepository $profileRepo,
        private readonly \App\Insurance\Repository\InsuranceRepository $insuranceRepo,
    ) {}

    private function buildPatientProfile(User $patient): array
    {
        $p = $this->profileRepo->findByUser($patient);
        $insName = function (?int $id): ?string {
            if ($id === null) { return null; }
            return $this->insuranceRepo->find($id)?->getName();
        };

        return [
            'full_name'                  => trim(($patient->getRealName() ?? '') . ' ' . ($p?->getFamily() ?? '')) ?: $patient->getRealName(),
            'national_code'              => $p?->getNationalCode() ?? $patient->getNationalCode(),
            'gender'                     => $p?->getGender(),
            'date_of_birth'              => $p?->getDateOfBirth(),
            'blood_type'                 => $p?->getBloodType(),
            'marital_status'             => $p?->getMaritalStatus(),
            'job'                        => $p?->getJob(),
            'address'                    => $p?->getAddress(),
            'home_phone'                 => $p?->getHomePhone(),
            'work_phone'                 => $p?->getWorkPhone(),
            'mobile'                     => $patient->getMobileNumber(),
            'basic_insurance_id'         => $p?->getBasicInsuranceId(),
            'basic_insurance_name'       => $insName($p?->getBasicInsuranceId()),
            'supplementary_insurance_id' => $p?->getSupplementaryInsuranceId(),
            'supplementary_insurance_name' => $insName($p?->getSupplementaryInsuranceId()),
        ];
    }

    #[Route('/api/v1/patient/search-user', methods: ['GET'])]
    public function searchUser(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        [$entityType, $entityId] = $this->resolveEntity($user);
        $this->assertPatientGate($entityType, $entityId);

        $mobile = trim($request->query->get('mobile', ''));
        if ($mobile === '') {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'mobile الزامی است', 422);
        }

        $patient = $this->userRepo->findByMobile($mobile);
        if ($patient === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'کاربری با این شماره یافت نشد', 404);
        }

        return $this->success([
            'uuid' => $patient->getUuid(),
            'name' => $patient->getRealName(),
            'mobile' => $patient->getMobileNumber(),
            'national_code' => $patient->getNationalCode(),
        ]);
    }

    #[Route('/api/v1/patients', methods: ['GET'])]
    public function list(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        [$entityType, $entityId] = $this->resolveEntity($user);
        $this->assertPatientGate($entityType, $entityId);

        $page   = max(1, (int) $request->query->get('page', 1));
        $limit  = min(50, max(10, (int) $request->query->get('limit', 20)));
        $search = $request->query->get('search') ?: null;

        $records = $this->recordRepo->findByEntity($entityType, $entityId, $page, $limit, $search);
        $total   = $this->recordRepo->countByEntity($entityType, $entityId, $search);

        return $this->paginated(
            array_map(fn(PatientRecord $r) => $r->toArray(), $records),
            $total,
            $page,
            $limit
        );
    }

    #[Route('/api/v1/patient', methods: ['POST'])]
    public function create(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        [$entityType, $entityId] = $this->resolveEntity($user);
        $this->assertPatientGate($entityType, $entityId);

        $data     = json_decode($request->getContent(), true) ?? [];
        $userUuid = trim($data['user_uuid'] ?? '');
        $mobile   = trim($data['mobile'] ?? '');
        $name     = trim($data['name'] ?? '');

        $nationalCode = trim((string) ($data['national_code'] ?? ''));
        if ($nationalCode !== '' && !preg_match('/^\d{10}$/', $nationalCode)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'کد ملی باید ۱۰ رقم باشد', 422);
        }

        $patient = null;
        if ($userUuid !== '') {
            $patient = $this->userRepo->findByUuid($userUuid);
        } elseif ($mobile !== '') {
            if (!preg_match('/^09\d{9}$/', $mobile)) {
                return $this->error(ErrorCodes::ERR_VALIDATION_001, 'شماره موبایل نامعتبر است', 422);
            }
            $patient = $this->userRepo->findByMobile($mobile);
        }

        // بیمار جدید بدون ثبت‌نام قبلی: موبایل + نام آمده ولی کاربری وجود ندارد
        if ($patient === null) {
            if ($mobile === '' || $name === '') {
                return $this->error(ErrorCodes::ERR_VALIDATION_001, 'برای ساخت بیمار جدید، شماره موبایل و نام الزامی است', 422);
            }
            $patient = new User($mobile);
            $patient->setRealName($name);
            if ($nationalCode !== '') {
                $patient->setNationalCode($nationalCode);
            }
            $this->userRepo->save($patient);
        } elseif ($nationalCode !== '' && $patient->getNationalCode() === null) {
            $patient->setNationalCode($nationalCode);
            $this->userRepo->save($patient);
        }

        $existing = $this->recordRepo->findByEntityAndUser($entityType, $entityId, $patient);
        if ($existing !== null) {
            return $this->success($existing->toArray());
        }

        $record = new PatientRecord($entityType, $entityId, $patient, $user->hasRole('ROLE_DOCTOR') ? 'doctor' : 'clinic', $entityId);
        $this->recordRepo->save($record);

        return $this->success($record->toArray(), 201);
    }

    #[Route('/api/v1/patient/{uuid}', methods: ['GET'])]
    public function show(string $uuid, #[CurrentUser] User $user): JsonResponse
    {
        [$entityType, $entityId] = $this->resolveEntity($user);
        $this->assertPatientGate($entityType, $entityId);

        $record = $this->recordRepo->findByUuid($uuid);
        if ($record === null || !$this->ownsRecord($record, $entityType, $entityId)) {
            return $this->error(ErrorCodes::ERR_PATIENT_NOT_FOUND, ErrorCodes::message(ErrorCodes::ERR_PATIENT_NOT_FOUND), 404);
        }

        $data = $record->toArray();
        $data['profile'] = $this->buildPatientProfile($record->getUser());

        return $this->success($data);
    }

    #[Route('/api/v1/patient/{uuid}/sessions', methods: ['GET'])]
    public function sessions(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
    {
        [$entityType, $entityId] = $this->resolveEntity($user);
        $this->assertPatientGate($entityType, $entityId);

        $record = $this->recordRepo->findByUuid($uuid);
        if ($record === null || !$this->ownsRecord($record, $entityType, $entityId)) {
            return $this->error(ErrorCodes::ERR_PATIENT_NOT_FOUND, ErrorCodes::message(ErrorCodes::ERR_PATIENT_NOT_FOUND), 404);
        }

        $page     = max(1, (int) $request->query->get('page', 1));
        $limit    = min(50, max(10, (int) $request->query->get('limit', 20)));
        $sessions = $this->sessionRepo->findByRecord($record, $page, $limit);
        $total    = $this->sessionRepo->countByRecord($record);

        return $this->paginated(
            array_map(fn($s) => $s->toArray(), $sessions),
            $total,
            $page,
            $limit
        );
    }

    #[Route('/api/v1/patient/{uuid}/session', methods: ['POST'])]
    public function createSession(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
    {
        [$entityType, $entityId] = $this->resolveEntity($user);
        $this->assertPatientGate($entityType, $entityId);

        $record = $this->recordRepo->findByUuid($uuid);
        if ($record === null || !$this->ownsRecord($record, $entityType, $entityId)) {
            return $this->error(ErrorCodes::ERR_PATIENT_NOT_FOUND, ErrorCodes::message(ErrorCodes::ERR_PATIENT_NOT_FOUND), 404);
        }

        $data    = json_decode($request->getContent(), true) ?? [];
        $session = $this->patientService->createSession($record, $data, $entityType, $entityId);

        return $this->success($session->toArray(), 201);
    }

    #[Route('/api/v1/session/{uuid}', methods: ['PATCH'])]
    public function updateSession(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
    {
        [$entityType, $entityId] = $this->resolveEntity($user);

        $session = $this->sessionRepo->findByUuid($uuid);
        if ($session === null || !$this->ownsRecord($session->getRecord(), $entityType, $entityId)) {
            return $this->error(ErrorCodes::ERR_SESSION_NOT_FOUND, ErrorCodes::message(ErrorCodes::ERR_SESSION_NOT_FOUND), 404);
        }

        $data = json_decode($request->getContent(), true) ?? [];

        if (isset($data['notes']))          { $session->setNotes($data['notes']); }
        if (isset($data['payment_method'])) { $session->setPaymentMethod($data['payment_method']); }

        $this->sessionRepo->save($session);

        return $this->success($session->toArray());
    }

    private function resolveEntity(User $user): array
    {
        if ($user->hasRole('ROLE_DOCTOR')) {
            $doctor = $this->doctorRepo->findByUser($user);
            return $doctor !== null ? ['doctor', $doctor->getId()] : ['doctor', null];
        }

        if ($user->hasRole('ROLE_CLINIC')) {
            $clinic = $this->clinicRepo->findByUser($user);
            return $clinic !== null ? ['clinic', $clinic->getId()] : ['clinic', null];
        }

        if ($user->hasRole('ROLE_SECRETARY')) {
            $dbUuid = $this->contextRepo->findByUser($user)?->getDbUuid();
            if ($dbUuid !== null) {
                $clinic = $this->clinicRepo->findByUuid($dbUuid);
                if ($clinic !== null) {
                    $rel = $this->secretaryRepo->findActiveBySecretaryForClinic($user, $clinic);
                    if ($rel !== null) {
                        return ['clinic', $clinic->getId()];
                    }
                }
                $doctor = $this->doctorRepo->findByUuid($dbUuid);
                if ($doctor !== null) {
                    $rel = $this->secretaryRepo->findActiveBySecretaryForDoctor($user, $doctor);
                    if ($rel !== null) {
                        return ['doctor', $doctor->getId()];
                    }
                }
            }
        }

        return ['unknown', null];
    }

    private function assertPatientGate(string $entityType, ?int $entityId): void
    {
        if ($entityId === null) {
            throw new AppException(ErrorCodes::ERR_FORBIDDEN_001, null, 403);
        }

        if (!$this->subscriptionService->hasFeature($entityType, $entityId, 'patient_records')) {
            throw new AppException(ErrorCodes::ERR_SUBSCRIPTION_REQUIRED, null, 403);
        }
    }

    private function ownsRecord($record, string $entityType, ?int $entityId): bool
    {
        return $entityId !== null
            && $record->getEntityType() === $entityType
            && $record->getEntityId() === $entityId;
    }
}
