<?php

namespace App\Discount\Service;

use App\Discount\Entity\DiscountRule;
use App\Discount\Repository\DiscountRuleRepository;
use App\Patient\Entity\PatientSession;
use App\Patient\Entity\SessionService;
use App\Patient\Repository\PatientSessionRepository;
use App\UserProfile\Repository\UserProfileRepository;

/**
 * موتور تخفیف: برای یک پرونده، قوانین قابل‌اعمالِ owner را ارزیابی و مبلغ تخفیف
 * هرکدام را محاسبه می‌کند. اعمال نهایی در PatientService::applyDiscount انجام می‌شود.
 */
class DiscountEngine
{
    public function __construct(
        private readonly DiscountRuleRepository   $ruleRepo,
        private readonly PatientSessionRepository $sessionRepo,
        private readonly UserProfileRepository    $profileRepo,
    ) {}

    /**
     * @return array<int, array{rule_uuid: string, rule_name: string, type: string, discount_type: string, value: int, discount_rials: int, combinable: bool, priority: int}>
     * مرتب بر priority نزولی (از repository). فقط قوانینِ برقرار با discount_rials > 0.
     */
    public function evaluate(PatientSession $session): array
    {
        $record = $session->getRecord();
        $rules  = $this->ruleRepo->findActiveForOwner($record->getEntityType(), $record->getEntityId());

        $final     = $session->getFinalPriceRials();
        $remaining = max(0, $final - $session->getPaidTotalRials());
        $now       = time();

        $out = [];
        foreach ($rules as $rule) {
            $rials = $this->computeForRule($session, $rule, $now, $remaining);
            if ($rials <= 0) {
                continue;
            }
            $out[] = [
                'rule_uuid'      => $rule->getUuid(),
                'rule_name'      => $rule->getName(),
                'type'           => $rule->getType(),
                'discount_type'  => $rule->getDiscountType(),
                'value'          => $rule->getValue(),
                'discount_rials' => $rials,
                'combinable'     => $rule->isCombinable(),
                'priority'       => $rule->getPriority(),
            ];
        }
        return $out;
    }

    /**
     * مبلغ تخفیف ریالیِ یک قانون برای یک پرونده؛ 0 اگر برقرار نباشد.
     * $remaining را ندهی، از خود session محاسبه می‌شود.
     */
    public function computeForRule(PatientSession $session, DiscountRule $rule, ?int $now = null, ?int $remaining = null): int
    {
        $now ??= time();
        $remaining ??= max(0, $session->getFinalPriceRials() - $session->getPaidTotalRials());
        if (!$this->withinValidity($rule, $now)) {
            return 0;
        }
        $base = $this->applicableBase($rule, $session, $session->getFinalPriceRials(), $now);
        if ($base === null) {
            return 0;
        }
        return min($this->computeRials($rule, $base), $remaining);
    }

    private function withinValidity(DiscountRule $rule, int $now): bool
    {
        if ($rule->getValidFrom() !== null && $now < $rule->getValidFrom()) {
            return false;
        }
        if ($rule->getValidTo() !== null && $now > $rule->getValidTo()) {
            return false;
        }
        return true;
    }

    /**
     * مبنای محاسبه‌ی تخفیف اگر قانون برقرار باشد؛ null یعنی قانون اعمال نمی‌شود.
     */
    private function applicableBase(DiscountRule $rule, PatientSession $session, int $final, int $now): ?int
    {
        $record = $session->getRecord();

        return match ($rule->getType()) {
            DiscountRule::TYPE_PATIENT_TAG => $this->hasTag($session, $rule->getTargetTagUuid()) ? $final : null,

            DiscountRule::TYPE_INVOICE_AMOUNT => ($rule->getMinAmountRials() !== null && $final >= $rule->getMinAmountRials()) ? $final : null,

            DiscountRule::TYPE_SPECIFIC_PATIENT => ($rule->getTargetRecordUuid() !== null && $record->getUuid() === $rule->getTargetRecordUuid()) ? $final : null,

            DiscountRule::TYPE_OCCASION => $this->occasionMatches($rule, $session, $now) ? $final : null,

            DiscountRule::TYPE_SERVICE => $this->serviceBase($session, $rule->getTargetServiceItemUuid()),

            DiscountRule::TYPE_VISIT_COUNT => ($rule->getMinVisitCount() !== null
                && $this->sessionRepo->countByRecord($record) >= $rule->getMinVisitCount()) ? $final : null,

            default => null,
        };
    }

    private function hasTag(PatientSession $session, ?string $tagUuid): bool
    {
        if ($tagUuid === null) {
            return false;
        }
        foreach ($session->getRecord()->getTags() as $tag) {
            if ($tag->getUuid() === $tagUuid) {
                return true;
            }
        }
        return false;
    }

    /** مبنای تخفیف سرویس = جمع خطوطِ همان سرویس؛ null اگر سرویس در پرونده نباشد. */
    private function serviceBase(PatientSession $session, ?string $serviceItemUuid): ?int
    {
        if ($serviceItemUuid === null) {
            return null;
        }
        $sum = 0;
        foreach ($session->getServices() as $line) {
            /** @var SessionService $line */
            if ($line->getServiceItem()->getUuid() === $serviceItemUuid) {
                $sum += $line->getLineTotalRials();
            }
        }
        return $sum > 0 ? $sum : null;
    }

    private function occasionMatches(DiscountRule $rule, PatientSession $session, int $now): bool
    {
        if ($rule->getOccasionKind() !== DiscountRule::OCCASION_BIRTHDAY) {
            // مناسبت مبتنی بر بازه‌ی تاریخی؛ withinValidity قبلاً چک شده.
            return true;
        }
        $profile = $this->profileRepo->findByUser($session->getRecord()->getUser());
        $dob = $profile?->getDateOfBirth();
        if ($dob === null) {
            return false;
        }
        return date('m-d', $dob) === date('m-d', $now);
    }

    private function computeRials(DiscountRule $rule, int $base): int
    {
        if ($rule->getDiscountType() === DiscountRule::DISCOUNT_PERCENT) {
            return (int) round($base * min(100, $rule->getValue()) / 100);
        }
        return min($rule->getValue(), $base);
    }
}
