<?php

namespace App\Billing\Service;

use App\Billing\Contract\ClaimSubmitterInterface;
use App\Billing\Entity\Claim;
use App\Billing\Entity\ClaimItem;
use App\Billing\Entity\Invoice;
use App\Billing\Repository\ClaimRepository;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Exception\AppException;

class ClaimService
{
    public function __construct(
        private readonly ClaimRepository          $claimRepo,
        private readonly ClaimSubmitterInterface  $submitter,
    ) {}

    /**
     * ساخت مطالبات از یک صورتحساب نهایی‌شده.
     * یک Claim برای بیمه‌ی پایه و یک Claim برای بیمه‌ی مکمل (در صورت وجود سهم).
     * @return Claim[]
     */
    public function createFromInvoice(Invoice $invoice): array
    {
        if ($invoice->getStatus() !== Invoice::STATUS_FINALIZED) {
            throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'فقط از صورتحساب نهایی‌شده می‌توان مطالبه ساخت', 422);
        }

        if ($invoice->getId() !== null && $this->claimRepo->existsForInvoice($invoice->getId())) {
            throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'برای این صورتحساب قبلاً مطالبه ثبت شده است', 422);
        }

        $claims = [];

        $baseId = $invoice->getBaseInsuranceId();
        if ($baseId !== null) {
            $claim = $this->buildClaim($invoice, $baseId, Claim::KIND_BASE);
            if ($claim !== null) {
                $claims[] = $claim;
            }
        }

        $suppId = $invoice->getSupplementaryInsuranceId();
        if ($suppId !== null) {
            $claim = $this->buildClaim($invoice, $suppId, Claim::KIND_SUPPLEMENTARY);
            if ($claim !== null) {
                $claims[] = $claim;
            }
        }

        if ($claims === []) {
            throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'سهم بیمه‌ای برای این صورتحساب وجود ندارد', 422);
        }

        foreach ($claims as $claim) {
            $this->claimRepo->save($claim, false);
        }
        $this->claimRepo->getEntityManager()->flush();

        return $claims;
    }

    private function buildClaim(Invoice $invoice, int $insuranceId, string $kind): ?Claim
    {
        $claim = new Claim($invoice->getEntityType(), $invoice->getEntityId(), $insuranceId, $kind);

        $hasShare = false;
        foreach ($invoice->getItems() as $item) {
            $share = $kind === Claim::KIND_BASE
                ? $item->getBaseInsuranceRials()
                : $item->getSupplementaryRials();

            if ($share > 0) {
                $claim->addItem(new ClaimItem($claim, $item->getId(), $share));
                $hasShare = true;
            }
        }

        return $hasShare ? $claim : null;
    }

    public function transition(Claim $claim, string $target, array $opts = []): void
    {
        if (!$claim->canTransitionTo($target)) {
            throw new AppException(
                ErrorCodes::ERR_VALIDATION_001,
                sprintf('انتقال از «%s» به «%s» مجاز نیست', $claim->getStatus(), $target),
                422
            );
        }

        match ($target) {
            Claim::STATUS_SUBMITTED => $this->doSubmit($claim),
            Claim::STATUS_APPROVED  => $claim->approve($opts['approved_rials'] ?? null),
            Claim::STATUS_REJECTED  => $claim->reject($opts['reason'] ?? ''),
            Claim::STATUS_PAID      => $claim->pay($opts['paid_rials'] ?? null),
            default                 => throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'وضعیت نامعتبر', 422),
        };

        $this->claimRepo->save($claim);
    }

    private function doSubmit(Claim $claim): void
    {
        $result = $this->submitter->submit($claim);
        if (!$result->success) {
            throw new AppException(ErrorCodes::ERR_VALIDATION_001, $result->errorMessage ?? 'ارسال مطالبه ناموفق بود', 422);
        }
        $claim->submit();
    }
}
