<?php

namespace App\Billing\Service;

use App\Billing\Entity\Invoice;
use App\Billing\Entity\InvoiceItem;
use App\Billing\Event\InvoiceFinalized;
use App\Billing\Repository\InvoiceRepository;
use App\Billing\ValueObject\Money;
use App\ClinicService\Service\TariffService;
use App\Insurance\Enum\ServiceCategory;
use App\Insurance\Repository\InsuranceRepository;
use App\Insurance\Service\TenantInsuranceService;
use App\Patient\Entity\PatientSession;
use App\Patient\Repository\PatientSessionRepository;
use Psr\EventDispatcher\EventDispatcherInterface;

class InvoiceService
{
    public function __construct(
        private readonly InvoiceRepository        $invoiceRepo,
        private readonly TariffService            $tariffService,
        private readonly TenantInsuranceService   $tenantInsuranceService,
        private readonly BillingCalculator        $calculator,
        private readonly PatientSessionRepository $sessionRepo,
        private readonly InsuranceRepository      $insuranceRepo,
        private readonly EventDispatcherInterface $events,
    ) {}

    /** @var array<int, string|null> نام بیمه‌ها، یک‌بار در هر درخواست. */
    private array $insuranceNameCache = [];

    private function insuranceName(?int $id): ?string
    {
        if ($id === null) {
            return null;
        }

        return $this->insuranceNameCache[$id] ??= $this->insuranceRepo->find($id)?->getName();
    }

    /**
     * ساخت Invoice از یک Encounter (PatientSession).
     * تعرفه‌ی هر خدمت از Tariff سال جاری (با fallback)، پوشش از قرارداد بیمه‌ی tenant.
     * ویزیت به‌عنوان یک آیتم جداگانه با همان قانون پوشش لحاظ می‌شود.
     */
    public function createFromSession(PatientSession $session, string $entityType, int $entityId): Invoice
    {
        $existing = $session->getId() !== null ? $this->invoiceRepo->findBySession($session->getId()) : null;
        if ($existing !== null) {
            return $existing;
        }

        $invoice = new Invoice($entityType, $entityId);
        $invoice->setPatientSessionId($session->getId())
            ->setPatientRecordId($session->getRecord()->getId())
            ->setBaseInsuranceId($session->getInsuranceBaseId())
            ->setSupplementaryInsuranceId($session->getInsuranceSupplementaryId());

        $baseId = $session->getInsuranceBaseId();
        $suppId = $session->getInsuranceSupplementaryId();

        // ویزیت — با نوع خدمتِ همان مراجعه (سرپایی/بستری)، snapshot روی خودِ فاکتور.
        $visitCategory = $session->getInsuranceServiceCategory() ?? ServiceCategory::Outpatient;
        $invoice->setServiceCategory($session->getInsuranceServiceCategory());

        $visitPrice = $session->getVisitPriceRials();
        if ($visitPrice > 0) {
            $baseRule = $this->tenantInsuranceService->coverageRule($entityType, $entityId, $baseId, $visitCategory);
            $suppRule = $this->tenantInsuranceService->coverageRule($entityType, $entityId, $suppId, $visitCategory);
            $breakdown = $this->calculator->calculateItem(new Money($visitPrice), $baseRule, $suppRule);
            $invoice->addItem(new InvoiceItem($invoice, 'ویزیت', $visitPrice, 1, $breakdown, null));
        }

        // خدمات
        foreach ($session->getServices() as $sessionService) {
            $item       = $sessionService->getServiceItem();
            $qty        = max(1, $sessionService->getQuantity());
            $unitPrice  = $this->tariffService->resolvePrice($item);
            $total      = new Money($unitPrice * $qty);

            $baseRule = $this->tenantInsuranceService->coverageRuleForService($entityType, $entityId, $baseId, $item->getId());
            $suppRule = $this->tenantInsuranceService->coverageRuleForService($entityType, $entityId, $suppId, $item->getId());

            $breakdown = $this->calculator->calculateItem($total, $baseRule, $suppRule);
            $invoice->addItem(new InvoiceItem($invoice, $item->getName(), $unitPrice, $qty, $breakdown, $item->getId()));
        }

        $invoice->recalculateTotals();
        $this->invoiceRepo->save($invoice);

        return $invoice;
    }

    /** نهایی‌سازی، و اعلامش به مصرف‌کننده‌های اثر جانبی (ساخت مطالبهٔ بیمه). */
    public function finalize(Invoice $invoice): void
    {
        $invoice->finalize();
        $this->invoiceRepo->save($invoice);

        $this->events->dispatch(new InvoiceFinalized($invoice));
    }

    /**
     * Invoice detail enriched with its source encounter (`session` key):
     * payments, consumables, discount and paid totals for the invoice summary
     * view. `session` is null for invoices not created from a session.
     */
    public function detailWithSession(Invoice $invoice): array
    {
        $data = $invoice->toArray();

        $sessionId = $data['patient_session_id'];
        $session   = $sessionId !== null ? $this->sessionRepo->find($sessionId) : null;
        $data['session'] = $session?->toArray();

        // نام بیمه‌ها برای چاپ روی فاکتور؛ فاکتور فقط شناسه را نگه می‌دارد.
        $data['base_insurance_name']          = $this->insuranceName($invoice->getBaseInsuranceId());
        $data['supplementary_insurance_name'] = $this->insuranceName($invoice->getSupplementaryInsuranceId());

        return $data;
    }

    /**
     * Paginated flat list of a tenant's recorded (finalized/paid) invoices for
     * the payments list (node 1). Rows arrive ready-shaped from the repository;
     * this only pairs them with the total for pagination.
     *
     * @param array{national_code?:?string,status?:?string,from?:?int,to?:?int} $filters
     * @return array{items: list<array<string, mixed>>, total: int}
     */
    public function tenantInvoiceList(string $entityType, int $entityId, array $filters, int $page, int $limit): array
    {
        return [
            'items' => $this->invoiceRepo->tenantInvoices($entityType, $entityId, $filters, $page, $limit),
            'total' => $this->invoiceRepo->countTenantInvoices($entityType, $entityId, $filters),
        ];
    }

    /**
     * Financial summary of the same filtered invoice set as {@see tenantInvoiceList},
     * used by the payments page stat cards.
     *
     * @param array{national_code?:?string,status?:?string,from?:?int,to?:?int} $filters
     * @return array{total_rials:int,paid_rials:int,unsettled_rials:int,invoices_count:int}
     */
    public function tenantInvoiceSummary(string $entityType, int $entityId, array $filters): array
    {
        return $this->invoiceRepo->tenantInvoiceSummary($entityType, $entityId, $filters);
    }

    /**
     * A patient's recorded invoices, shaped for the detail table: number, issue
     * time, a single service title (first item, "+ more" when several), total,
     * a two-state status (paid|unsettled), and the full item breakdown.
     *
     * @return array{items: list<array<string, mixed>>, total: int, summary: array{total_rials:int,paid_rials:int,unsettled_rials:int,invoices_count:int}}
     */
    public function patientInvoiceList(string $entityType, int $entityId, int $recordId, int $page, int $limit): array
    {
        $invoices     = $this->invoiceRepo->invoicesForPatient($entityType, $entityId, $recordId, $page, $limit);
        $paymentsById = $this->invoiceRepo->paymentsForInvoices($invoices);

        $items = array_map(function (Invoice $invoice) use ($paymentsById): array {
            $lineItems = array_map(static fn(InvoiceItem $i) => $i->toArray(), $invoice->getItems()->toArray());
            $title = match (count($lineItems)) {
                0       => null,
                1       => $lineItems[0]['title'],
                default => $lineItems[0]['title'] . ' و موارد دیگر',
            };

            $payments = $paymentsById[$invoice->getId()] ?? [];
            $paid     = array_sum(array_column($payments, 'amount_rials'));

            return [
                'uuid'          => $invoice->getUuid(),
                'number'        => $invoice->getId(),
                'issued_at'     => $invoice->getIssuedAt(),
                'total_rials'   => $invoice->getTotalRials(),
                'patient_rials' => $invoice->getPatientRials(),
                'paid_rials'    => $paid,
                'status'        => InvoicePaymentStatus::resolve($invoice->getPatientRials(), $paid),
                'service_title' => $title,
                'items'         => $lineItems,
                'payments'      => $payments,
            ];
        }, $invoices);

        $summary = $this->invoiceRepo->patientInvoiceSummary($entityType, $entityId, $recordId);

        return [
            'items'   => $items,
            'total'   => $summary['invoices_count'],
            'summary' => $summary,
        ];
    }
}
