<?php

namespace App\Services;

use App\Models\Transaction;
use App\Models\Deposit;
use App\Models\WalletReserve;
use App\Models\Withdrawal;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Log;

class TransactionHandler
{
      /**
       * Approve a deposit.
       */
      public function approveDeposit(Deposit $deposit): ?Transaction
      {
            Log::info('Approve deposit requested', [
                  'deposit_id' => $deposit->id,
                  'status' => $deposit->status,
                  'account_id' => $deposit->account_id,
                  'amount' => $deposit->total,
            ]);

            $transaction = $this->processTransaction(
                  $deposit,
                  'deposit',
                  fn($account, $model) => bcadd((string) $account->balance, (string) $model->amount, 2), // credit only amount
                  fn($model) => $model->amount, // record only amount
                  fn(User $user, Deposit $deposit, Transaction $transaction) =>
                  $this->handleReferralBonus($user, $deposit)
            );

            if (!$transaction) {
                  Log::warning('Approve deposit failed or skipped', [
                        'deposit_id' => $deposit->id,
                        'reason' => 'Not pending or already processed',
                  ]);

                  return null;
            }

            Log::info('Deposit approved successfully', [
                  'deposit_id' => $deposit->id,
                  'transaction_id' => $transaction->id,
                  'new_balance' => $transaction->balance_after,
            ]);

            return $transaction;
      }


      /**
       * Approve a withdrawal.
       */
      public function approveWithdrawal(Withdrawal $withdrawal): ?Transaction
      {
            return $this->processTransaction(
                  $withdrawal,
                  'withdrawal',
                  function ($account, $model) {
                        // Deduct total from actual balance
                        return bcsub((string) $account->balance, (string) $model->total, 2);
                  },
                  fn($model) => $model->total,
                  function ($user, $model, $transaction) {
                        // Mark WalletReserve as completed
                        $reserve = WalletReserve::where('action_type', 'withdrawal')
                              ->where('action_id', $model->id)
                              ->lockForUpdate()
                              ->first();
                        if ($reserve) {
                              $reserve->delete(); // ✅ Remove reserve completely
                        }
                  },
                  false // ✅ No need to check available_balance since funds are reserved
            );
      }


      /**
       * Reject a transaction (deposit or withdrawal).
       */
      public function rejectTransaction($model): void
      {
            DB::transaction(function () use ($model) {

                  $model = $model::where('id', $model->id)->lockForUpdate()->first();


                  if (!$model || $model->status !== $model::STATUS_PENDING) {
                        return;
                  }

                  $model->update(['status' => $model::STATUS_REJECTED]);

                  // Release any reserved funds
                  $reserve = WalletReserve::where('action_type', $model instanceof Withdrawal ? 'withdrawal' : 'deposit')
                        ->where('action_id', $model->id)
                        ->lockForUpdate()
                        ->first();

                  if ($reserve) {
                        $reserve->delete(); // ✅ Remove reserve completely
                  }

                  $fk = $model instanceof Deposit ? 'deposit_id' : 'withdrawal_id';

                  $transaction = Transaction::where($fk, $model->id)
                        ->where('type', $fk === 'deposit_id' ? 'deposit' : 'withdrawal')
                        ->lockForUpdate()
                        ->first();

                  if ($transaction) {
                        $transaction->update([
                              'status' => Transaction::STATUS_REJECTED,
                              'description' => ucfirst($fk === 'deposit_id' ? 'deposit' : 'withdrawal') . ' rejected',
                              'balance_after' => $model->account->balance,
                        ]);
                  }

                  DB::afterCommit(fn() => $model->account?->profile?->user?->notify(
                        new \App\Notifications\TransactionNotification($transaction)
                  ));
            });
      }

      /**
       * Generic processor for deposits and withdrawals.
       */
      private function processTransaction(
            $model,
            string $type,
            callable $balanceCalculator,
            callable $amountCallback,
            callable $afterCallback = null,
            bool $checkAvailableBalance = false
      ): ?Transaction {
            return DB::transaction(function () use ($model, $type, $balanceCalculator, $amountCallback, $afterCallback, $checkAvailableBalance) {

                  // Lock model row
                  $model = $model::where('id', $model->id)
                        ->lockForUpdate()
                        ->first();

                  if (!$model) {
                        return null;
                  }

                  \Log::info('Processing approval', [
                        'model_id' => $model->id,
                        'type' => $type,
                        'status' => $model->status,
                  ]);

                  // Only allow approval from pending state
                  if ($model->status !== $model::STATUS_PENDING) {
                        return null;
                  }

                  // Lock account
                  $account = $model->account()->lockForUpdate()->first();
                  $user = $account?->profile?->user;

                  if (!$account || !$user) {
                        throw new \Exception("Account or user missing for ID {$model->id}");
                  }

                  // Withdrawal balance check
                  if (
                        $checkAvailableBalance &&
                        bccomp((string) $account->available_balance, (string) $model->total, 2) < 0
                  ) {
                        throw new \Exception("Insufficient available balance for {$type} ID {$model->id}");
                  }

                  // Fetch existing transaction (must exist in your architecture)
                  $fk = $model instanceof Deposit ? 'deposit_id' : 'withdrawal_id';

                  $transaction = Transaction::where($fk, $model->id)
                        ->where('type', $type)
                        ->lockForUpdate()
                        ->first();

                  if (!$transaction) {
                        \Log::critical('Missing transaction ledger row', [
                              'model_id' => $model->id,
                              'type' => $type,
                        ]);
                        throw new \Exception('Ledger record missing.');
                  }

                  // Idempotency: block if already approved
                  if ($transaction->status === Transaction::STATUS_APPROVED) {
                        \Log::warning('Transaction already approved', [
                              'transaction_id' => $transaction->id,
                              'model_id' => $model->id,
                        ]);
                        return null;
                  }

                  // Optional: block approving rejected
                  if ($transaction->status === Transaction::STATUS_REJECTED) {
                        \Log::warning('Attempt to approve rejected transaction', [
                              'transaction_id' => $transaction->id,
                              'model_id' => $model->id,
                        ]);
                        return null;
                  }

                  // Calculate new balance
                  $newBalance = $balanceCalculator($account, $model);

                  // Update account balance
                  $account->update([
                        'balance' => $newBalance,
                  ]);

                  // Update model status
                  $model->update([
                        'status' => $model::STATUS_APPROVED,
                  ]);

                  // Update existing transaction (NOT create)
                  $transaction->update([
                        'status' => Transaction::STATUS_APPROVED,
                        'amount' => $amountCallback($model),
                        'balance_after' => $newBalance,
                        'description' => ucfirst($type) . ' approved',
                        'transaction_ref' => $transaction->transaction_ref ?? Str::uuid(),
                  ]);

                  // After-commit hooks
                  if ($afterCallback) {
                        DB::afterCommit(
                              fn() =>
                              $afterCallback($user, $model, $transaction)
                        );
                  }

                  DB::afterCommit(
                        fn() =>
                        $user->notify(new \App\Notifications\TransactionNotification($transaction))
                  );

                  return $transaction;
            });
      }


      private function handleReferralBonus(User $user, Deposit $deposit): void
      {
            Log::info('Referral bonus process started', [
                  'deposit_id' => $deposit->id,
                  'user_id' => $user->id,
            ]);

            $referrer = $user->referrer;

            if (!$referrer) {
                  Log::info('Referral bonus skipped: No referrer found', [
                        'user_id' => $user->id,
                  ]);
                  return;
            }

            if (!$referrer->profile?->accounts()->exists()) {
                  Log::warning('Referral bonus skipped: Referrer has no account', [
                        'referrer_id' => $referrer->id,
                  ]);
                  return;
            }

            $referrerAccount = $referrer->profile->accounts()
                  ->lockForUpdate()
                  ->first();

            if (!$referrerAccount) {
                  Log::warning('Referral bonus skipped: Unable to lock referrer account', [
                        'referrer_id' => $referrer->id,
                  ]);
                  return;
            }

            $alreadyGiven = Transaction::where('type', 'referral_bonus')
                  ->where('deposit_id', $deposit->id)
                  ->lockForUpdate()
                  ->exists();

            if ($alreadyGiven) {
                  Log::warning('Referral bonus already given for this deposit', [
                        'deposit_id' => $deposit->id,
                  ]);
                  return;
            }

            $bonus = bcmul((string) $deposit->amount, '0.05', 2);

            Log::info('Referral bonus calculated', [
                  'deposit_amount' => $deposit->amount,
                  'bonus' => $bonus,
                  'referrer_id' => $referrer->id,
            ]);

            $referrerNewBalance = bcadd((string) $referrerAccount->balance, $bonus, 2);

            $referrerAccount->update([
                  'balance' => $referrerNewBalance,
            ]);

            Log::info('Referrer balance updated', [
                  'referrer_id' => $referrer->id,
                  'new_balance' => $referrerNewBalance,
            ]);

            $referralTransaction = Transaction::create([
                  'account_id' => $referrerAccount->id,
                  'type' => 'referral_bonus',
                  'amount' => $bonus,
                  'status' => Transaction::STATUS_APPROVED,
                  'currency' => $referrerAccount->profile->currency ?? 'USD',
                  'description' => 'Referral commission from deposit of user ID ' . $user->id,
                  'transaction_ref' => Str::uuid(),
                  'balance_after' => $referrerNewBalance,
                  'referral_user_id' => $user->id,
                  'deposit_id' => $deposit->id,
            ]);

            Log::info('Referral transaction created', [
                  'transaction_id' => $referralTransaction->id,
                  'referrer_id' => $referrer->id,
                  'deposit_id' => $deposit->id,
            ]);

            DB::afterCommit(function () use ($referrer, $referralTransaction) {
                  Log::info('Sending referral bonus notification', [
                        'referrer_id' => $referrer->id,
                        'transaction_id' => $referralTransaction->id,
                  ]);

                  $referrer->notify(
                        new \App\Notifications\TransactionNotification($referralTransaction)
                  );
            });

            Log::info('Referral bonus process completed', [
                  'deposit_id' => $deposit->id,
            ]);
      }

}
