<?php

namespace App\Billing\Controller;

use App\Auth\Entity\User;
use App\Billing\Entity\Claim;
use App\Billing\Repository\ClaimRepository;
use App\Billing\Repository\InvoiceItemRepository;
use App\Billing\Repository\InvoiceRepository;
use App\Billing\Service\ClaimService;
use App\Billing\Service\InvoiceService;
use App\Clinic\Repository\ClinicRepository;
use App\Doctor\Repository\DoctorRepository;
use App\Insurance\Repository\InsuranceRepository;
use App\Patient\Repository\PatientSessionRepository;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
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: 'Billing')]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
class BillingController extends BaseController
{
    public function __construct(
        private readonly InvoiceService            $invoiceService,
        private readonly InvoiceRepository          $invoiceRepo,
        private readonly InvoiceItemRepository      $invoiceItemRepo,
        private readonly ClaimService               $claimService,
        private readonly ClaimRepository            $claimRepo,
        private readonly PatientSessionRepository   $sessionRepo,
        private readonly DoctorRepository           $doctorRepo,
        private readonly ClinicRepository           $clinicRepo,
        private readonly InsuranceRepository        $insuranceRepo,
        private readonly \App\Secretary\Repository\DoctorSecretaryRepository $secretaryRepo,
        private readonly \App\Auth\Repository\UserActiveContextRepository $contextRepo,
    ) {}

    /**
     * مطالبات را با نام بیمه و جزئیات هر آیتم (نوع/نام، تاریخ مراجعه، تعداد) غنی می‌کند.
     * @param Claim[] $claims
     */
    private function enrichClaims(array $claims): array
    {
        // جمع‌آوری همه‌ی invoice_item_idها در یک رفت‌وآمد
        $invoiceItemIds = [];
        foreach ($claims as $claim) {
            foreach ($claim->getItems() as $ci) {
                $invoiceItemIds[] = $ci->getInvoiceItemId();
            }
        }
        $itemDetails = $this->invoiceItemRepo->detailsForIds(array_values(array_unique($invoiceItemIds)));

        $sessionIds = [];
        foreach ($itemDetails as $d) {
            if ($d['patient_session_id'] !== null) {
                $sessionIds[] = $d['patient_session_id'];
            }
        }
        $uniqueSessionIds = array_values(array_unique($sessionIds));
        $sessionDates = $this->sessionRepo->datesForIds($uniqueSessionIds);
        $patientInfo  = $this->sessionRepo->patientInfoForIds($uniqueSessionIds);

        // Batch-fetch insurance names instead of one find() per claim (N+1).
        $insuranceIds   = array_values(array_unique(array_map(fn(Claim $c) => $c->getInsuranceId(), $claims)));
        $insuranceNames = [];
        if ($insuranceIds !== []) {
            foreach ($this->insuranceRepo->findBy(['id' => $insuranceIds]) as $ins) {
                $insuranceNames[$ins->getId()] = $ins->getName();
            }
        }

        return array_map(function (Claim $claim) use ($itemDetails, $sessionDates, $patientInfo, $insuranceNames) {
            $data = $claim->toArray();
            $data['insurance_name'] = $insuranceNames[$claim->getInsuranceId()] ?? null;

            $patientName = null;
            $patientMobile = null;

            $data['items'] = array_map(function (array $item) use ($itemDetails, $sessionDates, $patientInfo, &$patientName, &$patientMobile) {
                $detail = $itemDetails[$item['invoice_item_id']] ?? null;
                $sessionId = $detail['patient_session_id'] ?? null;
                $item['title']      = $detail['title'] ?? null;
                $item['is_visit']   = $detail !== null && $detail['service_item_id'] === null;
                $item['quantity']   = $detail['quantity'] ?? 1;
                $item['total_rials'] = $detail['total_rials'] ?? null;
                $item['visit_date'] = $sessionId !== null ? ($sessionDates[$sessionId] ?? null) : null;

                if ($patientName === null && $sessionId !== null && isset($patientInfo[$sessionId])) {
                    $patientName   = $patientInfo[$sessionId]['name'];
                    $patientMobile = $patientInfo[$sessionId]['mobile'];
                }
                return $item;
            }, $data['items']);

            $data['patient_name']   = $patientName;
            $data['patient_mobile'] = $patientMobile;

            return $data;
        }, $claims);
    }

    #[Route('/api/v1/billing/invoices', methods: ['POST'])]
    public function create(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        [$entityType, $entityId] = $this->resolveEntity($user);
        if ($entityId === null) {
            return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
        }

        $data        = json_decode($request->getContent(), true) ?? [];
        $sessionUuid = trim($data['session_uuid'] ?? '');
        if ($sessionUuid === '') {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'session_uuid الزامی است', 422);
        }

        $session = $this->sessionRepo->findByUuid($sessionUuid);
        if ($session === null || !$this->ownsSession($session, $entityType, $entityId)) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'مراجعه یافت نشد', 404);
        }

        $invoice = $this->invoiceService->createFromSession($session, $entityType, $entityId);

        return $this->success(['data' => $invoice->toArray()], 201);
    }

    #[Route('/api/v1/billing/invoices/{uuid}', methods: ['GET'])]
    public function show(string $uuid, #[CurrentUser] User $user): JsonResponse
    {
        [$entityType, $entityId] = $this->resolveEntity($user);
        $invoice = $this->invoiceRepo->findByUuid($uuid);
        if ($invoice === null || $invoice->getEntityType() !== $entityType || $invoice->getEntityId() !== $entityId) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'صورتحساب یافت نشد', 404);
        }

        return $this->success(['data' => $invoice->toArray()]);
    }

    #[Route('/api/v1/billing/invoices/{uuid}/finalize', methods: ['POST'])]
    public function finalize(string $uuid, #[CurrentUser] User $user): JsonResponse
    {
        [$entityType, $entityId] = $this->resolveEntity($user);
        $invoice = $this->invoiceRepo->findByUuid($uuid);
        if ($invoice === null || $invoice->getEntityType() !== $entityType || $invoice->getEntityId() !== $entityId) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'صورتحساب یافت نشد', 404);
        }

        $this->invoiceService->finalize($invoice);

        return $this->success(['data' => $invoice->toArray()]);
    }

    // ── Claims (مطالبات بیمه) ──────────────────────────────────────────────────

    #[Route('/api/v1/billing/claims', methods: ['POST'])]
    public function createClaim(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        [$entityType, $entityId] = $this->resolveEntity($user);
        if ($entityId === null) {
            return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
        }

        $data        = json_decode($request->getContent(), true) ?? [];
        $invoiceUuid = trim($data['invoice_uuid'] ?? '');
        if ($invoiceUuid === '') {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'invoice_uuid الزامی است', 422);
        }

        $invoice = $this->invoiceRepo->findByUuid($invoiceUuid);
        if ($invoice === null || $invoice->getEntityType() !== $entityType || $invoice->getEntityId() !== $entityId) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'صورتحساب یافت نشد', 404);
        }

        $claims = $this->claimService->createFromInvoice($invoice);

        return $this->success(['data' => $this->enrichClaims($claims)], 201);
    }

    #[Route('/api/v1/billing/claims', methods: ['GET'])]
    public function listClaims(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        [$entityType, $entityId] = $this->resolveEntity($user);
        if ($entityId === null) {
            return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
        }

        $filters = [
            'status'       => $request->query->get('status') ?: null,
            'insurance_id' => $request->query->get('insurance_id') ?: null,
            'from'         => $request->query->get('from') ?: null,
            'to'           => $request->query->get('to') ?: null,
            'q'            => $request->query->get('q') ?: null,
        ];

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

        $claims = $this->claimRepo->findByTenant($entityType, $entityId, $filters, $page, $limit);
        $total  = $this->claimRepo->countByTenant($entityType, $entityId, $filters);

        // meta added inside the existing { data: { data: [...] } } envelope so
        // existing clients keep reading data.data unchanged.
        return $this->success([
            'data' => $this->enrichClaims($claims),
            'meta' => [
                'totalRecords' => $total,
                'totalPages'   => (int) ceil($total / $limit),
                'currentPage'  => $page,
            ],
        ]);
    }

    #[Route('/api/v1/billing/claims/{uuid}/{action}', methods: ['POST'], requirements: ['action' => 'submit|approve|reject|pay'])]
    public function transitionClaim(string $uuid, string $action, Request $request, #[CurrentUser] User $user): JsonResponse
    {
        [$entityType, $entityId] = $this->resolveEntity($user);
        $claim = $this->claimRepo->findByUuid($uuid);
        if ($claim === null || $claim->getEntityType() !== $entityType || $claim->getEntityId() !== $entityId) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'مطالبه یافت نشد', 404);
        }

        $data   = json_decode($request->getContent(), true) ?? [];
        $target = match ($action) {
            'submit'  => Claim::STATUS_SUBMITTED,
            'approve' => Claim::STATUS_APPROVED,
            'reject'  => Claim::STATUS_REJECTED,
            'pay'     => Claim::STATUS_PAID,
        };

        if ($action === 'reject' && trim($data['reason'] ?? '') === '') {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'دلیل رد الزامی است', 422);
        }

        // Bound the financial figures: approved/paid cannot be negative, approved
        // cannot exceed the claimed total, and paid cannot exceed approved.
        if ($action === 'approve' && isset($data['approved_rials'])) {
            $approved = (int) $data['approved_rials'];
            if ($approved < 0 || $approved > $claim->getTotalClaimedRials()) {
                return $this->error(ErrorCodes::ERR_VALIDATION_001, 'مبلغ تأییدشده باید بین ۰ و مبلغ مطالبه‌شده باشد', 422, 'approved_rials');
            }
        }
        if ($action === 'pay' && isset($data['paid_rials'])) {
            $paid    = (int) $data['paid_rials'];
            $ceiling = $claim->getTotalApprovedRials() ?? $claim->getTotalClaimedRials();
            if ($paid < 0 || $paid > $ceiling) {
                return $this->error(ErrorCodes::ERR_VALIDATION_001, 'مبلغ پرداختی باید بین ۰ و مبلغ تأییدشده باشد', 422, 'paid_rials');
            }
        }

        $this->claimService->transition($claim, $target, [
            'approved_rials' => isset($data['approved_rials']) ? (int) $data['approved_rials'] : null,
            'paid_rials'     => isset($data['paid_rials']) ? (int) $data['paid_rials'] : null,
            'reason'         => trim($data['reason'] ?? ''),
        ]);

        return $this->success(['data' => $this->enrichClaims([$claim])[0]]);
    }

    #[Route('/api/v1/billing/reports/insurance-debt', methods: ['GET'])]
    public function insuranceDebt(#[CurrentUser] User $user): JsonResponse
    {
        [$entityType, $entityId] = $this->resolveEntity($user);
        if ($entityId === null) {
            return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
        }

        $rows = array_map(function (array $row) {
            $row['insurance_name'] = $this->insuranceRepo->find($row['insurance_id'])?->getName();
            return $row;
        }, $this->claimRepo->debtReport($entityType, $entityId));

        return $this->success(['data' => $rows]);
    }

    private function ownsSession($session, string $entityType, int $entityId): bool
    {
        $record = $session->getRecord();
        return $record->getEntityType() === $entityType && $record->getEntityId() === $entityId;
    }

    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 && $this->secretaryRepo->findActiveBySecretaryForClinic($user, $clinic) !== null) {
                    return ['clinic', $clinic->getId()];
                }
                $doctor = $this->doctorRepo->findByUuid($dbUuid);
                if ($doctor !== null && $this->secretaryRepo->findActiveBySecretaryForDoctor($user, $doctor) !== null) {
                    return ['doctor', $doctor->getId()];
                }
            }
        }
        return ['unknown', null];
    }
}
