<?php

namespace App\Insurance\Service;

use App\ClinicService\Repository\ServiceItemRepository;
use App\Insurance\Entity\TenantInsurance;
use App\Insurance\Repository\InsuranceRepository;
use App\Insurance\Repository\TenantInsuranceRepository;
use App\Insurance\Repository\TenantServiceCoverageRepository;
use App\Insurance\ValueObject\CoverageRule;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Exception\AppException;

class TenantInsuranceService
{
    public function __construct(
        private readonly TenantInsuranceRepository        $repo,
        private readonly InsuranceRepository              $insuranceRepo,
        private readonly TenantServiceCoverageRepository  $coverageRepo,
        private readonly ServiceItemRepository            $serviceItemRepo,
    ) {}

    /**
     * فعال‌سازی یا به‌روزرسانی قرارداد بیمه برای یک tenant.
     * اگر قرارداد فعالی موجود باشد، همان ویرایش می‌شود؛ در غیر این صورت نسخه‌ی جدید ساخته می‌شود.
     */
    public function activate(
        string $entityType,
        int $entityId,
        int $insuranceId,
        float $coveragePercent,
        int $franchiseRials = 0,
        ?int $annualCeilingRials = null,
    ): TenantInsurance {
        if ($this->insuranceRepo->find($insuranceId) === null) {
            throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, 'بیمه یافت نشد', 404);
        }

        $contract = $this->repo->findActiveContract($entityType, $entityId, $insuranceId);
        if ($contract === null) {
            $version  = $this->repo->latestVersion($entityType, $entityId, $insuranceId) + 1;
            $contract = new TenantInsurance($entityType, $entityId, $insuranceId, $version);
        }

        $contract->setCoveragePercent($coveragePercent)
            ->setFranchiseRials($franchiseRials)
            ->setAnnualCeilingRials($annualCeilingRials)
            ->setActive(true);

        $this->repo->save($contract);

        return $contract;
    }

    public function deactivate(TenantInsurance $contract): void
    {
        $contract->setActive(false)->setEffectiveTo(time());
        $this->repo->save($contract);
    }

    /**
     * بررسی فعال‌بودن یک بیمه برای tenant. در پذیرش/صورتحساب استفاده می‌شود.
     */
    public function assertActive(string $entityType, int $entityId, int $insuranceId): TenantInsurance
    {
        $contract = $this->repo->findActiveContract($entityType, $entityId, $insuranceId);
        if ($contract === null) {
            throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'این بیمه برای این کلینیک/پزشک فعال نیست', 422);
        }
        return $contract;
    }

    /**
     * قانون پوشش یک بیمه برای tenant جاری (برای BillingCalculator).
     * اگر قرارداد فعالی نباشد، notCovered برمی‌گردد.
     */
    public function coverageRule(string $entityType, int $entityId, ?int $insuranceId): CoverageRule
    {
        if ($insuranceId === null) {
            return CoverageRule::notCovered();
        }

        $contract = $this->repo->findActiveContract($entityType, $entityId, $insuranceId);
        if ($contract === null) {
            return CoverageRule::notCovered();
        }

        return new CoverageRule(
            coveragePercent: $contract->getCoveragePercent(),
            franchiseRials:  $contract->getFranchiseRials(),
            ceilingRials:    $contract->getAnnualCeilingRials(),
            covered:         true,
        );
    }

    /**
     * قانون پوشش یک خدمت خاص تحت بیمه‌ی tenant.
     * اگر override خدمت موجود باشد اعمال می‌شود؛ فیلدهای null از قرارداد ارث می‌برند.
     */
    public function coverageRuleForService(string $entityType, int $entityId, ?int $insuranceId, int $serviceItemId): CoverageRule
    {
        if ($insuranceId === null) {
            return CoverageRule::notCovered();
        }

        // پرچم سرویس gate نهایی است: اگر «این خدمت شامل بیمه می‌شود» غیرفعال باشد، هیچ پوششی محاسبه نمی‌شود.
        $service = $this->serviceItemRepo->find($serviceItemId);
        if ($service === null || !$service->isInsuranceCovered()) {
            return CoverageRule::notCovered();
        }

        $contract = $this->repo->findActiveContract($entityType, $entityId, $insuranceId);
        if ($contract === null) {
            return CoverageRule::notCovered();
        }

        $override = $this->coverageRepo->findOneFor($contract->getId(), $serviceItemId);
        if ($override !== null && !$override->isCovered()) {
            return CoverageRule::notCovered();
        }

        return new CoverageRule(
            coveragePercent: $override?->getCoveragePercent() ?? $contract->getCoveragePercent(),
            franchiseRials:  $override?->getFranchiseRials()  ?? $contract->getFranchiseRials(),
            ceilingRials:    $override?->getCeilingRials()    ?? $contract->getAnnualCeilingRials(),
            covered:         true,
        );
    }

    /** @return array{covered: bool, percent: float|null, franchise: int|null, ceiling: int|null}|null */
    public function getServiceCoverage(int $tenantInsuranceId, int $serviceItemId): ?array
    {
        $override = $this->coverageRepo->findOneFor($tenantInsuranceId, $serviceItemId);
        return $override?->toArray();
    }

    public function setServiceCoverage(
        TenantInsurance $contract,
        int $serviceItemId,
        bool $covered,
        ?float $coveragePercent,
        ?int $franchiseRials,
        ?int $ceilingRials,
    ): void {
        $override = $this->coverageRepo->findOneFor($contract->getId(), $serviceItemId)
            ?? new \App\Insurance\Entity\TenantServiceCoverage($contract->getId(), $serviceItemId);

        $override->setCovered($covered)
            ->setCoveragePercent($coveragePercent)
            ->setFranchiseRials($franchiseRials)
            ->setCeilingRials($ceilingRials);

        $this->coverageRepo->save($override);
    }
}
