<?php

namespace App\Services;

use App\Models\Loan;
use App\Models\LoanInstallment;
use App\Models\Transaction;
use App\Models\Account;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Notifications\TransactionNotification;

class LoanHandler
{
      /**
       * Approve a loan:
       * - Credit account
       * - Update loan status
       * - Update the existing pending transaction (do NOT create a new one)
       * - Generate installments
       */
      public function approve(Loan $loan): void
      {
            DB::transaction(function () use ($loan) {

                  // Lock loan
                  $loan = Loan::where('id', $loan->id)->lockForUpdate()->firstOrFail();

                  if ($loan->status !== 'pending') {
                        throw new \Exception('Loan is not pending.');
                  }

                  // Lock account
                  $account = Account::where('id', $loan->account_id)->lockForUpdate()->firstOrFail();

                  // Find the transaction created when user applied
                  $transaction = Transaction::where('loan_id', $loan->id)
                        ->where('type', 'loan')
                        ->where('status', Transaction::STATUS_PENDING)
                        ->lockForUpdate()
                        ->first();

                  if (!$transaction) {
                        // This is important: approval must not proceed without transaction consistency
                        throw new \Exception("Pending transaction not found for loan ID {$loan->id}");
                  }

                  // -------------------------------
                  // 1️⃣ Credit user account
                  // -------------------------------
                  $account->balance = bcadd((string) $account->balance, (string) $loan->amount, 2);
                  $account->save();

                  // -------------------------------
                  // 2️⃣ Update loan core fields
                  // -------------------------------
                  $loan->update([
                        'status' => 'approved',
                        'approved_at' => now(),
                        'principal_outstanding' => $loan->amount,
                        'interest_outstanding' => $loan->total_interest,
                        'penalty_outstanding' => 0,
                        'total_paid' => 0,
                  ]);

                  // -------------------------------
                  // 3️⃣ Update existing transaction (NOT create)
                  // -------------------------------
                  $transaction->update([
                        'status' => Transaction::STATUS_APPROVED,
                        'balance_after' => $account->balance,
                        'description' => 'Loan disbursement (approved)',
                  ]);

                  // -------------------------------
                  // 4️⃣ Generate installments
                  // -------------------------------
                  $this->generateRepaymentSchedule($loan);

                  // -------------------------------
                  // 5️⃣ Notify after commit
                  // -------------------------------
                  DB::afterCommit(function () use ($transaction) {
                        $this->notifyTransaction($transaction);
                  });
            });
      }

      /**
       * Reject a loan:
       * - Update loan status
       * - Update the existing pending transaction to rejected
       * - Notify user
       */
      public function rejectLoan(Loan $loan, string $reason = 'Loan rejected'): void
      {
            DB::transaction(function () use ($loan, $reason) {

                  $loan = Loan::where('id', $loan->id)->lockForUpdate()->firstOrFail();

                  if ($loan->status !== 'pending') {
                        Log::warning('Loan not pending', ['loan_id' => $loan->id]);
                        return;
                  }

                  // Lock account
                  $account = Account::where('id', $loan->account_id)->lockForUpdate()->first();

                  // Find the transaction created when user applied
                  $transaction = Transaction::where('loan_id', $loan->id)
                        ->where('type', 'loan')
                        ->where('status', Transaction::STATUS_PENDING)
                        ->lockForUpdate()
                        ->first();

                  if (!$transaction) {
                        throw new \Exception("Pending transaction not found for loan ID {$loan->id}");
                  }

                  // -------------------------------
                  // 1️⃣ Update loan
                  // -------------------------------
                  $loan->update([
                        'status' => 'rejected',
                        'rejection_reason' => $reason,
                        'rejected_at' => now(),
                  ]);

                  // -------------------------------
                  // 2️⃣ Update existing transaction
                  // -------------------------------
                  $transaction->update([
                        'status' => Transaction::STATUS_REJECTED,
                        'balance_after' => $account?->balance ?? '0.00',
                        'description' => $reason,
                  ]);

                  // -------------------------------
                  // 3️⃣ Notify after commit
                  // -------------------------------
                  DB::afterCommit(function () use ($transaction) {
                        $this->notifyTransaction($transaction);
                  });
            });
      }

      protected function generateRepaymentSchedule(Loan $loan): void
      {
            // Prevent duplicates if approve gets triggered twice accidentally
            if ($loan->installments()->exists()) {
                  return;
            }

            $remainingPrincipal = $loan->amount;
            $rate = $loan->interest_rate; // monthly rate
            $duration = (int) $loan->duration;

            $r = $rate;
            $n = $duration;

            $onePlusR = bcadd('1', $r, 8);
            $onePlusRToN = bcpow($onePlusR, $n, 8);
            $numerator = bcmul(bcmul($loan->amount, $r, 8), $onePlusRToN, 8);
            $denominator = bcsub($onePlusRToN, '1', 8);

            $monthlyPayment = bcdiv($numerator, $denominator, 2);

            $firstDueDate = now()->addMonth()->startOfDay();

            for ($i = 1; $i <= $n; $i++) {

                  $interest = bcmul($remainingPrincipal, $r, 2);
                  $principal = bcsub($monthlyPayment, $interest, 2);
                  $remainingPrincipal = bcsub($remainingPrincipal, $principal, 2);

                  if ($i === $n && bccomp($remainingPrincipal, '0', 2) !== 0) {
                        $principal = bcadd($principal, $remainingPrincipal, 2);
                        $monthlyPayment = bcadd($principal, $interest, 2);
                        $remainingPrincipal = '0.00';
                  }

                  $loan->installments()->create([
                        'installment_number' => $i,
                        'principal' => $principal,
                        'interest' => $interest,
                        'penalty' => 0,
                        'total' => $monthlyPayment,
                        'due_date' => $firstDueDate->copy()->addMonths($i - 1),
                        'status' => 'pending',
                  ]);
            }

            $loan->update([
                  'first_due_date' => $firstDueDate,
                  'last_due_date' => $firstDueDate->copy()->addMonths($n - 1),
            ]);
      }

      /**
       * Mark a loan installment as paid:
       * - Deduct from account
       * - Mark installment paid
       * - Update loan outstanding ledgers
       * - Create repayment transaction
       * - Mark loan completed if fully paid
       */
      public function markAsPaid(int $installmentId): void
      {
            DB::transaction(function () use ($installmentId) {

                  // Lock installment row
                  $installment = LoanInstallment::where('id', $installmentId)
                        ->lockForUpdate()
                        ->firstOrFail();

                  if ($installment->status === 'paid') {
                        return; // prevent double processing
                  }

                  // Lock loan
                  $loan = Loan::where('id', $installment->loan_id)
                        ->lockForUpdate()
                        ->firstOrFail();

                  if ($loan->status !== 'approved' && $loan->status !== 'overdue') {
                        throw new \Exception("Cannot pay installment for a loan that is {$loan->status}.");
                  }

                  // Lock account
                  $account = Account::where('id', $loan->account_id)
                        ->lockForUpdate()
                        ->firstOrFail();

                  // -------------------------------
                  // 1️⃣ Deduct money from account
                  // -------------------------------
                  $newBalance = bcsub((string) $account->balance, (string) $installment->total, 2);

                  if (bccomp($newBalance, '0.00', 2) === -1) {
                        throw new \Exception("Insufficient balance to mark installment as paid.");
                  }

                  $account->balance = $newBalance;
                  $account->save();

                  // -------------------------------
                  // 2️⃣ Mark installment paid
                  // -------------------------------
                  $installment->update([
                        'status' => 'paid',
                        'paid_at' => now(),
                  ]);

                  // -------------------------------
                  // 3️⃣ Update loan ledgers
                  // -------------------------------
                  $loan->principal_outstanding = bcsub((string) $loan->principal_outstanding, (string) $installment->principal, 2);
                  $loan->interest_outstanding = bcsub((string) $loan->interest_outstanding, (string) $installment->interest, 2);
                  $loan->penalty_outstanding = bcsub((string) $loan->penalty_outstanding, (string) $installment->penalty, 2);

                  $loan->total_paid = bcadd((string) $loan->total_paid, (string) $installment->total, 2);

                  // Prevent negatives due to rounding
                  if (bccomp($loan->principal_outstanding, '0.00', 2) === -1)
                        $loan->principal_outstanding = '0.00';
                  if (bccomp($loan->interest_outstanding, '0.00', 2) === -1)
                        $loan->interest_outstanding = '0.00';
                  if (bccomp($loan->penalty_outstanding, '0.00', 2) === -1)
                        $loan->penalty_outstanding = '0.00';

                  $loan->save();

                  // -------------------------------
                  // 4️⃣ Create repayment transaction
                  // -------------------------------
                  $transaction = Transaction::create([
                        'user_id' => $loan->user_id,
                        'account_id' => $loan->account_id,
                        'loan_id' => $loan->id,
                        'type' => 'loan',
                        'amount' => $installment->total,
                        'status' => Transaction::STATUS_COMPLETED,
                        'direction' => 'debit',
                        'balance_after' => $account->balance,
                        'description' => "Loan repayment (Installment #{$installment->installment_number})",
                  ]);

                  // -------------------------------
                  // 5️⃣ Mark loan completed if fully paid
                  // -------------------------------
                  $loan->markCompletedIfPaid();

                  // -------------------------------
                  // 6️⃣ Notify after commit
                  // -------------------------------
                  DB::afterCommit(function () use ($transaction) {
                        $this->notifyTransaction($transaction);
                  });
            });
      }


      protected function notifyTransaction(Transaction $transaction): void
      {
            try {
                  $transaction->account->profile?->user?->notify(
                        new TransactionNotification($transaction)
                  );
            } catch (\Throwable $e) {
                  Log::error('Error sending loan transaction notification', [
                        'transaction_id' => $transaction->id,
                        'exception' => $e,
                  ]);
            }
      }
}
