<?php

namespace App\Services;

use App\Models\LoanApplication;
use App\Models\LoanDocument;
use App\Models\LoanProduct;
use App\Models\LoanRepayment;
use App\Models\LoanAmortizationSchedule;
use App\Models\Member;
use App\Models\MembershipTransaction;
use App\Models\MemberActivityLog;
use App\Services\PaymentService;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Carbon\Carbon;

class LoanService
{
    protected PaymentService $paymentService;
    protected NotificationService $notificationService;

    public function __construct(PaymentService $paymentService, NotificationService $notificationService)
    {
        $this->paymentService = $paymentService;
        $this->notificationService = $notificationService;
    }

    /**
     * Calculate loan details.
     */
    public function calculateLoanDetails(LoanProduct $product, float $amount, int $termMonths): array
    {
        $monthlyPayment = $product->calculateMonthlyPayment($amount, $termMonths);
        $totalInterest = $product->calculateTotalInterest($amount, $termMonths);
        $totalRepayable = $amount + $totalInterest;
        $processingFee = $product->calculateProcessingFee($amount);

        return [
            'amount' => $amount,
            'term_months' => $termMonths,
            'interest_rate' => $product->interest_rate,
            'interest_type' => $product->interest_type,
            'monthly_payment' => round($monthlyPayment, 2),
            'total_interest' => round($totalInterest, 2),
            'total_repayable' => round($totalRepayable, 2),
            'processing_fee' => round($processingFee, 2),
            'currency' => $product->currency,
        ];
    }

    /**
     * Submit a loan application.
     */
    public function submitApplication(Member $member, LoanProduct $product, array $data): LoanApplication
    {
        return DB::transaction(function () use ($member, $product, $data) {
            $amount = $data['amount_requested'];
            $termMonths = $data['term_months'];
            $loanDetails = $this->calculateLoanDetails($product, $amount, $termMonths);

            $application = LoanApplication::create([
                'reference_number' => LoanApplication::generateReferenceNumber(),
                'member_id' => $member->id,
                'loan_product_id' => $product->id,
                'user_id' => $member->user_id,
                'amount_requested' => $amount,
                'currency' => $product->currency,
                'term_months' => $termMonths,
                'interest_rate' => $product->interest_rate,
                'purpose' => $data['purpose'] ?? null,
                'monthly_payment' => $loanDetails['monthly_payment'],
                'total_interest' => $loanDetails['total_interest'],
                'total_repayable' => $loanDetails['total_repayable'],
                'processing_fee' => $loanDetails['processing_fee'],
                'status' => 'pending',
                'employment_status' => $data['employment_status'] ?? null,
                'employer_name' => $data['employer_name'] ?? null,
                'monthly_income' => $data['monthly_income'] ?? null,
                'income_currency' => $data['income_currency'] ?? null,
                'guarantors' => $data['guarantors'] ?? null,
                'collateral' => $data['collateral'] ?? null,
            ]);

            MemberActivityLog::log(
                'loan_application_submitted',
                "Loan application submitted: {$application->reference_number}",
                $member,
                $member->user,
                ['loan_application_id' => $application->id]
            );

            // Notify managers
            $this->notificationService->notifyManagersOfNewLoanApplication($application);

            return $application;
        });
    }

    /**
     * Upload loan document.
     */
    public function uploadDocument(LoanApplication $application, $file, string $documentType, int $uploadedBy): LoanDocument
    {
        $filename = uniqid() . '.' . $file->getClientOriginalExtension();
        $path = $file->storeAs('loan_documents/' . $application->id, $filename, 'private');

        return LoanDocument::create([
            'loan_application_id' => $application->id,
            'uploaded_by' => $uploadedBy,
            'document_type' => $documentType,
            'document_name' => $documentType . '_' . $application->reference_number,
            'file_path' => $path,
            'original_filename' => $file->getClientOriginalName(),
            'file_extension' => $file->getClientOriginalExtension(),
            'file_size' => $file->getSize(),
            'mime_type' => $file->getMimeType(),
        ]);
    }

    /**
     * Manager review of loan application.
     */
    public function managerReview(LoanApplication $application, bool $approved, ?string $notes, int $managerId): LoanApplication
    {
        $application->update([
            'status' => $approved ? 'manager_approved' : 'rejected',
            'manager_id' => $managerId,
            'manager_reviewed_at' => now(),
            'manager_review_notes' => $notes,
            'manager_approved' => $approved,
            'rejected_at' => $approved ? null : now(),
            'rejection_reason' => $approved ? null : $notes,
        ]);

        MemberActivityLog::log(
            $approved ? 'loan_manager_approved' : 'loan_manager_rejected',
            "Loan application {$application->reference_number} " . ($approved ? 'approved' : 'rejected') . " by manager",
            $application->member,
            \App\Models\User::find($managerId),
            ['loan_application_id' => $application->id]
        );

        if (!$approved) {
            $this->notificationService->notifyMemberLoanRejected($application);
        }

        return $application;
    }

    /**
     * Super manager approval of loan application.
     */
    public function superManagerApprove(LoanApplication $application, ?float $approvedAmount, ?string $notes, int $superManagerId): LoanApplication
    {
        $approvedAmount = $approvedAmount ?? $application->amount_requested;
        $product = $application->loanProduct;

        // Recalculate if amount changed
        if ($approvedAmount != $application->amount_requested) {
            $loanDetails = $this->calculateLoanDetails($product, $approvedAmount, $application->term_months);
        } else {
            $loanDetails = [
                'monthly_payment' => $application->monthly_payment,
                'total_interest' => $application->total_interest,
                'total_repayable' => $application->total_repayable,
            ];
        }

        $application->update([
            'status' => 'approved',
            'super_manager_id' => $superManagerId,
            'super_manager_approved_at' => now(),
            'super_manager_notes' => $notes,
            'super_manager_approved' => true,
            'amount_approved' => $approvedAmount,
            'monthly_payment' => $loanDetails['monthly_payment'],
            'total_interest' => $loanDetails['total_interest'],
            'total_repayable' => $loanDetails['total_repayable'],
        ]);

        MemberActivityLog::log(
            'loan_approved',
            "Loan application {$application->reference_number} approved",
            $application->member,
            \App\Models\User::find($superManagerId),
            ['loan_application_id' => $application->id, 'approved_amount' => $approvedAmount]
        );

        $this->notificationService->notifyMemberLoanApproved($application);

        return $application;
    }

    /**
     * Disburse loan.
     */
    public function disburseLoan(LoanApplication $application, string $phoneNumber, int $disbursedBy): array
    {
        return DB::transaction(function () use ($application, $phoneNumber, $disbursedBy) {
            // Ensure processing_fee is never null to avoid negative disbursement amount
            $processingFee      = (float) ($application->processing_fee ?? 0);
            $disbursementAmount = max(0, (float) $application->amount_approved - $processingFee);

            // Create transaction record
            $transaction = MembershipTransaction::create([
                'transaction_reference' => 'DIS-' . strtoupper(uniqid()),
                'type' => 'disbursement',
                'member_id' => $application->member_id,
                'loan_application_id' => $application->id,
                'amount' => $disbursementAmount,
                'currency' => $application->currency,
                'fee_amount' => $application->processing_fee,
                'net_amount' => $disbursementAmount,
                'payment_method' => 'mtn_momo',
                'payment_phone' => $phoneNumber,
                'status' => 'pending',
                'initiated_at' => now(),
                'initiated_by' => $disbursedBy,
            ]);

            try {
                $result = $this->paymentService->initiateDisbursement(
                    phoneNumber: $phoneNumber,
                    amount: $disbursementAmount,
                    reference: $transaction->transaction_reference,
                    payerMessage: "Loan disbursement - {$application->reference_number}",
                    payeeNote: "Loan disbursement for {$application->member->full_name}"
                );

                if ($result['success']) {
                    $transaction->update([
                        'external_reference' => $result['transaction_id'],
                        'status' => 'processing',
                    ]);

                    // Generate repayment schedule (idempotent — safe to call here and in callback)
                    $this->generateRepaymentSchedule($application);

                    // Transition to 'active' immediately so the member can see and repay the loan.
                    // The disbursement callback will also fire (SUCCESSFUL) and is idempotent.
                    $application->update([
                        'status' => 'active',
                        'disbursed_at' => now(),
                        'disbursed_by' => $disbursedBy,
                        'amount_disbursed' => $disbursementAmount,
                        'outstanding_balance' => $application->total_repayable,
                        'next_payment_date' => now()->addMonth(),
                        'next_payment_amount' => $application->monthly_payment,
                    ]);

                    MemberActivityLog::log(
                        'loan_disbursed',
                        "Loan {$application->reference_number} disbursed: {$disbursementAmount}",
                        $application->member,
                        \App\Models\User::find($disbursedBy),
                        ['loan_application_id' => $application->id]
                    );

                    // Notify applicant of disbursement
                    try {
                        $this->notificationService->sendDisbursementNotification($application, $disbursementAmount);
                    } catch (\Throwable $e) {
                        Log::error('Failed to send disbursement notification', [
                            'application_id' => $application->id,
                            'error' => $e->getMessage(),
                        ]);
                    }

                    return [
                        'success' => true,
                        'message' => 'Disbursement initiated successfully.',
                        'transaction_reference' => $transaction->transaction_reference,
                    ];
                }

                $transaction->markAsFailed($result['message'] ?? 'Disbursement failed');

                return [
                    'success' => false,
                    'message' => $result['message'] ?? 'Disbursement failed',
                ];
            } catch (\Exception $e) {
                $transaction->markAsFailed($e->getMessage());

                Log::error('Loan disbursement failed', [
                    'application_id' => $application->id,
                    'error' => $e->getMessage(),
                ]);

                return [
                    'success' => false,
                    'message' => 'An error occurred during disbursement.',
                ];
            }
        });
    }

    /**
     * Generate repayment schedule.
     *
     * Supports all three interest types:
     *   - reducing_balance : standard amortising schedule (interest on remaining balance)
     *   - flat_rate        : total interest = principal × rate × (term/12), split evenly
     *   - fixed            : same as flat_rate (simple interest)
     *
     * Idempotent: if a schedule already exists for this application it will not
     * be regenerated, preventing duplicate rows when both the initial disburse
     * call and the MoMo callback both attempt to create the schedule.
     */
    public function generateRepaymentSchedule(LoanApplication $application): void
    {
        // Guard: do not regenerate if schedule already exists
        if (LoanRepayment::where('loan_application_id', $application->id)->exists()) {
            Log::info('generateRepaymentSchedule: schedule already exists, skipping.', [
                'loan_application_id' => $application->id,
            ]);
            return;
        }

        // Use disbursed amount if available, otherwise approved amount
        $principal    = (float) ($application->amount_disbursed ?? $application->amount_approved);
        $termMonths   = (int)   $application->term_months;
        $annualRate   = (float) $application->interest_rate;
        $interestType = $application->loanProduct?->interest_type ?? 'reducing_balance';
        $monthlyRate  = $annualRate / 100 / 12;

        // Pre-compute schedule rows depending on interest type
        $rows = [];

        if ($interestType === 'reducing_balance') {
            // Standard amortising: fixed monthly payment, interest on remaining balance
            if ($monthlyRate > 0) {
                $monthlyPayment = $principal * $monthlyRate * pow(1 + $monthlyRate, $termMonths)
                    / (pow(1 + $monthlyRate, $termMonths) - 1);
            } else {
                $monthlyPayment = $principal / $termMonths;
            }

            $balance = $principal;
            for ($i = 1; $i <= $termMonths; $i++) {
                $interest  = round($balance * $monthlyRate, 2);
                $payment   = ($i === $termMonths) ? round($balance + $interest, 2) : round($monthlyPayment, 2);
                $pPrincipal = round($payment - $interest, 2);
                $endBalance = round(max(0, $balance - $pPrincipal), 2);

                $rows[] = [
                    'beginning_balance' => round($balance, 2),
                    'scheduled_payment' => $payment,
                    'principal'         => $pPrincipal,
                    'interest'          => $interest,
                    'ending_balance'    => $endBalance,
                ];
                $balance = $endBalance;
            }
        } else {
            // flat_rate / fixed: total interest computed upfront, split evenly
            $totalInterest  = $principal * ($annualRate / 100) * ($termMonths / 12);
            $monthlyPayment = round(($principal + $totalInterest) / $termMonths, 2);
            $monthlyInterest = round($totalInterest / $termMonths, 2);
            $monthlyPrincipal = round($principal / $termMonths, 2);

            $balance = $principal;
            for ($i = 1; $i <= $termMonths; $i++) {
                $pPrincipal = ($i === $termMonths) ? round($balance, 2) : $monthlyPrincipal;
                $interest   = ($i === $termMonths)
                    ? round($monthlyPayment * ($termMonths - $i + 1) - $balance, 2)
                    : $monthlyInterest;
                $payment    = round($pPrincipal + $interest, 2);
                $endBalance = round(max(0, $balance - $pPrincipal), 2);

                $rows[] = [
                    'beginning_balance' => round($balance, 2),
                    'scheduled_payment' => $payment,
                    'principal'         => $pPrincipal,
                    'interest'          => $interest,
                    'ending_balance'    => $endBalance,
                ];
                $balance = $endBalance;
            }
        }

        // Persist schedule rows and repayment records
        foreach ($rows as $i => $row) {
            $installmentNumber = $i + 1;
            $dueDate           = now()->addMonths($installmentNumber);

            LoanAmortizationSchedule::create([
                'loan_application_id' => $application->id,
                'installment_number'  => $installmentNumber,
                'payment_date'        => $dueDate,
                'beginning_balance'   => $row['beginning_balance'],
                'scheduled_payment'   => $row['scheduled_payment'],
                'principal'           => $row['principal'],
                'interest'            => $row['interest'],
                'ending_balance'      => $row['ending_balance'],
            ]);

            LoanRepayment::create([
                'reference_number'    => LoanRepayment::generateReferenceNumber(),
                'loan_application_id' => $application->id,
                'member_id'           => $application->member_id,
                'installment_number'  => $installmentNumber,
                'due_date'            => $dueDate,
                'scheduled_amount'    => $row['scheduled_payment'],
                'scheduled_principal' => $row['principal'],
                'scheduled_interest'  => $row['interest'],
                'principal_balance'   => $row['ending_balance'],
                'status'              => 'scheduled',
            ]);
        }
    }

    /**
     * Process loan repayment.
     */
    public function processRepayment(
        LoanRepayment $repayment,
        string $phoneNumber,
        string $paymentMethod = 'mobile',
        ?string $cardNumber = null,
        ?string $cardExpiry = null,
        ?string $cardCvv = null
    ): array {
        return DB::transaction(function () use ($repayment, $phoneNumber, $paymentMethod, $cardNumber, $cardExpiry, $cardCvv) {
            $application  = $repayment->loanApplication;
            $storedMethod = $paymentMethod === 'card' ? 'mtn_momo_card' : 'mtn_momo';

            $transaction = MembershipTransaction::create([
                'transaction_reference' => 'REP-' . strtoupper(uniqid()),
                'type' => 'loan_repayment',
                'member_id' => $repayment->member_id,
                'loan_application_id' => $application->id,
                'loan_repayment_id' => $repayment->id,
                'amount' => $repayment->scheduled_amount,
                'currency' => $application->currency,
                'fee_amount' => 0,
                'net_amount' => $repayment->scheduled_amount,
                'payment_method' => $storedMethod,
                'payment_phone' => $paymentMethod !== 'card' ? $phoneNumber : null,
                'status' => 'pending',
                'initiated_at' => now(),
                'initiated_by' => $repayment->member->user_id,
            ]);

            try {
                if ($paymentMethod === 'card') {
                    $result = $this->paymentService->initiateCardCollection(
                        cardNumber: $cardNumber ?? '',
                        expiry: $cardExpiry ?? '',
                        cvv: $cardCvv ?? '',
                        amount: $repayment->scheduled_amount,
                        reference: $transaction->transaction_reference,
                        payerMessage: "Loan repayment - {$application->reference_number}",
                        payeeNote: "Loan repayment for {$repayment->member->full_name}"
                    );
                } else {
                    $result = $this->paymentService->initiateCollection(
                        phoneNumber: $phoneNumber,
                        amount: $repayment->scheduled_amount,
                        reference: $transaction->transaction_reference,
                        payerMessage: "Loan repayment - {$application->reference_number}",
                        payeeNote: "Loan repayment for {$repayment->member->full_name}"
                    );
                }

                if ($result['success']) {
                    $transaction->update([
                        'external_reference' => $result['transaction_id'],
                        'status' => 'processing',
                    ]);

                    return [
                        'success' => true,
                        'message' => 'Payment initiated. Please check your phone.',
                        'transaction_reference' => $transaction->transaction_reference,
                    ];
                }

                $transaction->markAsFailed($result['message'] ?? 'Payment failed');

                try {
                    $this->notificationService->sendLoanRepaymentFailed($repayment, $result['message'] ?? 'Payment failed');
                } catch (\Throwable $e) {
                    Log::error('Failed to send repayment failure notification', [
                        'repayment_id' => $repayment->id,
                        'error' => $e->getMessage(),
                    ]);
                }

                return [
                    'success' => false,
                    'message' => $result['message'] ?? 'Payment failed',
                ];
            } catch (\Exception $e) {
                $transaction->markAsFailed($e->getMessage());

                return [
                    'success' => false,
                    'message' => 'An error occurred while processing the payment.',
                ];
            }
        });
    }

    /**
     * Handle disbursement callback.
     */
    public function handleDisbursementCallback(string $transactionReference, string $status, array $data = []): void
    {
        $transaction = MembershipTransaction::where('transaction_reference', $transactionReference)->first();

        if (!$transaction || !$transaction->loan_application_id) {
            Log::warning('Disbursement callback: transaction not found or missing loan_application_id', [
                'reference' => $transactionReference,
            ]);
            return;
        }

        $application = LoanApplication::find($transaction->loan_application_id);

        if (!$application) {
            Log::warning('Disbursement callback: LoanApplication not found', [
                'reference' => $transactionReference,
            ]);
            return;
        }

        if ($status === 'SUCCESSFUL') {
            $transaction->markAsCompleted($data['transaction_id'] ?? null, $data);

            $disbursedAmount = $transaction->amount;

            // Only update if not already active (idempotent — disburseLoan() may have already set this)
            if (!in_array($application->status, ['active', 'completed'])) {
                $application->update([
                    'status'                 => 'active',
                    'disbursed_at'           => $application->disbursed_at ?? now(),
                    'amount_disbursed'       => $disbursedAmount,
                    'outstanding_balance'    => $application->total_repayable,
                    'next_payment_date'      => $application->next_payment_date ?? now()->addMonth(),
                    'next_payment_amount'    => $application->monthly_payment,
                    'disbursement_reference' => $transactionReference,
                ]);
            } else {
                // Still record the disbursement reference if missing
                if (!$application->disbursement_reference) {
                    $application->update(['disbursement_reference' => $transactionReference]);
                }
            }

            // Generate repayment schedule if not already created
            // (handles the case where disburseLoan() failed mid-way after
            //  the MoMo request was sent but before the schedule was persisted)
            $this->generateRepaymentSchedule($application->fresh());

            Log::info('Loan disbursement confirmed via callback', [
                'reference'      => $transactionReference,
                'application_id' => $application->id,
            ]);

            try {
                $this->notificationService->sendDisbursementNotification($application, (float) $disbursedAmount);
            } catch (\Throwable $e) {
                Log::error('Failed to send disbursement notification after callback', [
                    'application_id' => $application->id,
                    'error'          => $e->getMessage(),
                ]);
            }
        } elseif (in_array($status, ['FAILED', 'REJECTED'])) {
            $transaction->markAsFailed($data['reason'] ?? 'Disbursement failed', $status);

            Log::info('Loan disbursement failed via callback', [
                'reference' => $transactionReference,
                'status'    => $status,
            ]);
        }
    }

    /**
     * Handle repayment callback.
     */
    public function handleRepaymentCallback(string $transactionReference, string $status, array $data = []): void
    {
        $transaction = MembershipTransaction::where('transaction_reference', $transactionReference)->first();

        if (!$transaction || !$transaction->loan_repayment_id) {
            return;
        }

        if ($status === 'SUCCESSFUL') {
            $transaction->markAsCompleted($data['transaction_id'] ?? null, $data);

            $repayment = LoanRepayment::find($transaction->loan_repayment_id);
            $application = $repayment->loanApplication;

            $repayment->markAsPaid([
                'paid_date' => now(),
                'amount_paid' => $repayment->scheduled_amount,
                'principal_paid' => $repayment->scheduled_principal,
                'interest_paid' => $repayment->scheduled_interest,
                'payment_method' => 'mtn_momo',
                'payment_reference' => $transactionReference,
                'transaction_id' => $data['transaction_id'] ?? null,
                'payment_details' => $data,
            ]);

            // Update application balance
            $newAmountRepaid    = (float) $application->amount_repaid    + (float) $repayment->scheduled_amount;
            $newPrincipalRepaid = (float) $application->principal_repaid + (float) $repayment->scheduled_principal;
            $newInterestRepaid  = (float) $application->interest_repaid  + (float) $repayment->scheduled_interest;
            $newOutstanding     = max(0, (float) $application->outstanding_balance - (float) $repayment->scheduled_amount);

            // Find the next unpaid installment to update next_payment_date
            $nextInstallment = LoanRepayment::where('loan_application_id', $application->id)
                ->whereIn('status', ['scheduled', 'pending', 'overdue'])
                ->where('installment_number', '>', $repayment->installment_number)
                ->orderBy('installment_number')
                ->first();

            $applicationUpdates = [
                'amount_repaid'       => $newAmountRepaid,
                'principal_repaid'    => $newPrincipalRepaid,
                'interest_repaid'     => $newInterestRepaid,
                'outstanding_balance' => $newOutstanding,
            ];

            if ($nextInstallment) {
                $applicationUpdates['next_payment_date']   = $nextInstallment->due_date;
                $applicationUpdates['next_payment_amount'] = $nextInstallment->scheduled_amount;
            }

            $application->update($applicationUpdates);

            // Check if loan is fully paid
            if ($newOutstanding <= 0) {
                $application->update(['status' => 'completed', 'next_payment_date' => null, 'next_payment_amount' => null]);
            }

            $this->notificationService->sendLoanRepaymentConfirmation($repayment);
        } else {
            $transaction->markAsFailed($data['reason'] ?? 'Payment failed', $status);

            $repayment = LoanRepayment::find($transaction->loan_repayment_id);
            if ($repayment) {
                try {
                    $this->notificationService->sendLoanRepaymentFailed($repayment, $data['reason'] ?? 'Payment failed');
                } catch (\Throwable $e) {
                    Log::error('Failed to send repayment failure notification after callback', [
                        'repayment_id' => $repayment->id,
                        'error'        => $e->getMessage(),
                    ]);
                }
            }
        }
    }

    /**
     * Get loan statistics.
     *
     * Returns keys used by both the admin applications index view and the
     * membership dashboard:
     *   total, pending, under_review, manager_approved, approved,
     *   disbursed, active, rejected, completed, defaulted,
     *   active_loans, overdue_loans, total_disbursed, total_repaid,
     *   outstanding_balance
     */
    public function getStatistics(): array
    {
        $total        = LoanApplication::count();
        $pending      = LoanApplication::whereIn('status', ['pending', 'under_review'])->count();
        $managerApproved = LoanApplication::where('status', 'manager_approved')->count();
        $approved     = LoanApplication::where('status', 'approved')->count();
        $disbursed    = LoanApplication::where('status', 'disbursed')->count();
        $active       = LoanApplication::where('status', 'active')->count();
        $completed    = LoanApplication::where('status', 'completed')->count();
        $rejected     = LoanApplication::where('status', 'rejected')->count();
        $defaulted    = LoanApplication::where('status', 'defaulted')->count();

        return [
            // Keys expected by admin applications/index view
            'total'            => $total,
            'pending'          => $pending,
            'manager_approved' => $managerApproved,
            'approved'         => $approved,
            'disbursed'        => $disbursed,
            'rejected'         => $rejected,

            // Additional keys used by dashboard / other views
            'active_loans'        => $active,
            'completed'           => $completed,
            'defaulted'           => $defaulted,
            'overdue_loans'       => LoanApplication::overdue()->count(),
            'total_disbursed'     => LoanApplication::whereIn('status', ['disbursed', 'active', 'completed'])
                                        ->sum('amount_disbursed'),
            'total_repaid'        => LoanApplication::whereIn('status', ['disbursed', 'active', 'completed'])
                                        ->sum('amount_repaid'),
            'outstanding_balance' => LoanApplication::active()->sum('outstanding_balance'),
        ];
    }
}
