<?php

namespace App\Services;

use App\Models\BinaryLedger;
use App\Models\BinaryMatchingIncome;
use App\Models\JoiningPairIncome;
use App\Models\User;
use App\Models\UserBinaryCarry;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

class MatchingService
{
    public function __construct(private readonly WalletService $walletService)
    {
    }

    public function runJoiningMatching(Carbon $date): void
    {
        $pairValue = (float) setting('joining_pair_value', 0);
        $capping = (float) setting('joining_daily_capping', 0);

        if ($pairValue <= 0) {
            return;
        }

        $this->processMatching($date, 'joining', $pairValue, $capping);
    }

    public function runRepurchaseMatching(Carbon $date): void
    {
        $rate = (float) setting('pair_rate', 0.10);
        $capping = (float) setting('repurchase_daily_capping', 0);

        if ($rate <= 0) {
            return;
        }

        $this->processMatching($date, 'repurchase', $rate, $capping);
    }

    private function processMatching(Carbon $date, string $type, float $rate, float $defaultCapping): void
    {
        $ledgerTypes = $type === 'joining'
            ? ['joining', 'reversal_joining']
            : ['repurchase', 'reversal_repurchase'];

        $endOfDay = $date->copy()->endOfDay();

        BinaryLedger::query()
            ->join('users', 'users.id', '=', 'binary_ledger.beneficiary_user_id')
            ->whereNull('binary_ledger.processed_at')
            ->whereIn('binary_ledger.ledger_type', $ledgerTypes)
            ->where('binary_ledger.posted_at', '<=', $endOfDay)
            ->where('users.role', 'user')
            ->where('users.status', 'active')
            ->select('binary_ledger.beneficiary_user_id')
            ->groupBy('binary_ledger.beneficiary_user_id')
            ->orderBy('binary_ledger.beneficiary_user_id')
            ->chunkById(200, function (Collection $rows) use ($date, $rate, $defaultCapping, $ledgerTypes, $type, $endOfDay): void {
                $userIds = $rows->pluck('beneficiary_user_id')->filter()->values();
                if ($userIds->isEmpty()) {
                    return;
                }

                $users = User::query()
                    ->whereIn('id', $userIds)
                    ->with('currentRank')
                    ->get()
                    ->keyBy('id');

                foreach ($userIds as $userId) {
                    $user = $users->get($userId);
                    if (! $user) {
                        continue;
                    }

                    DB::transaction(function () use ($user, $date, $rate, $defaultCapping, $ledgerTypes, $type, $endOfDay): void {
                        $carry = UserBinaryCarry::query()->firstOrCreate(
                            ['user_id' => $user->id],
                            [
                                'joining_left' => 0,
                                'joining_right' => 0,
                                'repurchase_left' => 0,
                                'repurchase_right' => 0,
                            ]
                        );

                        $ledgerRows = BinaryLedger::query()
                            ->where('beneficiary_user_id', $user->id)
                            ->whereNull('processed_at')
                            ->whereIn('ledger_type', $ledgerTypes)
                            ->where('posted_at', '<=', $endOfDay)
                            ->lockForUpdate()
                            ->get(['id', 'leg', 'direction', 'units']);

                        if ($ledgerRows->isEmpty()) {
                            return;
                        }

                        $leftDelta = 0.0;
                        $rightDelta = 0.0;
                        foreach ($ledgerRows as $row) {
                            $sign = $row->direction === 'credit' ? 1 : -1;
                            if ($row->leg === 'left') {
                                $leftDelta += $sign * (float) $row->units;
                            } else {
                                $rightDelta += $sign * (float) $row->units;
                            }
                        }

                        if ($type === 'joining') {
                            $leftAvailable = max(0, (float) $carry->joining_left + $leftDelta);
                            $rightAvailable = max(0, (float) $carry->joining_right + $rightDelta);
                        } else {
                            $leftAvailable = max(0, (float) $carry->repurchase_left + $leftDelta);
                            $rightAvailable = max(0, (float) $carry->repurchase_right + $rightDelta);
                        }

                        $matched = min($leftAvailable, $rightAvailable);
                        $gross = round($matched * $rate, 2);

                        if ($type === 'joining') {
                            $todayNet = (float) JoiningPairIncome::query()
                                ->where('user_id', $user->id)
                                ->whereDate('run_date', $date->toDateString())
                                ->sum('net_income');
                        } else {
                            $todayNet = (float) BinaryMatchingIncome::query()
                                ->where('user_id', $user->id)
                                ->whereDate('run_date', $date->toDateString())
                                ->sum('net_income');
                        }

                        $rankCapping = (float) ($user->currentRank?->daily_capping ?? 0);
                        $effectiveCapping = $rankCapping > 0 ? $rankCapping : $defaultCapping;
                        $availableForToday = $effectiveCapping > 0 ? max(0, $effectiveCapping - $todayNet) : $gross;
                        $net = min($gross, $availableForToday);
                        $capDeduction = round($gross - $net, 2);

                        $displayReference = $type === 'joining'
                            ? 'JOIN-'.$date->format('Ymd')
                            : 'BV-'.$date->format('Ymd');

                        if ($matched > 0) {
                            if ($type === 'joining') {
                                $income = JoiningPairIncome::query()->create([
                                    'user_id' => $user->id,
                                    'run_date' => $date->toDateString(),
                                    'left_units' => $leftAvailable,
                                    'right_units' => $rightAvailable,
                                    'matched_pairs' => $matched,
                                    'pair_value' => $rate,
                                    'gross_income' => $gross,
                                    'capping_deduction' => $capDeduction,
                                    'net_income' => $net,
                                    'status' => 'credited',
                                    'reference' => $displayReference.'-'.$user->id.'-'.now()->format('His'),
                                ]);

                                if ($net > 0) {
                                    $this->walletService->credit(
                                        $user->id,
                                        'income',
                                        $net,
                                        'joining_matching',
                                        $income->id,
                                        [
                                            'reference' => 'JOIN-INC-'.$income->id,
                                            'display_reference' => $displayReference,
                                            'narration' => 'Joining pair income',
                                        ]
                                    );
                                    $this->walletService->credit(
                                        $user->id,
                                        'withdrawable',
                                        $net,
                                        'joining_matching',
                                        $income->id,
                                        [
                                            'reference' => 'JOIN-WDR-'.$income->id,
                                            'display_reference' => $displayReference,
                                            'narration' => 'Joining pair income',
                                        ]
                                    );
                                }
                            } else {
                                $income = BinaryMatchingIncome::query()->create([
                                    'user_id' => $user->id,
                                    'run_date' => $date->toDateString(),
                                    'left_bv' => $leftAvailable,
                                    'right_bv' => $rightAvailable,
                                    'matched_bv' => $matched,
                                    'rate' => $rate,
                                    'gross_income' => $gross,
                                    'capping_deduction' => $capDeduction,
                                    'net_income' => $net,
                                    'status' => 'credited',
                                    'reference' => $displayReference.'-'.$user->id.'-'.now()->format('His'),
                                ]);

                                if ($net > 0) {
                                    $this->walletService->credit(
                                        $user->id,
                                        'income',
                                        $net,
                                        'binary_matching',
                                        $income->id,
                                        [
                                            'reference' => 'BV-INC-'.$income->id,
                                            'display_reference' => $displayReference,
                                            'narration' => 'Repurchase matching income',
                                        ]
                                    );
                                    $this->walletService->credit(
                                        $user->id,
                                        'withdrawable',
                                        $net,
                                        'binary_matching',
                                        $income->id,
                                        [
                                            'reference' => 'BV-WDR-'.$income->id,
                                            'display_reference' => $displayReference,
                                            'narration' => 'Repurchase matching income',
                                        ]
                                    );
                                }
                            }
                        }

                        if ($type === 'joining') {
                            $carry->joining_left = round($leftAvailable - $matched, 4);
                            $carry->joining_right = round($rightAvailable - $matched, 4);
                            $carry->last_joining_processed_at = now();
                        } else {
                            $carry->repurchase_left = round($leftAvailable - $matched, 4);
                            $carry->repurchase_right = round($rightAvailable - $matched, 4);
                            $carry->last_repurchase_processed_at = now();
                        }

                        $carry->last_processed_at = now();
                        $carry->save();

                        DB::table('binary_ledger')
                            ->whereIn('id', $ledgerRows->pluck('id'))
                            ->update([
                                'processed_at' => now(),
                                'updated_at' => now(),
                            ]);
                    });
                }
            }, 'beneficiary_user_id');
    }
}

