<?php

namespace App\Settlement\Service;

use App\Auth\Entity\User;
use App\Config\Repository\SiteConfigRepository;
use App\Payment\Entity\Payment;
use App\Representation\Entity\Representation;
use App\Representation\Repository\RepresentationRepository;
use App\Secretary\Entity\SecretaryEarning;
use App\Secretary\Repository\SecretaryEarningRepository;
use App\Secretary\Service\SecretaryShareResolver;
use App\Settlement\Entity\FinancialBreakdown;
use App\Settlement\Entity\WalletTransaction;
use App\Settlement\Repository\FinancialBreakdownRepository;
use App\Settlement\Repository\SettlementRepository;
use App\Settlement\Repository\WalletTransactionRepository;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;

/**
 * موتور تقسیم مالی پس از پرداخت موفق.
 * ترتیب ثابت: ۱) کسر هزینه پنل پیامک  ۲) کسر مالیات از باقی‌مانده  ۳) پورسانت نماینده از خالصِ پس از مالیات.
 */
class CommissionService
{
    public function __construct(
        private readonly SiteConfigRepository         $configRepo,
        private readonly RepresentationRepository     $representationRepo,
        private readonly SettlementRepository         $settlementRepo,
        private readonly WalletTransactionRepository  $walletRepo,
        private readonly FinancialBreakdownRepository $breakdownRepo,
        private readonly SecretaryShareResolver       $secretaryShares,
        private readonly SecretaryEarningRepository   $earningRepo,
        private readonly LoggerInterface              $logger,
        private readonly EntityManagerInterface       $em,
    ) {}

    /**
     * تقسیم مالی نوبت آنلاین: پورسانت نماینده (اگر گاردِ دامنه برقرار باشد) و سهم
     * منشی‌های همان پزشک/کلینیک — هر کدام مستقل. سهم منشی به وجود نماینده گره نیست.
     */
    public function processAppointment(Payment $payment, ?int $doctorRepId, ?int $bookingRepId, ?int $doctorId): void
    {
        // پرداخت دوبار پردازش نشود — قبل از هر اعتبارِ کیف پول.
        if ($this->breakdownRepo->existsForPayment($payment)) return;

        $rep         = $this->eligibleAppointmentRep($doctorRepId, $bookingRepId);
        $secretaries = $this->secretaryShares->for($payment);

        if ($rep === null && $secretaries === []) return;

        $this->settle(
            $payment,
            FinancialBreakdown::SOURCE_APPOINTMENT,
            $rep !== null ? (float) $rep->getCommissionPercent() : 0.0,
            $rep,
            $doctorId,
            null,
            $secretaries,
        );
    }

    /**
     * نمایندهٔ واجد شرط برای پورسانت نوبت. گاردِ دامنه: پزشک باید متعلق به نماینده
     * باشد و نوبت هم از دامنهٔ همان نماینده ثبت شده باشد.
     */
    private function eligibleAppointmentRep(?int $doctorRepId, ?int $bookingRepId): ?Representation
    {
        if ($this->configRepo->get('appointment_commission_enabled') !== '1') return null;
        if ($doctorRepId === null || $bookingRepId === null || $doctorRepId !== $bookingRepId) return null;

        return $this->resolveRep($doctorRepId);
    }

    /**
     * پورسانت ارتقاء اشتراک: درصد سراسری = upgrade_commission_percent.
     * گاردِ دامنه (مثل نوبت): مالک پزشک/کلینیک و نماینده‌ی دامنه‌ی خرید باید یکی باشند.
     */
    public function processSubscription(Payment $payment, ?int $ownerRepId, ?int $bookingRepId, ?int $doctorId, ?int $clinicId): void
    {
        if ($this->configRepo->get('upgrade_commission_enabled') !== '1') return;

        if ($ownerRepId === null || $bookingRepId === null || $ownerRepId !== $bookingRepId) return;

        $rep = $this->resolveRep($ownerRepId);
        if ($rep === null) return;

        $this->settle(
            $payment,
            FinancialBreakdown::SOURCE_SUBSCRIPTION,
            (float) $this->configRepo->get('upgrade_commission_percent'),
            $rep,
            $doctorId,
            $clinicId,
            [],
        );
    }

    private function resolveRep(?int $representationId): ?Representation
    {
        if ($representationId === null) return null;
        $rep = $this->representationRepo->find($representationId);
        return ($rep !== null && $rep->isActive()) ? $rep : null;
    }

    /**
     * @param list<array{user: User, percent: float, relation_uuid: string}> $secretaries
     */
    private function settle(
        Payment $payment,
        string $source,
        float $commissionPercent,
        ?Representation $rep,
        ?int $doctorId,
        ?int $clinicId,
        array $secretaries,
    ): void {
        // پرداخت دوبار پردازش نشود.
        if ($this->breakdownRepo->existsForPayment($payment)) return;

        $gross = $payment->getAmountRials();

        // مرحله ۱: کسر هزینه ثابت پنل پیامک.
        $smsFee   = (int) $this->configRepo->get('sms_panel_fee_rials');
        $afterSms = max(0, $gross - $smsFee);

        // مرحله ۲: مالیاتِ استخراجی از مبلغِ شامل مالیات: tax = amount × p/(100+p).
        $taxEnabled = $this->configRepo->get('tax_enabled') === '1';
        $taxPercent = $taxEnabled ? (float) $this->configRepo->get('tax_percent') : 0.0;
        $taxRials   = ($taxEnabled && $taxPercent > 0)
            ? (int) round($afterSms * $taxPercent / (100 + $taxPercent))
            : 0;
        $netAfterTax = $afterSms - $taxRials;

        // مرحله ۳: سهم‌ها، همه از «خالصِ پس از مالیات» — نه از مبلغ کل و نه از
        // باقی‌ماندهٔ سهم دیگری، تا ترتیب اجرا روی مبالغ اثر نگذارد.
        [$commissionPercent, $secretaries] = $this->clipPercents($payment, $commissionPercent, $secretaries);

        $repShare       = (int) round($netAfterTax * $commissionPercent / 100);
        $secretaryTotal = 0;
        $secretaryRows  = [];
        foreach ($secretaries as $secretary) {
            $share = (int) round($netAfterTax * $secretary['percent'] / 100);
            if ($share <= 0) continue;
            $secretaryTotal += $share;
            $secretaryRows[] = $secretary + ['share' => $share];
        }

        $systemShare = $gross - $smsFee - $taxRials - $repShare - $secretaryTotal;

        if ($rep !== null && $repShare > 0) {
            $this->credit($rep->getUser(), $repShare, $payment, sprintf('پورسانت %s %s', $source, $payment->getOrderId()));
        }

        $breakdown = new FinancialBreakdown(
            $payment,
            $source,
            $payment->getUser(),
            $gross,
            $smsFee,
            number_format($taxPercent, 2, '.', ''),
            $taxRials,
            $netAfterTax,
            number_format($commissionPercent, 2, '.', ''),
            $repShare,
            $systemShare,
            $rep?->getId(),
            $doctorId,
            $clinicId,
        );
        $breakdown->setSecretaryShareRials($secretaryTotal);
        $this->breakdownRepo->save($breakdown, false);

        foreach ($secretaryRows as $row) {
            $this->credit($row['user'], $row['share'], $payment, sprintf('سهم نوبت آنلاین %s', $payment->getOrderId()));
            $this->earningRepo->save(
                new SecretaryEarning($breakdown, $row['user'], $row['relation_uuid'], $row['percent'], $row['share']),
                false,
            );
        }

        $this->em->flush();
    }

    private function credit(User $user, int $amountRials, Payment $payment, string $description): void
    {
        $balance = $this->settlementRepo->getWalletBalance($user);
        $tx      = new WalletTransaction($user, $amountRials, WalletTransaction::TYPE_CREDIT, $balance + $amountRials);
        $tx->setPayment($payment);
        $tx->setDescription($description);
        $this->walletRepo->save($tx, false);
    }

    /**
     * سهم سیستم نباید منفی شود: اگر مجموع درصدها از ۱۰۰ بگذرد، به نسبت کلیپ می‌شود و
     * هشدار ثبت می‌گردد (سکوت نمی‌کنیم — تنظیمِ اشتباه باید دیده شود).
     *
     * @param list<array{user: User, percent: float, relation_uuid: string}> $secretaries
     * @return array{0: float, 1: list<array{user: User, percent: float, relation_uuid: string}>}
     */
    private function clipPercents(Payment $payment, float $commissionPercent, array $secretaries): array
    {
        $total = $commissionPercent + array_sum(array_column($secretaries, 'percent'));
        if ($total <= 100.0 || $total <= 0.0) {
            return [$commissionPercent, $secretaries];
        }

        $this->logger->warning('Commission + secretary shares exceed 100% — clipping proportionally', [
            'payment_uuid'  => $payment->getUuid(),
            'total_percent' => $total,
        ]);

        $factor = 100.0 / $total;

        $clipped = [];
        foreach ($secretaries as $secretary) {
            $secretary['percent'] *= $factor;
            $clipped[] = $secretary;
        }

        return [$commissionPercent * $factor, $clipped];
    }
}
