<?php

namespace App\Http\Controllers;

use App\Models\PaymentLog;
use App\Services\ContributionService;
use App\Services\LoanService;
use App\Services\Mentorship\PaymentService as MentorshipPaymentService;
use App\Services\RegistrationService;
use App\Services\SelfPacedService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;

/**
 * MomoCallbackController
 *
 * Handles the single global MTN MoMo webhook endpoint.
 * Routes callbacks to the correct module service based on the externalId prefix.
 *
 * Routing table:
 *   SP-ENR-    → SelfPacedService::handlePaymentCallback
 *   TR-REG-    → RegistrationService::handlePaymentCallback
 *   REP-       → LoanService::handleRepaymentCallback
 *   DIS-       → LoanService::handleDisbursementCallback (via handleRepaymentCallback)
 *   CON-PAY-   → ContributionService::handlePaymentCallback
 *   TRX-       → ContributionService::handlePaymentCallback (legacy prefix)
 *   INST-      → PaymentController::handleInstitutionCallback (inline)
 *   MACH-      → PaymentController::handleMachineCallback (inline)
 *   MENTOR-    → MentorshipPaymentService::handleCallback
 */
class MomoCallbackController extends Controller
{
    public function __construct(
        protected SelfPacedService $selfPacedService,
        protected RegistrationService $registrationService,
        protected LoanService $loanService,
        protected ContributionService $contributionService,
        protected MentorshipPaymentService $mentorshipPaymentService,
    ) {}

    /**
     * Handle an incoming MTN MoMo callback.
     *
     * Always returns HTTP 200 to prevent MTN MoMo from retrying.
     */
    public function handle(Request $request): JsonResponse
    {
        $payload = $request->all();

        // Log every callback immediately
        PaymentLog::log(
            type: PaymentLog::TYPE_CALLBACK,
            endpoint: $request->fullUrl(),
            requestPayload: $payload,
            responseStatus: 200,
            responseBody: null,
            transactionReference: $payload['externalId'] ?? null,
            executionTime: null,
            errorMessage: null
        );

        // Validate required fields
        $externalId            = $payload['externalId']            ?? null;
        $status                = $payload['status']                ?? null;
        $financialTransactionId = $payload['financialTransactionId'] ?? null;

        if (!$externalId || !$status || !$financialTransactionId) {
            Log::warning('MTN MoMo callback missing required fields', [
                'payload'    => $payload,
                'missing'    => array_filter([
                    'externalId'             => !$externalId,
                    'status'                 => !$status,
                    'financialTransactionId' => !$financialTransactionId,
                ]),
            ]);

            // Still return 200 — we don't want MTN to keep retrying a malformed payload
            return response()->json(['received' => true]);
        }

        Log::info('MTN MoMo callback received', [
            'externalId'             => $externalId,
            'status'                 => $status,
            'financialTransactionId' => $financialTransactionId,
        ]);

        try {
            $this->route($externalId, $status, $financialTransactionId, $payload);
        } catch (\Throwable $e) {
            // Log but never propagate — always return 200
            Log::error('MTN MoMo callback processing error', [
                'externalId' => $externalId,
                'status'     => $status,
                'error'      => $e->getMessage(),
                'trace'      => $e->getTraceAsString(),
            ]);
        }

        return response()->json(['received' => true]);
    }

    /**
     * Route the callback to the correct module service based on the externalId prefix.
     */
    protected function route(
        string $externalId,
        string $status,
        string $financialTransactionId,
        array  $payload
    ): void {
        $data = array_merge($payload, [
            'transaction_id'          => $financialTransactionId,
            'financialTransactionId'  => $financialTransactionId,
        ]);

        // Self-Paced enrollments: SP-ENR-XXXXXXXXXX
        if (str_starts_with($externalId, 'SP-ENR-')) {
            $this->selfPacedService->handlePaymentCallback($externalId, $status, $data);
            return;
        }

        // Training registrations: TR-REG-XXXXXXXXXX
        if (str_starts_with($externalId, 'TR-REG-')) {
            $this->registrationService->handlePaymentCallback($externalId, $status, $data);
            return;
        }

        // Loan repayments: REP-XXXXXXXXXX
        if (str_starts_with($externalId, 'REP-')) {
            $this->loanService->handleRepaymentCallback($externalId, $status, $data);
            return;
        }

        // Loan disbursements: DIS-XXXXXXXXXX
        if (str_starts_with($externalId, 'DIS-')) {
            $this->loanService->handleDisbursementCallback($externalId, $status, $data);
            return;
        }

        // Contributions: CON-PAY-XXXXXXXXXX or legacy TRX-XXXXXXXXXX
        if (str_starts_with($externalId, 'CON-PAY-') || str_starts_with($externalId, 'TRX-')) {
            $this->contributionService->handlePaymentCallback($externalId, $status, $data);
            return;
        }

        // Health Directory — institution listings: INST-XXXXXXXXXX
        if (str_starts_with($externalId, 'INST-') || str_starts_with($externalId, 'INS-PAY-')) {
            $this->handleInstitutionCallback($externalId, $status, $data);
            return;
        }

        // Health Directory — machine listings: MACH-XXXXXXXXXX
        if (str_starts_with($externalId, 'MACH-') || str_starts_with($externalId, 'MCH-PAY-')) {
            $this->handleMachineCallback($externalId, $status, $data);
            return;
        }

        // Mentorship payments: MENTOR-XXXXXXXXXX or MENT-PAY-XXXXXXXXXX
        if (str_starts_with($externalId, 'MENTOR-') || str_starts_with($externalId, 'MENT-PAY-')) {
            $this->mentorshipPaymentService->handleCallback(array_merge($data, [
                'reference' => $externalId,
                'status'    => $status,
            ]));
            return;
        }

        // No matching prefix — log and return (HTTP 200 already guaranteed by caller)
        Log::warning('MTN MoMo callback: no module matched for externalId', [
            'externalId' => $externalId,
            'status'     => $status,
        ]);
    }

    /**
     * Handle institution listing payment callback.
     *
     * Requirements: 1.3, 9.1
     */
    protected function handleInstitutionCallback(string $reference, string $status, array $data): void
    {
        $payment = \App\Models\InstitutionPayment::where('reference', $reference)->first();

        if (!$payment) {
            Log::warning('MoMo callback: InstitutionPayment not found', ['reference' => $reference]);
            return;
        }

        if ($status === 'SUCCESSFUL') {
            $payment->markSuccessful($data['financialTransactionId'] ?? $data['transaction_id'] ?? '');
            Log::info('Institution payment activated via callback', ['reference' => $reference]);
        } elseif (in_array($status, ['FAILED', 'REJECTED'])) {
            $payment->markFailed($data['reason'] ?? 'Payment ' . strtolower($status));
            Log::info('Institution payment failed via callback', ['reference' => $reference, 'status' => $status]);
        }
    }

    /**
     * Handle machine listing payment callback.
     *
     * Requirements: 1.3, 9.2
     */
    protected function handleMachineCallback(string $reference, string $status, array $data): void
    {
        $payment = \App\Models\MachinePayment::where('reference', $reference)->first();

        if (!$payment) {
            Log::warning('MoMo callback: MachinePayment not found', ['reference' => $reference]);
            return;
        }

        if ($status === 'SUCCESSFUL') {
            $payment->markSuccessful($data['financialTransactionId'] ?? $data['transaction_id'] ?? '');
            Log::info('Machine payment activated via callback', ['reference' => $reference]);
        } elseif (in_array($status, ['FAILED', 'REJECTED'])) {
            $payment->markFailed($data['reason'] ?? 'Payment ' . strtolower($status));
            Log::info('Machine payment failed via callback', ['reference' => $reference, 'status' => $status]);
        }
    }
}
