<?php

namespace App\Services;

use App\Models\CommissionLedger;
use App\Models\Order;
use App\Models\User;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\DB;

class CommissionEngine
{
    public function __construct(private readonly WalletService $walletService, private readonly ?MlmLogService $mlmLogService = null)
    {
    }

    public function calculateDirectReferralIncome(Order $order): ?CommissionLedger
    {
        $user = $order->user;
        if (! $user || ! $user->sponsor_id) {
            return null;
        }

        $sponsor = User::query()->find($user->sponsor_id);
        if (! $sponsor || $sponsor->status !== 'active') {
            return null;
        }

        $percent = (float) setting('direct_income_percent', 0);
        $amount = round(((float) $order->total) * $percent / 100, 2);

        return $this->applySafetyAndCredit(
            $sponsor,
            $amount,
            'direct_referral',
            'order',
            $order->id,
            'DIR-'.$order->id.'-'.$sponsor->id,
            ['order_number' => $order->order_number, 'from_user_id' => $user->id]
        );
    }

    public function calculateBinaryIncome(Order $order): ?CommissionLedger
    {
        $user = $order->user;
        if (! $user || $user->status !== 'active') {
            return null;
        }

        $rate = (float) setting('binary_order_income_rate', 0);
        $amount = round(((float) $order->total_bv) * $rate, 2);

        return $this->applySafetyAndCredit(
            $user,
            $amount,
            'binary',
            'order',
            $order->id,
            'BIN-'.$order->id.'-'.$user->id,
            ['order_number' => $order->order_number, 'bv' => (float) $order->total_bv]
        );
    }

    public function calculateLevelIncome(Order $order): int
    {
        $buyer = $order->user;
        if (! $buyer) {
            return 0;
        }

        $maxLevels = (int) setting('level_income_levels', 0);
        $levelPercent = (float) setting('level_income_percent', 0);
        if ($maxLevels <= 0 || $levelPercent <= 0) {
            return 0;
        }

        $count = 0;
        $current = $buyer;
        for ($level = 1; $level <= $maxLevels; $level++) {
            if (! $current->sponsor_id) {
                break;
            }
            $upline = User::query()->find($current->sponsor_id);
            if (! $upline || $upline->status !== 'active') {
                break;
            }

            $amount = round(((float) $order->total) * $levelPercent / 100, 2);
            $result = $this->applySafetyAndCredit(
                $upline,
                $amount,
                'level',
                'order',
                $order->id,
                'LVL-'.$order->id.'-'.$level.'-'.$upline->id,
                ['order_number' => $order->order_number, 'level' => $level, 'from_user_id' => $buyer->id]
            );

            if ($result) {
                $count++;
            }

            $current = $upline;
        }

        return $count;
    }

    public function calculateMatchingBonus(Order $order): ?CommissionLedger
    {
        $user = $order->user;
        if (! $user) {
            return null;
        }

        $bonus = (float) setting('matching_bonus_fixed', 0);

        return $this->applySafetyAndCredit(
            $user,
            $bonus,
            'matching_bonus',
            'order',
            $order->id,
            'MBN-'.$order->id.'-'.$user->id,
            ['order_number' => $order->order_number]
        );
    }

    public function calculateRankBonus(Order $order): ?CommissionLedger
    {
        $user = $order->user;
        if (! $user) {
            return null;
        }

        $rankAmount = (float) ($user->currentRank?->reward_amount ?? 0);

        return $this->applySafetyAndCredit(
            $user,
            $rankAmount,
            'rank_bonus',
            'order',
            $order->id,
            'RBN-'.$order->id.'-'.$user->id,
            ['order_number' => $order->order_number, 'rank_id' => $user->current_rank_id]
        );
    }

    public function calculateMatrixIncome(Order $order): ?CommissionLedger
    {
        $user = $order->user;
        if (! $user) {
            return null;
        }

        $percent = (float) setting('matrix_income_percent', 0);
        $amount = round(((float) $order->total) * $percent / 100, 2);

        return $this->applySafetyAndCredit(
            $user,
            $amount,
            'matrix_income',
            'order',
            $order->id,
            'MTX-'.$order->id.'-'.$user->id,
            ['order_number' => $order->order_number]
        );
    }

    public function calculateUnilevelCommission(Order $order): ?CommissionLedger
    {
        $user = $order->user;
        if (! $user || ! $user->sponsor_id) {
            return null;
        }

        $sponsor = User::query()->find($user->sponsor_id);
        if (! $sponsor || $sponsor->status !== 'active') {
            return null;
        }

        $percent = (float) setting('unilevel_income_percent', 0);
        $amount = round(((float) $order->total) * $percent / 100, 2);

        return $this->applySafetyAndCredit(
            $sponsor,
            $amount,
            'unilevel_income',
            'order',
            $order->id,
            'UNI-'.$order->id.'-'.$sponsor->id,
            ['order_number' => $order->order_number, 'from_user_id' => $user->id]
        );
    }

    public function calculateGenerationBonus(Order $order): int
    {
        $buyer = $order->user;
        if (! $buyer) {
            return 0;
        }

        $percent = (float) setting('generation_bonus_percent', 0);
        if ($percent <= 0) {
            return 0;
        }

        $count = 0;
        $current = $buyer;
        for ($generation = 1; $generation <= 3; $generation++) {
            if (! $current->sponsor_id) {
                break;
            }
            $upline = User::query()->find($current->sponsor_id);
            if (! $upline || $upline->status !== 'active') {
                break;
            }

            $amount = round(((float) $order->total) * $percent / 100, 2);
            $result = $this->applySafetyAndCredit(
                $upline,
                $amount,
                'generation_bonus',
                'order',
                $order->id,
                'GEN-'.$order->id.'-'.$generation.'-'.$upline->id,
                ['order_number' => $order->order_number, 'generation' => $generation, 'from_user_id' => $buyer->id]
            );

            if ($result) {
                $count++;
            }
            $current = $upline;
        }

        return $count;
    }

    public function calculateLeadershipBonus(Order $order): int
    {
        $buyer = $order->user;
        if (! $buyer) {
            return 0;
        }

        $percent = (float) setting('leadership_bonus_percent', 0);
        if ($percent <= 0) {
            return 0;
        }

        $count = 0;
        $current = $buyer;
        for ($level = 1; $level <= 10; $level++) {
            if (! $current->sponsor_id) {
                break;
            }
            $upline = User::query()->find($current->sponsor_id);
            if (! $upline || $upline->status !== 'active') {
                break;
            }

            if (($upline->currentRank?->priority ?? 0) >= 2) {
                $amount = round(((float) $order->total) * $percent / 100, 2);
                $result = $this->applySafetyAndCredit(
                    $upline,
                    $amount,
                    'leadership_bonus',
                    'order',
                    $order->id,
                    'LDR-'.$order->id.'-'.$level.'-'.$upline->id,
                    ['order_number' => $order->order_number, 'level' => $level, 'from_user_id' => $buyer->id]
                );
                if ($result) {
                    $count++;
                }
            }
            $current = $upline;
        }

        return $count;
    }

    public function calculateRoiIncome(Order $order): ?CommissionLedger
    {
        $user = $order->user;
        if (! $user) {
            return null;
        }

        $percent = (float) setting('roi_percent', 0);
        if ($percent <= 0) {
            return null;
        }
        $lockDays = (int) setting('roi_lock_days', 0);
        $base = max(0, (float) $user->package_amount);
        $amount = round($base * $percent / 100, 2);

        return $this->applySafetyAndCredit(
            $user,
            $amount,
            'roi_income',
            'order',
            $order->id,
            'ROI-'.$order->id.'-'.$user->id,
            [
                'order_number' => $order->order_number,
                'lock_days' => $lockDays,
                'base_package' => $base,
            ]
        );
    }

    /**
     * @param array<string,mixed> $meta
     */
    private function applySafetyAndCredit(
        User $user,
        float $grossAmount,
        string $commissionType,
        string $sourceType,
        int $sourceId,
        string $reference,
        array $meta = []
    ): ?CommissionLedger {
        if ($grossAmount <= 0 || $user->status !== 'active') {
            return null;
        }

        return DB::transaction(function () use ($user, $grossAmount, $commissionType, $sourceType, $sourceId, $reference, $meta): ?CommissionLedger {
            $existing = CommissionLedger::query()->where('reference', $reference)->first();
            if ($existing) {
                return $existing;
            }

            $lockedUser = User::query()->whereKey($user->id)->lockForUpdate()->first();
            if (! $lockedUser || $lockedUser->status !== 'active') {
                return null;
            }

            $dailyCap = (float) setting('daily_income_cap', 0);
            $todayIncome = (float) CommissionLedger::query()
                ->where('user_id', $lockedUser->id)
                ->whereDate('credited_at', now()->toDateString())
                ->sum('credited_amount');

            $remainingDaily = $dailyCap > 0 ? max(0, $dailyCap - $todayIncome) : $grossAmount;

            $packageLimit = round(max(0, (float) $lockedUser->package_amount) * 2, 2);
            $lifetimeIncome = (float) CommissionLedger::query()
                ->where('user_id', $lockedUser->id)
                ->sum('credited_amount');
            $remainingPackage = $packageLimit > 0 ? max(0, $packageLimit - $lifetimeIncome) : $grossAmount;

            $credited = round(min($grossAmount, $remainingDaily, $remainingPackage), 2);
            $capped = round($grossAmount - $credited, 2);
            $status = $credited > 0 ? ($capped > 0 ? 'partially_credited' : 'credited') : 'capped';

            try {
                $ledger = CommissionLedger::query()->create([
                    'user_id' => $lockedUser->id,
                    'order_id' => $sourceType === 'order' ? $sourceId : null,
                    'source_type' => $sourceType,
                    'source_id' => $sourceId,
                    'commission_type' => $commissionType,
                    'reference' => $reference,
                    'gross_amount' => $grossAmount,
                    'credited_amount' => $credited,
                    'capped_amount' => $capped,
                    'status' => $status,
                    'meta' => $meta,
                    'credited_at' => now(),
                ]);
            } catch (QueryException $exception) {
                $existing = CommissionLedger::query()->where('reference', $reference)->first();
                if ($existing) {
                    return $existing;
                }

                throw $exception;
            }

            if ($credited > 0) {
                $this->walletService->credit(
                    $lockedUser->id,
                    'income',
                    $credited,
                    $commissionType,
                    $ledger->id,
                    [
                        'reference' => 'COM-INC-'.$ledger->id,
                        'display_reference' => $reference,
                        'narration' => ucfirst(str_replace('_', ' ', $commissionType)).' credited',
                        'meta' => ['commission_ledger_id' => $ledger->id],
                    ]
                );

                $this->walletService->credit(
                    $lockedUser->id,
                    'withdrawable',
                    $credited,
                    $commissionType,
                    $ledger->id,
                    [
                        'reference' => 'COM-WDR-'.$ledger->id,
                        'display_reference' => $reference,
                        'narration' => ucfirst(str_replace('_', ' ', $commissionType)).' credited',
                        'meta' => ['commission_ledger_id' => $ledger->id],
                    ]
                );
            }

            $this->mlmLogService?->log(
                'commission.credited',
                $lockedUser->id,
                'commission_ledger',
                $ledger->id,
                [
                    'commission_type' => $commissionType,
                    'source_type' => $sourceType,
                    'source_id' => $sourceId,
                    'reference' => $reference,
                    'gross_amount' => $grossAmount,
                    'credited_amount' => $credited,
                    'capped_amount' => $capped,
                    'status' => $status,
                ]
            );
            if ($packageLimit > 0 && $remainingPackage <= 0 && setting('auto_suspend_on_cap_hit', '0') === '1') {
                $lockedUser->status = 'blocked';
                $lockedUser->closed_at = $lockedUser->closed_at ?? now();
                $lockedUser->save();
            }

            return $ledger;
        });
    }
}

