<?php
header("Access-Control-Allow-Origin: http://localhost:51147");
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json");
header("Access-Control-Allow-Methods: GET, POST, PATCH, DELETE");
header("Access-Control-Allow-Headers: Content-Type, Accept");
// === DEBUG: SHOW ALL GET PARAMS ===
error_log("GET Params: " . print_r($_GET, true));

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    error_log("Handling OPTIONS request for {$_SERVER['REQUEST_URI']}");
    http_response_code(200);
    header("Content-Type: text/plain");
    header("Access-Control-Allow-Origin: *");
    header("Access-Control-Allow-Methods: GET, POST, OPTIONS, PATCH, DELETE");
    header("Access-Control-Allow-Headers: Content-Type, Accept");
    exit;
}

// Database configuration
const DB_HOST = 'localhost';
const DB_USER = 'nebocoke_root';
const DB_PASS = 'Moses@4602';
const DB_NAME = 'nebocoke_saveapp';
const DEBUG = true;

// Informa API configuration
const INFORMA_API_KEY = '1PxlknjjiPtGXe9EYEkTeTKnXSivnk0OJT2JfudSBFbvkJr1MqxMKwAZQh2CaKN50';
const INFORMA_LIVE_ENDPOINT = 'https://api.informa.co.ke';
const INFORMA_SANDBOX_ENDPOINT = 'https://sandbox.informa.co.ke';
const INFORMA_API_BASE = INFORMA_SANDBOX_ENDPOINT; // Changed to sandbox for testing

// Safaricom M-PESA API configuration

const MPESA_B2B_SECURITY_CREDENTIAL = 'placeholder_credential'; // Not needed for STK Push
const MPESA_B2B_SHORTCODE = '4088127';

const MPESA_CALLBACK_URL = 'http://apis.nebo.co.ke/apis/mpesa-callback.php'; // Update with your ngrok URL
const MPESA_CONSUMER_KEY = 'iuqlSSSznmd7xkTrhfqhd0bs69BwGRhs';
const MPESA_CONSUMER_SECRET = 'ZyBbCZrWkmhYS4wH';
const MPESA_PASSKEY = '948e4bb1270ef63ed7c97edf9fcd58e16de93e3a62463399aa584979a6f679ac';
const MPESA_SHORTCODE = '4088127';

const MPESA_ENV = 'live';
// Firebase configuration
const FIREBASE_PROJECT_ID = 'save-d84c2';

// Helper function to verify Firebase ID token
function verifyFirebaseIdToken($idToken) {
    $parts = explode('.', $idToken);
    if (count($parts) !== 3) {
        return ['status' => 'error', 'message' => 'Invalid token format'];
    }
    
    $header = json_decode(base64_decode(str_replace(['-', '_'], ['+', '/'], $parts[0])), true);
    $payload = json_decode(base64_decode(str_replace(['-', '_'], ['+', '/'], $parts[1])), true);
    $signature = base64_decode(str_replace(['-', '_'], ['+', '/'], $parts[2]));
    
    if (!$header || !$payload) {
        return ['status' => 'error', 'message' => 'Invalid token structure'];
    }

    if (!isset($payload['exp']) || $payload['exp'] < time()) {
        return ['status' => 'error', 'message' => 'Token expired'];
    }

    if (!isset($payload['aud']) || $payload['aud'] !== FIREBASE_PROJECT_ID) {
        return ['status' => 'error', 'message' => 'Token audience mismatch'];
    }

    if (!isset($payload['iss']) || $payload['iss'] !== 'https://securetoken.google.com/' . FIREBASE_PROJECT_ID) {
        return ['status' => 'error', 'message' => 'Invalid issuer'];
    }

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    if (curl_errno($ch) || $httpCode !== 200) {
        curl_close($ch);
        return ['status' => 'error', 'message' => 'Failed to fetch public keys'];
    }
    curl_close($ch);
    $publicKeys = json_decode($response, true);

    if (!isset($header['kid']) || !isset($publicKeys[$header['kid']])) {
        return ['status' => 'error', 'message' => 'Invalid key ID'];
    }

    $data = $parts[0] . '.' . $parts[1];
    $publicKey = $publicKeys[$header['kid']];
    $isValid = openssl_verify(
        $data,
        $signature,
        $publicKey,
        OPENSSL_ALGO_SHA256
    );

    if ($isValid !== 1) {
        return ['status' => 'error', 'message' => 'Invalid signature'];
    }

    return [
        'status' => 'success',
        'email' => $payload['email'] ?? '',
        'name' => $payload['name'] ?? '',
        'google_id' => $payload['sub'] ?? ''
    ];
}
// PayHero Config (Username + Password Auth)
const PAYHERO_USERNAME = '4fK8JeCSaA9CUjmE8Wbm';
const PAYHERO_PASSWORD = 'huC8nDwwcjlF8m3ZUEwBOsklRXl5SniwqZUMdaER';
const PAYHERO_ENCODED_AUTH = 'NGZLOEplQ1NhQTlDVWptRThXYm06aHVDOG5Ed3djamxGOG0zWlVFd0JPc2tsUlhsNVNuaXdxWlVNZGFFUg==';  // Your key
const PAYHERO_BASE_URL = 'https://backend.payhero.co.ke/api/v2';  // v2 from docs

const PAYHERO_CHANNEL_ID = 4311;
const PAYHERO_CREDENTIAL_ID = '3242';  // Get from PayHero dashboard > Credentials


// Basic Auth Header
function getPayHeroAuthHeader() {
    $credentials = base64_encode(PAYHERO_USERNAME . ':' . PAYHERO_PASSWORD);
    return 'Basic ' . $credentials;
}

// PayHero STK Push (v2 from Docs)
// PayHero STK Push (Exact Match to Your cURL Example)
// PayHero STK Push (Exact Your cURL + Encoded Auth Key)
function payHeroSTKPush($phone, $amount, $ref, $callback = null, $customer_name = 'SaveApp User') {
    $url = PAYHERO_BASE_URL . '/payments';  // Exact from your cURL
    $payload = [
        'amount' => (int)$amount,
        'phone_number' => $phone,
        'channel_id' => PAYHERO_CHANNEL_ID,  // Exact
        'provider' => 'm-pesa',  // Exact
        'external_reference' => $ref,  // Exact
        'customer_name' => $customer_name  // Exact
    ];
    if ($callback) $payload['callback_url'] = $callback;  // Optional

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_ENCODING => '',  // Exact
        CURLOPT_MAXREDIRS => 10,  // Exact
        CURLOPT_TIMEOUT => 0,  // Exact
        CURLOPT_FOLLOWLOCATION => true,  // Exact
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,  // Exact
        CURLOPT_CUSTOMREQUEST => 'POST',  // Exact
        CURLOPT_POSTFIELDS => json_encode($payload),  // Exact JSON
        CURLOPT_HTTPHEADER => [
            'Content-Type: application/json',  // Exact
            'Authorization: Basic ' . PAYHERO_ENCODED_AUTH  // Your encoded key (fixes Unauthorized)
        ],
    ]);
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    error_log("PayHero STK Response ($httpCode): " . $response);  // Debug: Check server logs for full response

    $result = json_decode($response, true);
    if ($httpCode === 200 && isset($result['status']) && $result['status'] === 'success') {
        return ['status' => 'success', 'checkout_id' => $result['data']['checkout_request_id'] ?? $result['data']['id'] ?? ''];
    }
    return ['status' => 'error', 'message' => $result['message'] ?? 'STK failed (' . $httpCode . ')'];
}
// PayHero B2C (Withdraw/Merchant - v2)
function payHeroB2C($phone, $amount, $ref, $narration = 'Payment', $callback = null) {
    $url = PAYHERO_BASE_URL . '/payments/b2c';  // v2 B2C from docs
    $payload = [
        'phone_number' => $phone,
        'amount' => (int)$amount,
        'account_reference' => $ref,
        'transaction_desc' => $narration
    ];
    if ($callback) $payload['callback_url'] = $callback;

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => json_encode($payload),
        CURLOPT_HTTPHEADER => [
            'Authorization: ' . getPayHeroAuthHeader(),
            'Content-Type: application/json'
        ]
    ]);
    $resp = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    error_log("PayHero B2C Response ($httpCode): " . $resp);

    $result = json_decode($resp, true);
    if ($httpCode === 200 && isset($result['success']) && $result['success']) {
        return ['status' => 'success', 'ref' => $result['data']['transaction_ref'] ?? $result['data']['id']];
    }
    return ['status' => 'error', 'message' => $result['message'] ?? 'B2C failed (' . $httpCode . ')'];
}
// Create required tables
// Create required tables
function createRequiredTables() {
    $conn = getDbConnection();
    
    $usersTableQuery = "
        CREATE TABLE IF NOT EXISTS users (
            id INT AUTO_INCREMENT PRIMARY KEY,
            full_name VARCHAR(255) NOT NULL,
            date_of_birth DATE NULL,
            email VARCHAR(255) NOT NULL UNIQUE,
            phone_number VARCHAR(20) NOT NULL UNIQUE,
            national_id VARCHAR(20) NULL UNIQUE,
            password VARCHAR(255) NOT NULL,
            selfie_path VARCHAR(255) NULL,
            id_document_path VARCHAR(255) NULL,
            face_verified BOOLEAN DEFAULT FALSE,
            google_id VARCHAR(255) DEFAULT NULL UNIQUE,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        ) ENGINE=InnoDB
    ";
    $conn->query($usersTableQuery) ? error_log("Users table created or already exists") : error_log("Error creating users table: " . $conn->error);
    
    $transactionsTableQuery = "
        CREATE TABLE IF NOT EXISTS transactions (
            id INT AUTO_INCREMENT PRIMARY KEY,
            user_id INT NOT NULL,
            phone_number VARCHAR(15) NOT NULL,
            total_amount DECIMAL(10,2) NOT NULL,
            service_charge DECIMAL(10,2) NOT NULL,
            savings_amount DECIMAL(10,2) NOT NULL,
            merchant_amount DECIMAL(10,2) NOT NULL,
            merchant_paybill VARCHAR(20) NOT NULL,
            merchant_account VARCHAR(50) DEFAULT '',
            transaction_type ENUM('pay_bill', 'buy_goods') DEFAULT 'pay_bill',
            status ENUM('pending', 'completed', 'failed') DEFAULT 'pending',
            merchant_status ENUM('pending', 'completed', 'failed') DEFAULT 'pending',
            checkout_request_id VARCHAR(50) DEFAULT NULL,
            mpesa_receipt VARCHAR(20) DEFAULT NULL,
            merchant_reference VARCHAR(50) DEFAULT NULL,
            error_message TEXT DEFAULT NULL,
            merchant_error TEXT DEFAULT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            validated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
        ) ENGINE=InnoDB
    ";
    $conn->query($transactionsTableQuery) ? error_log("Transactions table created or already exists") : error_log("Error creating transactions table: " . $conn->error);
    
    $favoritesTableQuery = "
        CREATE TABLE IF NOT EXISTS favorites (
            id INT AUTO_INCREMENT PRIMARY KEY,
            user_id INT NOT NULL,
            name VARCHAR(100) NOT NULL,
            till_number VARCHAR(20) NOT NULL,
            account_number VARCHAR(50) DEFAULT '',
            type ENUM('buy_goods', 'pay_bill') NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY unique_till_user (user_id, till_number, type),
            FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
        ) ENGINE=InnoDB
    ";
    $conn->query($favoritesTableQuery) ? error_log("Favorites table created or already exists") : error_log("Error creating favorites table: " . $conn->error);
    
    $mpesaUsageTableQuery = "
        CREATE TABLE IF NOT EXISTS mpesa_usage (
            user_id INT PRIMARY KEY,
            total_spent DECIMAL(15,2) DEFAULT 0,
            weekly_avg DECIMAL(15,2) DEFAULT 0,
            monthly_avg DECIMAL(15,2) DEFAULT 0,
            yearly_avg DECIMAL(15,2) DEFAULT 0,
            usage_band VARCHAR(50) DEFAULT '',
            transaction_count INT DEFAULT 0,
            last_updated DATETIME,
            FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
        ) ENGINE=InnoDB
    ";
    $conn->query($mpesaUsageTableQuery) ? error_log("Mpesa_usage table created or already exists") : error_log("Error creating mpesa_usage table: " . $conn->error);
    
    $roundupSettingsTableQuery = "
        CREATE TABLE IF NOT EXISTS roundup_settings (
            user_id INT PRIMARY KEY,
            is_enabled BOOLEAN DEFAULT FALSE,
            pay_bill_round_up BOOLEAN DEFAULT FALSE,
            buy_goods_round_up BOOLEAN DEFAULT FALSE,
            retail_purchases_round_up BOOLEAN DEFAULT FALSE,
            rounding_value VARCHAR(10) DEFAULT 'KSh 1',
            max_round_up DECIMAL(10,2) DEFAULT NULL,
            monthly_cap DECIMAL(10,2) DEFAULT NULL,
            FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
        ) ENGINE=InnoDB
    ";
    $conn->query($roundupSettingsTableQuery) ? error_log("Roundup_settings table created or already exists") : error_log("Error creating roundup_settings table: " . $conn->error);
    
    $verificationLogsTableQuery = "
        CREATE TABLE IF NOT EXISTS verification_logs (
            id INT AUTO_INCREMENT PRIMARY KEY,
            user_id INT NOT NULL,
            verification_type VARCHAR(50) NOT NULL,
            request_id VARCHAR(50) DEFAULT NULL,
            status VARCHAR(20) NOT NULL,
            message TEXT DEFAULT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
        ) ENGINE=InnoDB
    ";
    $conn->query($verificationLogsTableQuery) ? error_log("Verification_logs table created or already exists") : error_log("Error creating verification_logs table: " . $conn->error);
    
    $passwordResetTokensTableQuery = "
        CREATE TABLE IF NOT EXISTS password_reset_tokens (
            id INT AUTO_INCREMENT PRIMARY KEY,
            user_id INT NOT NULL,
            token VARCHAR(255) NOT NULL,
            expires_at DATETIME NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
            UNIQUE KEY unique_token (token)
        ) ENGINE=InnoDB
    ";
    $conn->query($passwordResetTokensTableQuery) ? error_log("Password_reset_tokens table created or already exists") : error_log("Error creating password_reset_tokens table: " . $conn->error);
    
    $conn->close();
}

// Helper function to send password reset email
function sendPasswordResetEmail($email, $token) {
    $resetLink = "http://apis.nebo.co.ke/apis/reset-password?token=$token"; // Replace with your front-end reset password URL
    $subject = "Password Reset Request";
    $message = "Hello,\n\nYou requested a password reset. Click the link below to reset your password:\n$resetLink\n\nThis link will expire in 1 hour.\n\nIf you did not request a password reset, please ignore this email.\n\nBest regards,\nSaveApp Team";
    $headers = "From: no-reply@your-app-domain.com\r\n";
    
    if (mail($email, $subject, $message, $headers)) {
        return ['status' => 'success', 'message' => 'Password reset email sent successfully'];
    } else {
        return ['status' => 'error', 'message' => 'Failed to send password reset email'];
    }
}
// Connect to MySQL
function getDbConnection() {
    $conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
    if ($conn->connect_error) {
        http_response_code(500);
        echo json_encode(['status' => 'error', 'message' => 'Database connection failed: ' . $conn->connect_error]);
        exit;
    }
    return $conn;
}

// Handle selfie upload
function uploadSelfie($file) {
    $uploadDir = 'Uploads/Selfies/';
    if (!is_dir($uploadDir)) {
        mkdir($uploadDir, 0777, true);
    }
    $fileName = time() . '-' . uniqid() . '.' . pathinfo($file['name'], PATHINFO_EXTENSION);
    $uploadPath = $uploadDir . $fileName;
    if (move_uploaded_file($file['tmp_name'], $uploadPath)) {
        return $fileName;
    }
    return null;
}

// Handle ID document upload
function uploadIdDocument($file) {
    $uploadDir = 'Uploads/ID/';
    if (!is_dir($uploadDir)) {
        mkdir($uploadDir, 0777, true);
    }
    $fileName = time() . '-' . uniqid() . '.' . pathinfo($file['name'], PATHINFO_EXTENSION);
    $uploadPath = $uploadDir . $fileName;
    if (move_uploaded_file($file['tmp_name'], $uploadPath)) {
        return $fileName;
    }
    return null;
}

// Informa API helper function
function callInformaApi($endpoint, $method = 'GET', $params = [], $isFileUpload = false) {
    $url = INFORMA_API_BASE . $endpoint;
    $ch = curl_init();
    $headers = [
        'Authorization: Bearer ' . INFORMA_API_KEY,
        'Accept: application/vnd.informa.v1+json'
    ];
    if (!$isFileUpload) {
        $headers[] = 'Content-Type: application/json';
    }
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
    if ($method === 'POST') {
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $isFileUpload ? $params : json_encode($params));
    } elseif ($method === 'GET' && !empty($params)) {
        $url .= '?' . http_build_query($params);
        curl_setopt($ch, CURLOPT_URL, $url);
    }
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    if (DEBUG) {
        error_log("Informa API Request: $url");
        error_log("Informa API Response: $response");
    }
    if (curl_errno($ch)) {
        curl_close($ch);
        return ['status' => 'error', 'message' => 'API request failed: ' . curl_error($ch)];
    }
    curl_close($ch);
    $data = json_decode($response, true);
    if ($httpCode >= 400) {
        return ['status' => 'error', 'message' => $data['error']['message'] ?? 'Unknown API error', 'code' => $httpCode];
    }
    return $data;
}

// Verify identity with Informa (face comparison)
function verifyIdentityWithInforma($selfiePath, $idPath) {
    $selfieFullPath = __DIR__ . '/Uploads/Selfies/' . $selfiePath;
    $idFullPath = __DIR__ . '/Uploads/ID/' . $idPath;
    if (!file_exists($selfieFullPath) || !file_exists($idFullPath)) {
        return ['status' => 'error', 'message' => 'Required image files not found'];
    }
    $requestId = time() . uniqid();
    $cFile1 = new CURLFile($selfieFullPath, mime_content_type($selfieFullPath), 'photo_one');
    $cFile2 = new CURLFile($idFullPath, mime_content_type($idFullPath), 'photo_two');
    $postData = [
        'photo_one' => $cFile1,
        'photo_two' => $cFile2,
        'request_id' => $requestId
    ];
    return callInformaApi('/verify/compare-faces', 'POST', $postData, true);
}

// Log verification attempt
function logVerification($userId, $verificationType, $requestId, $status, $message = null) {
    $conn = getDbConnection();
    $stmt = $conn->prepare(
        "INSERT INTO verification_logs (user_id, verification_type, request_id, status, message) 
         VALUES (?, ?, ?, ?, ?)"
    );
    $stmt->bind_param("issss", $userId, $verificationType, $requestId, $status, $message);
    $stmt->execute();
    $stmt->close();
    $conn->close();
}

// Format phone number to required format (2547XXXXXXXX)
function formatPhoneNumber($phone) {
    $phone = preg_replace('/\D/', '', $phone);
    if (substr($phone, 0, 1) == '0') {
        $phone = '254' . substr($phone, 1);
    }
    if (strlen($phone) == 9) {
        $phone = '254' . $phone;
    }
    return $phone;
}

// Parse rounding value from string (e.g., "KSh 10" to 10)
function parseRoundingValue($roundingValue) {
    return (float)preg_replace('/[^0-9.]/', '', $roundingValue);
}

// Calculate round-up amount based on transaction amount and rounding value
function calculateRoundUpAmount($amount, $roundingValue) {
    $roundedAmount = ceil($amount / $roundingValue) * $roundingValue;
    return $roundedAmount - $amount;
}

// Get M-PESA access token
function getMpesaAccessToken() {
    $url = (MPESA_ENV == 'live') ?
        'https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials' :
        'https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials';
    $credentials = base64_encode(MPESA_CONSUMER_KEY . ':' . MPESA_CONSUMER_SECRET);
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Basic ' . $credentials,
        'Content-Type: application/json'
    ]);
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    if (curl_errno($ch)) {
        curl_close($ch);
        error_log("Failed to get M-PESA access token: " . curl_error($ch));
        return null;
    }
    curl_close($ch);
    $data = json_decode($response, true);
    if ($httpCode !== 200 || !isset($data['access_token'])) {
        error_log("Failed to get M-PESA access token: " . json_encode($data));
        return null;
    }
    return $data['access_token'];
}

// Initiate M-PESA STK Push
function initiateSTKPush($phoneNumber, $amount, $transactionId) {
    $accessToken = getMpesaAccessToken();
    if (!$accessToken) {
        return ['ResponseCode' => '1', 'ResponseDescription' => 'Failed to get access token'];
    }
    $url = (MPESA_ENV == 'live') ?
        'https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest' :
        'https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest';
    $timestamp = date('YmdHis');
    $password = base64_encode(MPESA_SHORTCODE . MPESA_PASSKEY . $timestamp);
    $data = [
        'BusinessShortCode' => MPESA_SHORTCODE,
        'Password' => $password,
        'Timestamp' => $timestamp,
        'TransactionType' => 'CustomerPayBillOnline', // For STK Push
        'Amount' => (int)$amount,
        'PartyA' => $phoneNumber,
        'PartyB' => MPESA_SHORTCODE,
        'PhoneNumber' => $phoneNumber,
        'CallBackURL' => MPESA_CALLBACK_URL . '/mpesa-callback', // Updated to explicit callback
        'AccountReference' => 'SaveApp-' . $transactionId,
        'TransactionDesc' => 'Payment with savings'
    ];
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $accessToken,
        'Content-Type: application/json'
    ]);
    $response = curl_exec($ch);
    if (curl_errno($ch)) {
        curl_close($ch);
        return ['ResponseCode' => '1', 'ResponseDescription' => 'STK Push request failed: ' . curl_error($ch)];
    }
    curl_close($ch);
    $result = json_decode($response, true);
    if (DEBUG) {
        error_log("STK Push Response: " . json_encode($result));
    }
    return $result;
}

// Initiate M-PESA B2B Payment
function initiateB2BPayment($transactionId, $merchantPaybill, $merchantAccount, $amount, $transactionType) {
    $accessToken = getMpesaAccessToken();
    if (!$accessToken) {
        return [
            'status' => 'error',
            'message' => 'Failed to obtain M-PESA access token for B2B payment',
            'reference' => null
        ];
    }

    $url = (MPESA_ENV == 'live') ?
        'https://api.safaricom.co.ke/mpesa/b2b/v1/paymentrequest' :
        'https://sandbox.safaricom.co.ke/mpesa/b2b/v1/paymentrequest';

    $receiverPartyType = ($transactionType === 'buy_goods') ? 4 : 4; // 4 for both Paybill and Till Number in B2B

    $data = [
        'Initiator' => MPESA_B2B_INITIATOR_NAME,
        'SecurityCredential' => MPESA_B2B_SECURITY_CREDENTIAL,
        'CommandID' => ($transactionType === 'buy_goods') ? 'BusinessBuyGoods' : 'BusinessPayBill',
        'SenderIdentifierType' => 4, // Shortcode
        'ReceiverIdentifierType' => $receiverPartyType,
        'Amount' => (int)$amount,
        'PartyA' => MPESA_B2B_SHORTCODE,
        'PartyB' => $merchantPaybill,
        'AccountReference' => $merchantAccount ?: 'SaveApp-' . $transactionId,
        'Remarks' => 'Payment to merchant via SaveApp',
        'QueueTimeOutURL' => MPESA_CALLBACK_URL . '/b2b-timeout',
        'ResultURL' => MPESA_CALLBACK_URL . '/b2b-result/' . $transactionId
    ];

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $accessToken,
        'Content-Type: application/json'
    ]);
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    if (curl_errno($ch)) {
        curl_close($ch);
        error_log("B2B Payment request failed: " . curl_error($ch));
        return [
            'status' => 'error',
            'message' => 'B2B Payment request failed: ' . curl_error($ch),
            'reference' => null
        ];
    }
    curl_close($ch);
    $result = json_decode($response, true);
    if (DEBUG) {
        error_log("B2B Payment Response: " . json_encode($result));
    }

    if ($httpCode >= 400 || !isset($result['ResponseCode']) || $result['ResponseCode'] != '0') {
        $errorMsg = $result['errorMessage'] ?? 'Unknown B2B API error';
        return [
            'status' => 'error',
            'message' => 'B2B Payment initiation failed: ' . $errorMsg,
            'reference' => null
        ];
    }

    return [
        'status' => 'success',
        'message' => 'B2B Payment initiated successfully',
        'reference' => $result['ConversationID'] ?? null,
        'originatorConversationID' => $result['OriginatorConversationID'] ?? null
    ];
}

// Forward payment to merchant
function forwardToMerchant($transactionId, $merchantPaybill, $merchantAccount, $amount) {
    $conn = getDbConnection();
    $stmt = $conn->prepare("SELECT transaction_type FROM transactions WHERE id = ?");
    $stmt->bind_param("i", $transactionId);
    $stmt->execute();
    $result = $stmt->get_result();
    $transaction = $result->fetch_assoc();
    $stmt->close();
    $conn->close();

    if (!$transaction) {
        return [
            'status' => 'error',
            'message' => 'Transaction not found',
            'reference' => null
        ];
    }

    $transactionType = $transaction['transaction_type'] ?? 'pay_bill';
    return initiateB2BPayment($transactionId, $merchantPaybill, $merchantAccount, $amount, $transactionType);
}

// Create required tables on script load
createRequiredTables();

// Routing logic
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(200);
    exit;
}

$method = $_SERVER['REQUEST_METHOD'];
$requestUri = explode('/', trim($_SERVER['REQUEST_URI'], '/'));
$endpoint = isset($requestUri[1]) ? $requestUri[1] : '';

if (DEBUG) {
    error_log("Method: $method, URI: {$_SERVER['REQUEST_URI']}, Endpoint: $endpoint");
    if (isset($_FILES) && !empty($_FILES)) {
        error_log("Files received: " . json_encode(array_keys($_FILES)));
    }
}

// Basic routing
switch ($method . ' ' . $endpoint) {
   case 'POST google-signup':
    $data = json_decode(file_get_contents('php://input'), true) ?: [];
    $email = $data['email'] ?? '';
    $google_id = $data['google_id'] ?? '';
    $name = $data['name'] ?? '';
    $photo_url = $data['photo_url'] ?? '';

    if (empty($email) || empty($google_id) || empty($name)) {
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => 'Email, google_id, and name are required']);
        exit;
    }

    $conn = getDbConnection();

    // Check if user already exists
    $stmt = $conn->prepare("SELECT id FROM users WHERE email = ? OR google_id = ?");
    $stmt->bind_param("ss", $email, $google_id);
    $stmt->execute();
    $result = $stmt->get_result();
    if ($result->num_rows > 0) {
        $user = $result->fetch_assoc();
        http_response_code(200);
        echo json_encode([
            'status' => 'success',
            'message' => 'User already exists',
            'userId' => $user['id'],
            'profile_completed' => false
        ]);
        $stmt->close();
        $conn->close();
        exit;
    }
    $stmt->close();

    // Insert new user with minimal info
    $stmt = $conn->prepare(
        "INSERT INTO users (full_name, email, google_id, password, face_verified) VALUES (?, ?, ?, '', FALSE)"
    );
    $stmt->bind_param("sss", $name, $email, $google_id);

    if ($stmt->execute()) {
        $userId = $conn->insert_id;
        echo json_encode([
            'status' => 'success',
            'message' => 'User created successfully',
            'userId' => $userId,
            'profile_completed' => false
        ]);
    } else {
        http_response_code(500);
        echo json_encode(['status' => 'error', 'message' => 'Failed to create user: ' . $conn->error]);
    }
    $stmt->close();
    $conn->close();
    break;

   case 'POST register':
    if (isset($_SERVER['CONTENT_TYPE']) && strpos($_SERVER['CONTENT_TYPE'], 'multipart/form-data') !== false) {
        $data = $_POST;
        $selfie = isset($_FILES['selfie']) ? $_FILES['selfie'] : null;
        $idDocument = isset($_FILES['id_document']) ? $_FILES['id_document'] : null;
    } else {
        $data = json_decode(file_get_contents('php://input'), true) ?: [];
        $selfie = null;
        $idDocument = null;
    }

    $full_name = $data['full_name'] ?? '';
    $date_of_birth = $data['date_of_birth'] ?? '';
    $email = $data['email'] ?? '';
    $phone_number = $data['phone_number'] ?? '';
    $national_id = $data['national_id'] ?? '';
    $password = $data['password'] ?? '';
    $google_id_token = $data['google_id_token'] ?? '';
    $is_google_sign_in = filter_var($data['is_google_sign_in'] ?? false, FILTER_VALIDATE_BOOLEAN);

    // Google Sign-In path
    if ($is_google_sign_in && !empty($google_id_token)) {
        if (empty($full_name) || empty($email) || empty($phone_number) || empty($national_id)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Full name, email, phone number, and national ID required']);
            exit;
        }

        $tokenResult = verifyFirebaseIdToken($google_id_token);
        if ($tokenResult['status'] !== 'success') {
            http_response_code(401);
            echo json_encode(['status' => 'error', 'message' => $tokenResult['message']]);
            exit;
        }

        if ($tokenResult['email'] !== $email) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Email mismatch with Google account']);
            exit;
        }

        // Upload images if provided (optional for Google)
        $selfiePath = $selfie ? uploadSelfie($selfie) : '';
        $idPath = $idDocument ? uploadIdDocument($idDocument) : '';

        $conn = getDbConnection();

        // Check if user already exists
        $stmt = $conn->prepare("SELECT id FROM users WHERE email = ? OR google_id = ?");
        $stmt->bind_param("ss", $email, $tokenResult['google_id']);
        $stmt->execute();
        $result = $stmt->get_result();
        if ($result->num_rows > 0) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'User already exists']);
            $stmt->close();
            $conn->close();
            exit;
        }
        $stmt->close();

        // Insert user
        $stmt = $conn->prepare(
            "INSERT INTO users (full_name, date_of_birth, email, phone_number, national_id, password, selfie_path, id_document_path, face_verified, google_id)
             VALUES (?, ?, ?, ?, ?, '', ?, ?, TRUE, ?)"
        );
        $googleId = $tokenResult['google_id'];
        $stmt->bind_param("sssssssss", $full_name, $date_of_birth, $email, $phone_number, $national_id, $selfiePath, $idPath, $googleId);

        if ($stmt->execute()) {
            $userId = $conn->insert_id;
            echo json_encode([
                'status' => 'success',
                'message' => 'User registered successfully via Google Sign-In',
                'userId' => $userId,
                'faceVerified' => true
            ]);
        } else {
            http_response_code(500);
            echo json_encode(['status' => 'error', 'message' => 'Registration failed: ' . $conn->error]);
        }
        $stmt->close();
        $conn->close();
        exit;
    }

    // Normal registration (email + password)
    if (empty($full_name) || empty($email) || empty($password) || empty($phone_number)) {
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => 'Name, email, phone, and password are required']);
        exit;
    }

    // Images are now optional
    $selfiePath = $selfie ? uploadSelfie($selfie) : null;
    $idPath = $idDocument ? uploadIdDocument($idDocument) : null;

    $hashedPassword = password_hash($password, PASSWORD_DEFAULT);

    $conn = getDbConnection();

    // Check for duplicate email or phone - only check non-empty values
    error_log("Registration attempt: email=$email, phone=$phone_number");
    
    $stmt = $conn->prepare("SELECT id, email, phone_number FROM users WHERE (email = ? AND email != '') OR (phone_number = ? AND phone_number != '')");
    $stmt->bind_param("ss", $email, $phone_number);
    $stmt->execute();
    $result = $stmt->get_result();
    
    if ($result->num_rows > 0) {
        $existingUser = $result->fetch_assoc();
        error_log("Duplicate found: ID=" . $existingUser['id'] . ", email=" . $existingUser['email'] . ", phone=" . $existingUser['phone_number']);
        
        $conflicts = [];
        if ($existingUser['email'] === $email && !empty($email)) {
            $conflicts[] = 'email';
        }
        if ($existingUser['phone_number'] === $phone_number && !empty($phone_number)) {
            $conflicts[] = 'phone';
        }
        
        http_response_code(400);
        echo json_encode([
            'status' => 'error', 
            'message' => 'Email or phone number already registered',
            'conflicts' => $conflicts
        ]);
        $stmt->close();
        $conn->close();
        exit;
    }
    $stmt->close();
    error_log("No duplicates found, proceeding with registration");

    // Handle placeholders - convert to NULL
    $date_of_birth_value = (empty($date_of_birth) || $date_of_birth === '1990-01-01') ? null : $date_of_birth;
    $national_id_value = (empty($national_id) || $national_id === 'PENDING') ? null : $national_id;

    // Insert new user
    $stmt = $conn->prepare(
        "INSERT INTO users (full_name, date_of_birth, email, phone_number, national_id, password, selfie_path, id_document_path, face_verified)
         VALUES (?, ?, ?, ?, ?, ?, ?, ?, TRUE)"
    );
    $stmt->bind_param("ssssssss", $full_name, $date_of_birth_value, $email, $phone_number, $national_id_value, $hashedPassword, $selfiePath, $idPath);

    if ($stmt->execute()) {
        $userId = $conn->insert_id;
        echo json_encode([
            'status' => 'success',
            'message' => 'User registered and verified successfully.',
            'userId' => $userId
        ]);
    } else {
        http_response_code(500);
        echo json_encode(['status' => 'error', 'message' => 'Registration failed: ' . $conn->error]);
    }

    $stmt->close();
    $conn->close();
    break;

    case 'POST complete-profile':
    if (isset($_SERVER['CONTENT_TYPE']) && strpos($_SERVER['CONTENT_TYPE'], 'multipart/form-data') !== false) {
        $data = $_POST;
        $selfie = isset($_FILES['selfie_image']) ? $_FILES['selfie_image'] : null;
        $idDocument = isset($_FILES['id_document_image']) ? $_FILES['id_document_image'] : null;
    } else {
        $data = json_decode(file_get_contents('php://input'), true) ?: [];
        $selfie = null;
        $idDocument = null;
    }

    $userId = $data['userId'] ?? '';
    $date_of_birth = $data['date_of_birth'] ?? '';
    $national_id = $data['national_id'] ?? '';
    $phone_number = $data['phone_number'] ?? '';

    if (empty($userId)) {
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => 'User ID is required']);
        exit;
    }

    $conn = getDbConnection();

    // Check if user exists
    $stmt = $conn->prepare("SELECT id FROM users WHERE id = ?");
    $stmt->bind_param("i", $userId);
    $stmt->execute();
    $result = $stmt->get_result();
    if ($result->num_rows === 0) {
        http_response_code(404);
        echo json_encode(['status' => 'error', 'message' => 'User not found']);
        $stmt->close();
        $conn->close();
        exit;
    }
    $stmt->close();

    // Images are optional
    $selfiePath = $selfie ? uploadSelfie($selfie) : null;
    $idPath = $idDocument ? uploadIdDocument($idDocument) : null;

    // Build update query dynamically
    $updateFields = [];
    $types = "";
    $params = [];

    if (!empty($date_of_birth)) {
        $updateFields[] = "date_of_birth = ?";
        $types .= "s";
        $params[] = $date_of_birth;
    }
    if (!empty($national_id)) {
        $updateFields[] = "national_id = ?";
        $types .= "s";
        $params[] = $national_id;
    }
    if (!empty($phone_number)) {
        $updateFields[] = "phone_number = ?";
        $types .= "s";
        $params[] = $phone_number;
    }
    if ($selfiePath) {
        $updateFields[] = "selfie_path = ?";
        $types .= "s";
        $params[] = $selfiePath;
    }
    if ($idPath) {
        $updateFields[] = "id_document_path = ?";
        $types .= "s";
        $params[] = $idPath;
    }

    // Always set face_verified to TRUE when completing profile
    $updateFields[] = "face_verified = TRUE";

    if (empty($updateFields)) {
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => 'No fields to update']);
        $conn->close();
        exit;
    }

    $params[] = $userId;
    $types .= "i";

    $query = "UPDATE users SET " . implode(", ", $updateFields) . " WHERE id = ?";
    $stmt = $conn->prepare($query);
    $stmt->bind_param($types, ...$params);

    if ($stmt->execute()) {
        echo json_encode([
            'status' => 'success',
            'message' => 'Profile completed successfully',
            'profile_completed' => true
        ]);
    } else {
        http_response_code(500);
        echo json_encode(['status' => 'error', 'message' => 'Failed to update profile: ' . $conn->error]);
    }

    $stmt->close();
    $conn->close();
    break;

    case 'POST forgot-password':
        $data = json_decode(file_get_contents('php://input'), true) ?: $_POST;
        $email = $data['email'] ?? '';

        if (empty($email)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Email is required']);
            exit;
        }

        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT id, google_id FROM users WHERE email = ?");
        $stmt->bind_param("s", $email);
        $stmt->execute();
        $result = $stmt->get_result();
        $user = $result->fetch_assoc();
        $stmt->close();

        if (!$user) {
            http_response_code(404);
            echo json_encode(['status' => 'error', 'message' => 'User not found']);
            $conn->close();
            exit;
        }

        if (!empty($user['google_id'])) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'This account uses Google Sign-In. Password reset is not applicable.']);
            $conn->close();
            exit;
        }

        // Generate a unique token
        $token = bin2hex(random_bytes(32));
        $expiresAt = date('Y-m-d H:i:s', strtotime('+1 hour')); // Token expires in 1 hour

        // Store the token
        $stmt = $conn->prepare("INSERT INTO password_reset_tokens (user_id, token, expires_at) VALUES (?, ?, ?)");
        $stmt->bind_param("iss", $user['id'], $token, $expiresAt);
        if ($stmt->execute()) {
            $emailResult = sendPasswordResetEmail($email, $token);
            if ($emailResult['status'] === 'success') {
                echo json_encode(['status' => 'success', 'message' => 'Password reset link sent to your email']);
            } else {
                http_response_code(500);
                echo json_encode(['status' => 'error', 'message' => $emailResult['message']]);
            }
        } else {
            http_response_code(500);
            echo json_encode(['status' => 'error', 'message' => 'Failed to generate reset token: ' . $conn->error]);
        }
        $stmt->close();
        $conn->close();
        break;
// ————————————————————————
// POST /goals/create → Create Savings Goal
// ————————————————————————
case 'POST goalscreate':
    $data = json_decode(file_get_contents('php://input'), true) ?: $_POST;

    $user_id        = $data['user_id'] ?? '';
    $title          = trim($data['title'] ?? '');
    $target_amount  = (float)($data['target_amount'] ?? 0);
    $deadline       = $data['deadline'] ?? null; // Format: YYYY-MM-DD
    $description    = trim($data['description'] ?? '');

    // Validation
    if (!$user_id || !$title || $target_amount < 100) {
        http_response_code(400);
        echo json_encode([
            'status' => 'error',
            'message' => 'user_id, title, and target_amount ≥100 required'
        ]);
        exit;
    }

    // Optional: Validate date format
    if ($deadline && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $deadline)) {
        $deadline = null;
    }

    $conn = getDbConnection();

    // Check if user exists
    $stmt = $conn->prepare("SELECT id FROM users WHERE id = ?");
    $stmt->bind_param("i", $user_id);
    $stmt->execute();
    if ($stmt->get_result()->num_rows === 0) {
        $stmt->close();
        $conn->close();
        http_response_code(404);
        echo json_encode(['status' => 'error', 'message' => 'User not found']);
        exit;
    }
    $stmt->close();

    // Insert new goal
    $stmt = $conn->prepare("
        INSERT INTO savings_goals 
        (user_id, title, target_amount, current_amount, deadline, description, status, created_at, updated_at)
        VALUES (?, ?, ?, 0, ?, ?, 'active', NOW(), NOW())
    ");
    $stmt->bind_param("isdss", $user_id, $title, $target_amount, $deadline, $description);

    if ($stmt->execute()) {
        $goal_id = $conn->insert_id;
        $stmt->close();
        $conn->close();

        echo json_encode([
            'status' => 'success',
            'message' => 'Savings goal created!',
            'goal' => [
                'id'             => $goal_id,
                'goal_type'             => $goal_type,
                'user_id'        => (int)$user_id,
                'title'          => $title,
                'target_amount'  => $target_amount,
                'current_amount' => 0.0,
                'deadline'       => $deadline,
                'description'    => $description,
                'status'         => 'active',
                'progress'       => 0.0,
                'created_at'     => date('Y-m-d H:i:s')
            ]
        ]);
    } else {
        $stmt->close();
        $conn->close();
        http_response_code(500);
        echo json_encode(['status' => 'error', 'message' => 'Failed to create goal']);
    }
    break;
    case 'POST goalscontribute':
    $data = json_decode(file_get_contents('php://input'), true);
    $user_id = $data['user_id'] ?? '';
    $goal_id = $data['goal_id'] ?? '';
    $amount = (float)($data['amount'] ?? 0);

    if (!$user_id || !$goal_id || $amount <= 0) {
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => 'Invalid input']);
        exit;
    }

    $conn = getDbConnection();

    // Validate goal belongs to user and is active
    $stmt = $conn->prepare("SELECT id, target_amount, current_amount FROM savings_goals WHERE id = ? AND user_id = ? AND status = 'active'");
    $stmt->bind_param("ii", $goal_id, $user_id);
    $stmt->execute();
    $goal = $stmt->get_result()->fetch_assoc();
    $stmt->close();

    if (!$goal) {
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => 'Goal not found or not active']);
        $conn->close();
        exit;
    }

    // Check if adding this would exceed target
    $new_total = $goal['current_amount'] + $amount;
    if ($new_total > $goal['target_amount']) {
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => 'Amount exceeds remaining goal target']);
        $conn->close();
        exit;
    }

    // Record contribution
    $stmt = $conn->prepare("
        INSERT INTO goal_contributions (user_id, goal_id, amount)
        VALUES (?, ?, ?)
    ");
    $stmt->bind_param("iid", $user_id, $goal_id, $amount);
    if ($stmt->execute()) {
        // Update goal's current_amount
        $stmt2 = $conn->prepare("UPDATE savings_goals SET current_amount = current_amount + ? WHERE id = ?");
        $stmt2->bind_param("di", $amount, $goal_id);
        $stmt2->execute();
        $stmt2->close();

        // If goal is now complete
        if ($new_total >= $goal['target_amount']) {
            $stmt3 = $conn->prepare("UPDATE savings_goals SET status = 'completed', completed_at = NOW() WHERE id = ?");
            $stmt3->bind_param("i", $goal_id);
            $stmt3->execute();
            $stmt3->close();
        }

        echo json_encode([
            'status' => 'success',
            'message' => 'Contribution added to goal',
            'new_progress' => $new_total,
            'target' => (float)$goal['target_amount']
        ]);
    } else {
        http_response_code(500);
        echo json_encode(['status' => 'error', 'message' => 'Failed to save contribution']);
    }
    $stmt->close();
    $conn->close();
    break;
    case 'POST challengescontribute':
    $data = json_decode(file_get_contents('php://input'), true);

    $user_id      = $data['user_id'] ?? '';
    $challenge_id = $data['challenge_id'] ?? '';
    $amount       = (float)($data['amount'] ?? 0);
    $phone        = formatPhoneNumber($data['phone_number'] ?? ''); // Required for STK

    // Validation
    if (!$user_id || !$challenge_id || $amount <= 0 || empty($phone)) {
        http_response_code(400);
        echo json_encode([
            'status'  => 'error',
            'message' => 'user_id, challenge_id, amount, and phone_number are required'
        ]);
        break;
    }

    $conn = getDbConnection();

    // Check if user is enrolled in the challenge
    $stmt = $conn->prepare("
        SELECT id, current_week, total_saved, starting_amount 
        FROM user_challenges 
        WHERE user_id = ? AND challenge_id = ?
    ");
    $stmt->bind_param("is", $user_id, $challenge_id);
    $stmt->execute();
    $result = $stmt->get_result();

    if ($result->num_rows === 0) {
        $stmt->close();
        $conn->close();
        http_response_code(404);
        echo json_encode([
            'status'  => 'error',
            'message' => 'You are not enrolled in this challenge'
        ]);
        break;
    }

    $challenge = $result->fetch_assoc();
    $stmt->close();

    // Generate unique reference
    $ref = 'CHLG_' . strtoupper($challenge_id) . '_' . time() . '_' . $user_id;

    // === TRIGGER STK PUSH via PayHero ===
    $stkResult = payHeroSTKPush(
        $phone,
        $amount,
        $ref,
        'https://apis.nebo.co.ke/apis/challenge-payment-callback.php', // Your callback URL
        'Challenge Contribution'
    );

    if ($stkResult['status'] !== 'success') {
        $conn->close();
        http_response_code(400);
        echo json_encode([
            'status'  => 'error',
            'message' => $stkResult['message'] ?? 'Failed to initiate STK Push'
        ]);
        break;
    }

    // Record pending contribution (will be confirmed in callback)
    $pending_total = $challenge['total_saved'] + $amount;
    $pending_week  = $challenge_id === '52_week' ? $challenge['current_week'] + 1 : $challenge['current_week'];

    $insert_stmt = $conn->prepare("
        INSERT INTO challenge_contributions 
        (user_id, challenge_id, amount, checkout_request_id, reference, status, created_at)
        VALUES (?, ?, ?, ?, ?, 'pending', NOW())
    ");
    $insert_stmt->bind_param("isdss", $user_id, $challenge_id, $amount, $stkResult['checkout_id'], $ref);
    $insert_stmt->execute();
    $contribution_id = $conn->insert_id;
    $insert_stmt->close();

    $conn->close();

    // Return success with checkout ID (Flutter will poll or wait for callback)
    echo json_encode([
        'status'          => 'success',
        'message'         => 'STK Push sent! Please complete payment on your phone.',
        'checkout_id'     => $stkResult['checkout_id'],
        'contribution_id' => $contribution_id,
        'amount'          => $amount,
        'reference'       => $ref,
        'pending_progress'=> [
            'new_total'     => $pending_total,
            'next_week'     => $pending_week
        ]
    ]);
    break;
    case 'POST challengesjoin':
    $data = json_decode(file_get_contents('php://input'), true);
    $user_id = $data['user_id'] ?? '';
    $challenge_id = $data['challenge_id'] ?? '';
    $starting_amount = (int)($data['starting_amount'] ?? 0); // For 52-week only
    $reminder = $data['reminder'] ?? 'Weekly';

    if (!$user_id || !$challenge_id) {
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => 'Missing required fields']);
        break;
    }

    $conn = getDbConnection();

    // Check if already joined
    $stmt = $conn->prepare("SELECT id FROM user_challenges WHERE user_id = ? AND challenge_id = ?");
    $stmt->bind_param("is", $user_id, $challenge_id);
    $stmt->execute();
    $result = $stmt->get_result();
    if ($result->num_rows > 0) {
        $stmt->close();
        $conn->close();
        echo json_encode(['status' => 'success', 'message' => 'Already joined this challenge']);
        break;
    }
    $stmt->close();

    // Insert new challenge participation
    $stmt = $conn->prepare("
        INSERT INTO user_challenges 
        (user_id, challenge_id, starting_amount, reminder_frequency, joined_at, current_week, total_saved)
        VALUES (?, ?, ?, ?, NOW(), 0, 0.00)
    ");
    $stmt->bind_param("isii", $user_id, $challenge_id, $starting_amount, $reminder);

    if ($stmt->execute()) {
        $stmt->close();
        $conn->close();
        echo json_encode([
            'status' => 'success',
            'message' => 'Challenge joined successfully!',
            'data' => [
                'challenge_id' => $challenge_id,
                'starting_amount' => $starting_amount,
                'reminder_frequency' => $reminder
            ]
        ]);
    } else {
        $stmt->close();
        $conn->close();
        http_response_code(500);
        echo json_encode(['status' => 'error', 'message' => 'Failed to join challenge: ' . $stmt->error]);
    }
    break;
    // ————————————————————————
// GET /goals/list/{user_id} → Get All Savings Goals
// ————————————————————————
case 'GET goalslist':
    // Expecting: GET /apis/goals/{userId}  →  $requestUri[2] is the userId
    $userId = $requestUri[2] ?? $_GET['user_id'] ?? '';
    
    if (empty($userId) || !is_numeric($userId)) {
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => 'Valid user_id is required']);
        exit;
    }

    $conn = getDbConnection();

    // Optional: Verify user exists (recommended for security & clean errors)
    $stmt = $conn->prepare("SELECT id FROM users WHERE id = ?");
    $stmt->bind_param("i", $userId);
    $stmt->execute();
    $result = $stmt->get_result();
    if ($result->num_rows === 0) {
        $stmt->close();
        $conn->close();
        http_response_code(404);
        echo json_encode(['status' => 'error', 'message' => 'User not found']);
        exit;
    }
    $stmt->close();

    // Main query: Fetch all active goals for this user
    $stmt = $conn->prepare("
        SELECT 
            id,
            goal_type,
            title,
            target_amount,
            duration_days,
            selected_option,
            deadline,
            current_amount,
            created_at
        FROM savings_goals
        WHERE user_id = ?
        ORDER BY created_at DESC
    ");
    $stmt->bind_param("i", $userId);
    $stmt->execute();
    $result = $stmt->get_result();

    $goals = [];
    while ($row = $result->fetch_assoc()) {
        $goals[] = [
            'id'             => (int)$row['id'],
            'goal_type'      => $row['goal_type'] ?? '',
            'goal_name'      => $row['title'] ?? '',
            'goal_amount'    => (float)$row['target_amount'],
            'duration_days'  => (int)$row['duration_days'],
            'selected_option'=> $row['selected_option'],
            'created_at'     => $row['created_at']
        ];
    }

    $stmt->close();
    $conn->close();

    // Response
    echo json_encode([
        'status'  => 'success',
        'message' => count($goals) > 0 ? 'Goals retrieved successfully' : 'No goals found',
        'data'    => $goals
    ]);
    break;
    case 'POST reset_password':
        $data = json_decode(file_get_contents('php://input'), true) ?: $_POST;
        $token = $data['token'] ?? '';
        $new_password = $data['new_password'] ?? '';

        if (empty($token) || empty($new_password)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Token and new password are required']);
            exit;
        }

        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT user_id, expires_at FROM password_reset_tokens WHERE token = ?");
        $stmt->bind_param("s", $token);
        $stmt->execute();
        $result = $stmt->get_result();
        $tokenData = $result->fetch_assoc();
        $stmt->close();

        if (!$tokenData) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Invalid or expired token']);
            $conn->close();
            exit;
        }

        if (strtotime($tokenData['expires_at']) < time()) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Token has expired']);
            $conn->close();
            exit;
        }

        $userId = $tokenData['user_id'];
        $hashedPassword = password_hash($new_password, PASSWORD_DEFAULT);

        $stmt = $conn->prepare("UPDATE users SET password = ? WHERE id = ?");
        $stmt->bind_param("si", $hashedPassword, $userId);
        if ($stmt->execute()) {
            // Delete the used token
            $stmt = $conn->prepare("DELETE FROM password_reset_tokens WHERE token = ?");
            $stmt->bind_param("s", $token);
            $stmt->execute();
            $stmt->close();
            echo json_encode(['status' => 'success', 'message' => 'Password reset successfully']);
        } else {
            http_response_code(500);
            echo json_encode(['status' => 'error', 'message' => 'Failed to reset password: ' . $conn->error]);
        }
        $stmt->close();
        $conn->close();
        break;
        case 'GET last-payment':
    $userId = $requestUri[2] ?? '';
    if (empty($userId) || !is_numeric($userId)) {
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => 'Valid userId required']);
        exit;
    }
    $conn = getDbConnection();
    $stmt = $conn->prepare(
        "SELECT id, total_amount, merchant_paybill, merchant_account, status, created_at, transaction_type 
         FROM transactions 
         WHERE user_id = ? AND status = 'completed' 
         ORDER BY created_at DESC 
         LIMIT 3"
    );
    $stmt->bind_param("i", $userId);
    $stmt->execute();
    $result = $stmt->get_result();
    $transactions = [];
    while ($row = $result->fetch_assoc()) {
        $transactions[] = [
            'id' => (int)$row['id'],
            'business_name' => $row['merchant_account'] ?: $row['merchant_paybill'],
            'transaction_status' => $row['status'],
            'total_amount' => (float)$row['total_amount'],
            'type' => $row['transaction_type'],
            'created_at' => $row['created_at']
        ];
    }
    if (empty($transactions)) {
        echo json_encode(['status' => 'success', 'message' => 'No completed transactions found', 'data' => []]);
    } else {
        echo json_encode(['status' => 'success', 'data' => $transactions]);
    }
    $stmt->close();
    $conn->close();
    break;
    case 'POST login':
        $data = json_decode(file_get_contents('php://input'), true) ?: $_POST;
        $email = $data['email'] ?? '';
        $password = $data['password'] ?? '';
        $google_id_token = $data['google_id_token'] ?? '';
        $is_google_sign_in = filter_var($data['is_google_sign_in'] ?? false, FILTER_VALIDATE_BOOLEAN);

        if ($is_google_sign_in && !empty($google_id_token)) {
            if (empty($email)) {
                http_response_code(400);
                echo json_encode(['status' => 'error', 'message' => 'Email required']);
                exit;
            }

            $tokenResult = verifyFirebaseIdToken($google_id_token);
            if ($tokenResult['status'] !== 'success') {
                http_response_code(401);
                echo json_encode(['status' => 'error', 'message' => $tokenResult['message']]);
                exit;
            }

            if ($tokenResult['email'] !== $email) {
                http_response_code(400);
                echo json_encode(['status' => 'error', 'message' => 'Email mismatch with Google account']);
                exit;
            }

            $conn = getDbConnection();
            $stmt = $conn->prepare("SELECT id, face_verified FROM users WHERE email = ? OR google_id = ?");
            $googleId = $tokenResult['google_id'];
            $stmt->bind_param("ss", $email, $googleId);
            $stmt->execute();
            $result = $stmt->get_result();
            $user = $result->fetch_assoc();
            $stmt->close();
            $conn->close();

            if (!$user) {
                http_response_code(404);
                echo json_encode(['status' => 'error', 'message' => 'User not found']);
                exit;
            }

            echo json_encode([
                'status' => 'success',
                'message' => 'Login successful via Google Sign-In',
                'userId' => $user['id'],
                'faceVerified' => $user['face_verified']
            ]);
        } else {
            if (empty($email) || empty($password)) {
                http_response_code(400);
                echo json_encode(['status' => 'error', 'message' => 'Email and password required']);
                exit;
            }
            $conn = getDbConnection();
            $stmt = $conn->prepare("SELECT id, password, face_verified FROM users WHERE email = ?");
            $stmt->bind_param("s", $email);
            $stmt->execute();
            $result = $stmt->get_result();
            $user = $result->fetch_assoc();
            if (!$user) {
                http_response_code(401);
                echo json_encode(['status' => 'error', 'message' => 'User not found']);
            } elseif (password_verify($password, $user['password'])) {
                if ($user['face_verified']) {
                    echo json_encode([
                        'status' => 'success',
                        'message' => 'Login successful',
                        'userId' => $user['id'],
                        'faceVerified' => true
                    ]);
                } else {
                    echo json_encode([
                        'status' => 'warning',
                        'message' => 'Login successful but identity not verified',
                        'userId' => $user['id'],
                        'faceVerified' => false
                    ]);
                }
            } else {
                http_response_code(401);
                echo json_encode(['status' => 'error', 'message' => 'Invalid password']);
            }
            $stmt->close();
            $conn->close();
        }
        break;

    case 'GET user-savings':
        $userId = $requestUri[2] ?? $_GET['user_id'] ?? '';
        if (empty($userId) || !is_numeric($userId)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Valid user_id required']);
            exit;
        }

        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT id, full_name, email FROM users WHERE id = ?");
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        $result = $stmt->get_result();
        $user = $result->fetch_assoc();
        $stmt->close();

        if (!$user) {
            http_response_code(404);
            echo json_encode(['status' => 'error', 'message' => 'User not found']);
            $conn->close();
            exit;
        }

        $stmt = $conn->prepare("
            SELECT COALESCE(SUM(savings_amount), 0) as total_savings
            FROM transactions
            WHERE user_id = ?
        ");
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        $result = $stmt->get_result();
        $row = $result->fetch_assoc();
        $stmt->close();
        $conn->close();

        $totalSavings = (float)$row['total_savings'];

        echo json_encode([
            'status' => 'success',
            'message' => $totalSavings > 0 ? 'Savings retrieved successfully' : 'No savings found',
            'data' => [
                'user_id' => (int)$userId,
                'full_name' => $user['full_name'],
                'email' => $user['email'],
                'total_savings' => $totalSavings
            ]
        ]);
        break;
case 'GET p2p-portfolio':
    $userId = $requestUri[2] ?? $_GET['user_id'] ?? '';
    if (empty($userId) || !is_numeric($userId)) {
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => 'Valid user_id required']);
        exit;
    }

    $conn = getDbConnection();

    // Check if user exists
    $stmt = $conn->prepare("SELECT id, full_name, selfie_path FROM users WHERE id = ?");
    $stmt->bind_param("i", $userId);
    $stmt->execute();
    $result = $stmt->get_result();
    $user = $result->fetch_assoc();
    $stmt->close();

    if (!$user) {
        http_response_code(404);
        echo json_encode(['status' => 'error', 'message' => 'User not found']);
        $conn->close();
        exit;
    }

    // Get P2P loans where this user is the lender
    $stmt = $conn->prepare("
        SELECT 
            l.id,
            l.amount,
            l.interest_rate AS interest,
            l.tenure_months,
            l.status,
            l.created_at,
            l.due_date,
            u.full_name AS borrower_name,
            u.selfie_path AS borrower_photo,
            (l.amount * POW(1 + (l.interest_rate/100)/12, l.tenure_months)) AS expected_return,
            CASE 
                WHEN l.interest_rate > 15 THEN 'High'
                WHEN l.interest_rate > 10 THEN 'Medium'
                ELSE 'Low'
            END AS risk_level
        FROM p2p_loans l
        JOIN users u ON l.borrower_id = u.id
        WHERE l.lender_id = ?
          AND l.status IN ('active', 'repaying')
        ORDER BY l.created_at DESC
    ");

    $stmt->bind_param("i", $userId);
    $stmt->execute();
    $result = $stmt->get_result();

    $portfolio = [];
    while ($row = $result->fetch_assoc()) {
        $portfolio[] = [
            'id' => (int)$row['id'],
            'borrower_name' => $row['borrower_name'],
            'borrower_photo' => $row['borrower_photo'] ?: null,
            'amount' => (float)$row['amount'],
            'interest' => (float)$row['interest'],
            'tenure_months' => (int)$row['tenure_months'],
            'status' => ucfirst($row['status']),
            'expected_return' => round((float)$row['expected_return'], 2),
            'risk_level' => $row['risk_level'],
            'due_date' => date('M Y', strtotime($row['due_date'])),
            'created_at' => $row['created_at']
        ];
    }

    $stmt->close();
    $conn->close();

    echo json_encode([
        'status' => 'success',
        'message' => !empty($portfolio) ? 'Portfolio retrieved successfully' : 'No active loans found',
        'data' => $portfolio
    ]);
    break;
    
    case 'GET p2p-lenders':
    $page = max(1, (int)($_GET['page'] ?? 1));
    $limit = 10;
    $offset = ($page - 1) * $limit;

    $conn = getDbConnection();

    // Count total
    $countStmt = $conn->prepare("SELECT COUNT(*) as total FROM lenders");
    $countStmt->execute();
    $total = $countStmt->get_result()->fetch_assoc()['total'];
    $totalPages = ceil($total / $limit);
    $countStmt->close();

    // Fetch paginated
    $stmt = $conn->prepare("
        SELECT 
            id,
            name,
            available,
            interest_rate AS interest,
            tenure_max,
            rating
        FROM lenders
        ORDER BY rating DESC, available DESC
        LIMIT ? OFFSET ?
    ");
    $stmt->bind_param("ii", $limit, $offset);
    $stmt->execute();
    $result = $stmt->get_result();

    $lenders = [];
    while ($row = $result->fetch_assoc()) {
        $lenders[] = [
            'id' => (int)$row['id'],
            'name' => $row['name'],
            'available' => (float)$row['available'],
            'interest' => (float)$row['interest'],
            'tenure_max' => (int)$row['tenure_max'],
            'rating' => (float)$row['rating'],
        ];
    }

    $stmt->close();
    $conn->close();

    echo json_encode([
        'status' => 'success',
        'data' => $lenders,
        'page' => $page,
        'total_pages' => $totalPages,
        'total_lenders' => (int)$total
    ]);
    break;
    
    case 'POST verify-face':
        $userId = $requestUri[2] ?? '';
        if (empty($userId) || !is_numeric($userId)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Valid userId required']);
            exit;
        }
        if (!isset($_FILES['selfie'])) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Selfie required for verification']);
            exit;
        }
        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT id_document_path FROM users WHERE id = ?");
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        $result = $stmt->get_result();
        $user = $result->fetch_assoc();
        if (!$user) {
            http_response_code(404);
            echo json_encode(['status' => 'error', 'message' => 'User not found']);
            $stmt->close();
            $conn->close();
            exit;
        }
        if (empty($user['id_document_path'])) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'User does not have an ID document uploaded']);
            $stmt->close();
            $conn->close();
            exit;
        }
        $selfiePath = uploadSelfie($_FILES['selfie']);
        if (!$selfiePath) {
            http_response_code(500);
            echo json_encode(['status' => 'error', 'message' => 'Failed to upload selfie']);
            $stmt->close();
            $conn->close();
            exit;
        }
        $verifyResult = verifyIdentityWithInforma($selfiePath, $user['id_document_path']);
        $faceVerified = ($verifyResult['status'] === 'success');
        $requestId = isset($verifyResult['request_id']) ? $verifyResult['request_id'] : null;
        $verifyStatus = $faceVerified ? 'success' : 'failed';
        $verifyMessage = $faceVerified ? 'Face verification successful' : 
                        (isset($verifyResult['error']['message']) ? $verifyResult['error']['message'] : 'Face verification failed');
        logVerification($userId, 'face_comparison', $requestId, $verifyStatus, $verifyMessage);
        $stmt = $conn->prepare("UPDATE users SET face_verified = ?, selfie_path = ? WHERE id = ?");
        $stmt->bind_param("isi", $faceVerified, $selfiePath, $userId);
        $stmt->execute();
        if ($faceVerified) {
            echo json_encode([
                'status' => 'success',
                'message' => 'Face verification successful',
                'faceVerified' => true
            ]);
        } else {
            echo json_encode([
                'status' => 'error',
                'message' => 'Face verification failed: ' . $verifyMessage,
                'faceVerified' => false
            ]);
        }
        $stmt->close();
        $conn->close();
        break;

    case 'GET transactions':
        $userId = $requestUri[2] ?? '';
        if (empty($userId)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'userId required']);
            exit;
        }
        $conn = getDbConnection();
        $stmt = $conn->prepare(
            "SELECT id, total_amount, merchant_paybill, merchant_account, status, created_at, transaction_type 
             FROM transactions 
             WHERE user_id = ? 
             ORDER BY created_at DESC"
        );
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        $result = $stmt->get_result();
        $transactions = [];
        while ($row = $result->fetch_assoc()) {
            $transactions[] = [
                'id' => (int)$row['id'],
                'business_name' => $row['merchant_account'] ?: $row['merchant_paybill'],
                'transaction_status' => $row['status'],
                'total_amount' => (float)$row['total_amount'],
                'type' => $row['transaction_type'],
                'created_at' => $row['created_at']
            ];
        }
        if (empty($transactions)) {
            echo json_encode(['status' => 'success', 'message' => 'No transactions found', 'data' => []]);
        } else {
            echo json_encode(['status' => 'success', 'data' => $transactions]);
        }
        $stmt->close();
        $conn->close();
        break;

    case 'POST mpesa-usage':
        $data = json_decode(file_get_contents('php://input'), true) ?: $_POST;
        $user_id = $data['user_id'] ?? '';
        $total_spent = $data['total_spent'] ?? 0;
        $weekly_avg = $data['weekly_avg'] ?? 0;
        $monthly_avg = $data['monthly_avg'] ?? 0;
        $yearly_avg = $data['yearly_avg'] ?? 0;
        $usage_band = $data['usage_band'] ?? '';
        $transaction_count = $data['transaction_count'] ?? 0;
        $last_updated = $data['last_updated'] ?? date('c');
        if (empty($user_id)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'user_id is required']);
            exit;
        }
        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT phone_number FROM users WHERE id = ?");
        $stmt->bind_param("i", $user_id);
        $stmt->execute();
        $result = $stmt->get_result();
        $user = $result->fetch_assoc();
        $stmt->close();
        if (!$user) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Invalid user_id: User does not exist']);
            $conn->close();
            exit;
        }
        $mpesaVerify = callInformaApi('/verify/mpesa-phone-number', 'GET', [
            'phone' => substr($user['phone_number'], -10)
        ]);
        if ($mpesaVerify['status'] !== 'success' || $mpesaVerify['is_mpesa_number'] !== true) {
            $errorMessage = isset($mpesaVerify['error']['message']) ? (is_array($mpesaVerify['error']['message']) ? implode(', ', $mpesaVerify['error']['message']) : $mpesaVerify['error']['message']) : 'Phone number is not an M-Pesa number';
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => "M-Pesa verification failed: $errorMessage"]);
            $conn->close();
            exit;
        }
        $stmt = $conn->prepare(
            "INSERT INTO mpesa_usage (user_id, total_spent, weekly_avg, monthly_avg, yearly_avg, usage_band, transaction_count, last_updated) 
            VALUES (?, ?, ?, ?, ?, ?, ?, ?)
            ON DUPLICATE KEY UPDATE 
            total_spent = ?, weekly_avg = ?, monthly_avg = ?, yearly_avg = ?, usage_band = ?, transaction_count = ?, last_updated = ?"
        );
        $stmt->bind_param(
            "iddddssiddddssi",
            $user_id, $total_spent, $weekly_avg, $monthly_avg, $yearly_avg, $usage_band, $transaction_count, $last_updated,
            $total_spent, $weekly_avg, $monthly_avg, $yearly_avg, $usage_band, $transaction_count, $last_updated
        );
        if ($stmt->execute()) {
            echo json_encode(['status' => 'success', 'message' => 'M-Pesa usage data saved']);
        } else {
            http_response_code(500);
            echo json_encode(['status' => 'error', 'message' => 'Failed to save M-Pesa data: ' . $conn->error]);
        }
        $stmt->close();
        $conn->close();
        break;

    case 'GET user':
        $userId = $requestUri[2] ?? '';
        if (empty($userId)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'userId required']);
            exit;
        }
        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT full_name, selfie_path FROM users WHERE id = ?");
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        $result = $stmt->get_result();
        $user = $result->fetch_assoc();
        if (!$user) {
            http_response_code(404);
            echo json_encode(['status' => 'error', 'message' => 'User not found']);
        } else {
            $selfiePath = $user['selfie_path'] ? str_replace('Uploads/Selfies/', '', $user['selfie_path']) : '';
            echo json_encode([
                'status' => 'success',
                'name' => $user['full_name'],
                'selfie_path' => $selfiePath
            ]);
        }
        $stmt->close();
        $conn->close();
        break;
case 'GET last-payment':
    $userId = $requestUri[2] ?? '';
    if (empty($userId)) {
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => 'userId required']);
        exit;
    }
    $conn = getDbConnection();
    $stmt = $conn->prepare(
        "SELECT id, total_amount, merchant_paybill, merchant_account, status, created_at, transaction_type 
         FROM transactions 
         WHERE user_id = ? AND status = 'completed' 
         ORDER BY created_at DESC 
         LIMIT 3"
    );
    $stmt->bind_param("i", $userId);
    $stmt->execute();
    $result = $stmt->get_result();
    $transactions = [];
    while ($row = $result->fetch_assoc()) {
        $transactions[] = [
            'id' => (int)$row['id'],
            'business_name' => $row['merchant_account'] ?: $row['merchant_paybill'],
            'till_number' => $row['merchant_paybill'],
            'transaction_status' => $row['status'],
            'total_amount' => (float)$row['total_amount'],
            'type' => $row['transaction_type'],
            'created_at' => $row['created_at']
        ];
    }
    echo json_encode([
        'status' => 'success',
        'message' => empty($transactions) ? 'No transactions found' : 'Transactions retrieved successfully',
        'data' => $transactions
    ]);
    $stmt->close();
    $conn->close();
    break;
    case 'GET mpesa-usage':
        $userId = $requestUri[2] ?? '';
        if (empty($userId)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'userId required']);
            exit;
        }
        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT * FROM mpesa_usage WHERE user_id = ?");
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        $result = $stmt->get_result();
        $data = $result->fetch_assoc();
        if (!$data) {
            http_response_code(404);
            echo json_encode(['status' => 'error', 'message' => 'No M-Pesa data found']);
        } else {
            echo json_encode([
                'total_spent' => (float)$data['total_spent'],
                'weekly_avg' => (float)$data['weekly_avg'],
                'monthly_avg' => (float)$data['monthly_avg'],
                'yearly_avg' => (float)$data['yearly_avg'],
                'usage_band' => $data['usage_band'],
                'transaction_count' => (int)$data['transaction_count'],
                'last_updated' => $data['last_updated']
            ]);
        }
        $stmt->close();
        $conn->close();
        break;

    case 'GET roundup-settings':
        $userId = $requestUri[2] ?? '';
        if (empty($userId)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'userId required']);
            exit;
        }
        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT * FROM roundup_settings WHERE user_id = ?");
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        $result = $stmt->get_result();
        $data = $result->fetch_assoc();
        if (!$data) {
            echo json_encode([
                'is_enabled' => false,
                'pay_bill_round_up' => false,
                'buy_goods_round_up' => false,
                'retail_purchases_round_up' => false,
                'rounding_value' => 'KSh 1',
                'max_round_up' => null,
                'monthly_cap' => null
            ]);
        } else {
            echo json_encode($data);
        }
        $stmt->close();
        $conn->close();
        break;

    case 'GET user-details':
        $userId = $requestUri[2] ?? '';
        if (empty($userId)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'userId required']);
            exit;
        }
        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT full_name, email, phone_number, date_of_birth, selfie_path FROM users WHERE id = ?");
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        $result = $stmt->get_result();
        $user = $result->fetch_assoc();
        if (!$user) {
            http_response_code(404);
            echo json_encode(['status' => 'error', 'message' => 'User not found']);
        } else {
            $selfieUrl = $user['selfie_path'] ? 'http://apis.nebo.co.ke/apis/selfie/' . $user['selfie_path'] : null;
            echo json_encode([
                'status' => 'success',
                'data' => [
                    'full_name' => $user['full_name'],
                    'email' => $user['email'],
                    'phone_number' => $user['phone_number'],
                    'date_of_birth' => $user['date_of_birth'],
                    'selfie_path' => $selfieUrl
                ]
            ]);
        }
        $stmt->close();
        $conn->close();
        break;

    case 'POST upload-profile-picture':
        $userId = $requestUri[2] ?? '';
        if (empty($userId)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'userId required']);
            exit;
        }
        if (!isset($_FILES['profile_picture'])) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Profile picture file required']);
            exit;
        }
        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT id FROM users WHERE id = ?");
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        $result = $stmt->get_result();
        if ($result->num_rows === 0) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Invalid user_id: User does not exist']);
            $stmt->close();
            $conn->close();
            exit;
        }
        $stmt->close();
        $selfiePath = uploadSelfie($_FILES['profile_picture']);
        if (!$selfiePath) {
            http_response_code(500);
            echo json_encode(['status' => 'error', 'message' => 'Failed to upload profile picture']);
            $conn->close();
            exit;
        }
        $stmt = $conn->prepare("UPDATE users SET selfie_path = ? WHERE id = ?");
        $stmt->bind_param("si", $selfiePath, $userId);
        if ($stmt->execute()) {
            echo json_encode([
                'status' => 'success',
                'message' => 'Profile picture updated successfully',
                'selfie_path' => 'http://apis.nebo.co.ke/apis/selfie/' . $selfiePath
            ]);
        } else {
            http_response_code(500);
            echo json_encode(['status' => 'error', 'message' => 'Failed to update profile picture: ' . $conn->error]);
        }
        $stmt->close();
        $conn->close();
        break;

    case 'POST roundup-settings':
        $data = json_decode(file_get_contents('php://input'), true) ?: $_POST;
        $user_id = $data['user_id'] ?? '';
        $is_enabled = $data['is_enabled'] ?? 0;
        $pay_bill_round_up = $data['pay_bill_round_up'] ?? 0;
        $buy_goods_round_up = $data['buy_goods_round_up'] ?? 0;
        $retail_purchases_round_up = $data['retail_purchases_round_up'] ?? 0;
        $rounding_value = $data['rounding_value'] ?? 'KSh 1';
        $max_round_up = $data['max_round_up'] ?? null;
        $monthly_cap = $data['monthly_cap'] ?? null;
        if (empty($user_id)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'user_id required']);
            exit;
        }
        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT id FROM users WHERE id = ?");
        $stmt->bind_param("i", $user_id);
        $stmt->execute();
        $result = $stmt->get_result();
        if ($result->num_rows === 0) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Invalid user_id: User does not exist']);
            $stmt->close();
            $conn->close();
            exit;
        }
        $stmt->close();
        $stmt = $conn->prepare(
            "INSERT INTO roundup_settings (user_id, is_enabled, pay_bill_round_up, buy_goods_round_up, retail_purchases_round_up, rounding_value, max_round_up, monthly_cap) 
             VALUES (?, ?, ?, ?, ?, ?, ?, ?)
             ON DUPLICATE KEY UPDATE 
             is_enabled = ?, pay_bill_round_up = ?, buy_goods_round_up = ?, retail_purchases_round_up = ?, rounding_value = ?, max_round_up = ?, monthly_cap = ?"
        );
        $stmt->bind_param(
            "iiiiisddiiiiidd",
            $user_id, $is_enabled, $pay_bill_round_up, $buy_goods_round_up, $retail_purchases_round_up, $rounding_value, $max_round_up, $monthly_cap,
            $is_enabled, $pay_bill_round_up, $buy_goods_round_up, $retail_purchases_round_up, $rounding_value, $max_round_up, $monthly_cap
        );
        if ($stmt->execute()) {
            echo json_encode(['status' => 'success', 'message' => 'Round-Up settings saved']);
        } else {
            http_response_code(500);
            echo json_encode(['status' => 'error', 'message' => 'Failed to save settings: ' . $conn->error]);
        }
        $stmt->close();
        $conn->close();
        break;

    case 'POST buy-goods-payment':
    $data = json_decode(file_get_contents('php://input'), true) ?: $_POST;
    $user_id = $data['user_id'] ?? '';
    $till_number = $data['till_number'] ?? '';
    $account_number = $data['account_number'] ?? '';
    $amount = (float)($data['amount'] ?? 0); // Merchant gets this
    $round_up_savings = (float)($data['round_up_savings'] ?? 0); // User saves this
    $goal_id = !empty($data['goal_id']) ? (int)$data['goal_id'] : null; // ← NEW: Optional goal allocation

    $total_amount = $amount + $round_up_savings;

    if (!$user_id || !$till_number || $total_amount < 1) {
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => 'Invalid request']);
        exit;
    }

    $conn = getDbConnection();

    // Get user phone
    $stmt = $conn->prepare("SELECT phone_number, full_name FROM users WHERE id = ?");
    $stmt->bind_param("i", $user_id);
    $stmt->execute();
    $user = $stmt->get_result()->fetch_assoc();
    $stmt->close();
    if (!$user) {
        http_response_code(404);
        echo json_encode(['status' => 'error', 'message' => 'User not found']);
        $conn->close();
        exit;
    }

    $phone = formatPhoneNumber($user['phone_number']);
    $customer_name = $user['full_name'] ?? 'SaveApp User';

    // Validate goal_id if provided and round_up_savings > 0
    if ($goal_id !== null && $round_up_savings > 0) {
        $stmt = $conn->prepare("SELECT id, target_amount, current_amount FROM savings_goals WHERE id = ? AND user_id = ? AND status = 'active'");
        $stmt->bind_param("ii", $goal_id, $user_id);
        $stmt->execute();
        $goalResult = $stmt->get_result();
        if ($goalResult->num_rows === 0) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Invalid or inactive goal_id']);
            $stmt->close();
            $conn->close();
            exit;
        }
        $goal = $goalResult->fetch_assoc();
        $stmt->close();

        if (($goal['current_amount'] + $round_up_savings) > $goal['target_amount']) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Round-up savings would exceed goal target']);
            $conn->close();
            exit;
        }
    }

    $ref = 'TXN' . time() . $user_id;

    // Record transaction
    $stmt = $conn->prepare("
        INSERT INTO transactions
        (user_id, phone_number, total_amount, savings_amount, merchant_amount, merchant_paybill, merchant_account,
         status, transaction_type, merchant_reference, created_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', 'buy_goods', ?, NOW())
    ");
    $stmt->bind_param("isddssss", $user_id, $phone, $total_amount, $round_up_savings, $amount, $till_number, $account_number, $ref);
    $stmt->execute();
    $transaction_id = $conn->insert_id;
    $stmt->close();

    // Fire STK Push for total amount
    $stkResult = payHeroSTKPush(
        $phone,
        $total_amount,
        $ref,
        'http://apis.nebo.co.ke/apis/payhero-payment-callback.php',
        $customer_name
    );

    if ($stkResult['status'] === 'success') {
        $stmt = $conn->prepare("UPDATE transactions SET checkout_request_id = ? WHERE id = ?");
        $stmt->bind_param("si", $stkResult['checkout_id'], $transaction_id);
        $stmt->execute();
        $stmt->close();

        $response = [
            'status' => 'success',
            'message' => 'STK Push sent! Pay KSh ' . $total_amount,
            'transactionId' => $transaction_id,
            'checkout_id' => $stkResult['checkout_id'],
            'merchant_gets' => $amount,
            'user_saves' => $round_up_savings,
            'allocated_to_goal' => $goal_id
        ];
        $conn->close();
        echo json_encode($response);
    } else {
        $stmt = $conn->prepare("UPDATE transactions SET status = 'failed', error_message = ? WHERE id = ?");
        $stmt->bind_param("si", $stkResult['message'], $transaction_id);
        $stmt->execute();
        $stmt->close();
        $conn->close();
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => $stkResult['message']]);
    }
    break;
    // ————————————————————————
// POST remittance – Global Payout (M-Pesa, Bitcoin, USDT, Bank, etc.)
// ————————————————————————
case 'POST remittance':

    $data = json_decode(file_get_contents('php://input'), true) ?: $_POST;

    $user_id      = $data['user_id'] ?? '';
    $amount       = (float)($data['amount'] ?? 0);          // Amount in KES (or USD if crypto)
    $currency     = $data['currency'] ?? 'KES';            // KES, USD, BTC, etc.
    $provider     = $data['provider'] ?? 'm-pesa';         // m-pesa, bitlipa, airtel, etc.
    $phone        = formatPhoneNumber($data['phone'] ?? ''); // For M-Pesa/Airtel
    $email        = $data['email'] ?? '';
    $first_name   = $data['first_name'] ?? 'User';
    $last_name    = $data['last_name'] ?? '';
    $reference    = $data['reference'] ?? 'REM' . time() . $user_id;
    $description  = $data['description'] ?? 'Remittance from SaveApp';

    if (!$user_id || $amount < 1 || !$phone && !$email) {
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => 'Invalid request']);
        exit;
    }

    // Optional: Check user has enough savings balance first
    $conn = getDbConnection();
    $stmt = $conn->prepare("SELECT COALESCE(SUM(savings_amount),0) AS bal FROM transactions WHERE user_id = ? AND status = 'completed'");
    $stmt->bind_param("i", $user_id);
    $stmt->execute();
    $balance = (float)$stmt->get_result()->fetch_assoc()['bal'];
    $stmt->close();

    if ($balance < $amount) {
        $conn->close();
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => 'Insufficient savings balance']);
        exit;
    }

    // Record pending remittance (negative savings)
    $stmt = $conn->prepare("
        INSERT INTO transactions 
        (user_id, phone_number, total_amount, savings_amount, status, transaction_type, merchant_reference, created_at)
        VALUES (?, ?, ?, ?, 'pending', 'remittance', ?, NOW())
    ");
    $negative = -$amount;
    $stmt->bind_param("isdss", $user_id, $phone, $amount, $negative, $reference);
    $stmt->execute();
    $remit_id = $conn->insert_id;
    $stmt->close();

    // ——— PAYHERO GLOBAL PAYMENT REQUEST ———
    $url = 'https://backend.payhero.co.ke/api/global/payments';

    $payload = [
        "request_type"     => "payment",
        "provider"         => $provider,           // m-pesa, bitlipa, airtel-money, etc.
        "amount"           => (int)$amount,
        "currency"         => strtoupper($currency),
        "country"          => "KE",
        "reference"        => $reference,
        "description"      => $description,
        "customer"         => [
            "first_name"   => $first_name,
            "last_name"    => $last_name,
            "email"        => $email,
            "phone"        => $phone ?: null
        ],
        "provider_config"  => new stdClass(), // leave empty if not needed
        "vendor_config"    => [
            "vendor_id"    => 5000,                    // Your PayHero vendor ID (check dashboard)
            "channel_id"   => PAYHERO_CHANNEL_ID      // Same channel as STK (133 in your case)
        ],
        "callback_url"     => "http://apis.nebo.co.ke/apis/payhero-global-callback.php"
    ];

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_ENCODING       => '',
        CURLOPT_MAXREDIRS      => 10,
        CURLOPT_TIMEOUT        => 0,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_HTTP_VERSION   => CURL_HTTP_VERSION_1_1,
        CURLOPT_CUSTOMREQUEST  => 'POST',
        CURLOPT_POSTFIELDS     => json_encode($payload),
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            'Authorization: Basic ' . PAYHERO_ENCODED_AUTH   // Your working key
        ],
    ]);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    error_log("PayHero Global Payment Response ($httpCode): " . $response);

    $result = json_decode($response, true);

    if ($httpCode === 200 && ($result['status'] ?? '') === 'success') {
        // Mark as completed (or wait for callback if you want 100% confirmation)
        $conn->query("UPDATE transactions SET status = 'completed', merchant_reference = '{$result['data']['transaction_id']}' WHERE id = $remit_id");
        $conn->close();

        echo json_encode([
            'status' => 'success',
            'message' => 'Remittance sent!',
            'remittance_id' => $remit_id,
            'payhero_ref'   => $result['data']['transaction_id'] ?? '',
            'provider'      => $provider
        ]);
    } else {
        $msg = $result['error_message'] ?? 'Global payment failed';
        $conn->query("UPDATE transactions SET status = 'failed', error_message = '$msg' WHERE id = $remit_id");
        $conn->close();
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => $msg]);
    }
    break;
case 'POST deposit':
    $data = json_decode(file_get_contents('php://input'), true) ?: $_POST;
    $user_id = $data['user_id'] ?? '';
    $amount = (float)($data['amount'] ?? 0);
    $phone = formatPhoneNumber($data['phone'] ?? '');
    $goal_id = !empty($data['goal_id']) ? (int)$data['goal_id'] : null; // ← NEW: Optional goal allocation

    if (!$user_id || $amount < 10 || !$phone) {
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => 'user_id, amount ≥10, valid phone required']);
        exit;
    }

    $conn = getDbConnection();

    // Validate user exists
    $stmt = $conn->prepare("SELECT phone_number FROM users WHERE id = ?");
    $stmt->bind_param("i", $user_id);
    $stmt->execute();
    $result = $stmt->get_result();
    if ($result->num_rows === 0) {
        http_response_code(404);
        echo json_encode(['status' => 'error', 'message' => 'User not found']);
        $stmt->close();
        $conn->close();
        exit;
    }
    $stmt->close();

    // Validate goal_id if provided
    if ($goal_id !== null) {
        $stmt = $conn->prepare("SELECT id, target_amount, current_amount FROM savings_goals WHERE id = ? AND user_id = ? AND status = 'active'");
        $stmt->bind_param("ii", $goal_id, $user_id);
        $stmt->execute();
        $goalResult = $stmt->get_result();
        if ($goalResult->num_rows === 0) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Invalid or inactive goal_id']);
            $stmt->close();
            $conn->close();
            exit;
        }
        $goal = $goalResult->fetch_assoc();
        $stmt->close();

        // Optional: Prevent over-saving
        if (($goal['current_amount'] + $amount) > $goal['target_amount']) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'This deposit would exceed the goal target']);
            $conn->close();
            exit;
        }
    }

    // Record pending deposit
    $deposit_ref = 'DEP' . time() . '_' . $user_id;
    $stmt = $conn->prepare("
        INSERT INTO transactions 
        (user_id, phone_number, total_amount, savings_amount, merchant_paybill, status, merchant_reference, transaction_type, created_at)
        VALUES (?, ?, ?, ?, 'PAYHERO_WALLET', 'pending', ?, 'deposit', NOW())
    ");
    $stmt->bind_param("isdds", $user_id, $phone, $amount, $amount, $deposit_ref);
    $stmt->execute();
    $deposit_id = $conn->insert_id;
    $stmt->close();

    // Initiate STK Push
    $stkResult = payHeroSTKPush($phone, $amount, $deposit_ref, 'https://apis.nebo.co.ke/apis/payhero-deposit-callback.php');

    if ($stkResult['status'] === 'success') {
        $stmt = $conn->prepare("UPDATE transactions SET checkout_request_id = ? WHERE id = ?");
        $stmt->bind_param("si", $stkResult['checkout_id'], $deposit_id);
        $stmt->execute();
        $stmt->close();

        // If goal_id was provided, pre-allocate on success (will be confirmed in callback if needed)
        // Or just store it temporarily — here we just return it for frontend tracking
        $response = [
            'status' => 'success',
            'message' => 'Deposit STK sent via PayHero!',
            'deposit_id' => $deposit_id,
            'checkout_id' => $stkResult['checkout_id'],
            'allocated_to_goal' => $goal_id
        ];
        $conn->close();
        echo json_encode($response);
    } else {
        $stmt = $conn->prepare("UPDATE transactions SET status = 'failed', error_message = ? WHERE id = ?");
        $stmt->bind_param("si", $stkResult['message'], $deposit_id);
        $stmt->execute();
        $stmt->close();
        $conn->close();
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => $stkResult['message']]);
    }
    break;
    case 'POST withdraw':
    $data = json_decode(file_get_contents('php://input'), true) ?: $_POST;
    $user_id = $data['user_id'] ?? '';
    $amount = (float)($data['amount'] ?? 0);
    $phone = formatPhoneNumber($data['phone'] ?? '');

    if (!$user_id || $amount < 50 || !$phone) {
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => 'user_id, amount ≥50, valid phone required']);
        exit;
    }

    $conn = getDbConnection();
    $stmt = $conn->prepare("SELECT COALESCE(SUM(savings_amount), 0) AS balance FROM transactions WHERE user_id = ? AND status = 'completed'");
    $stmt->bind_param("i", $user_id);
    $stmt->execute();
    $balance = (float)$stmt->get_result()->fetch_assoc()['balance'];
    $stmt->close();

    if ($balance < $amount) {
        $conn->close();
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => 'Insufficient balance: ' . $balance]);
        exit;
    }

    $withdraw_ref = 'WD' . time() . '_' . $user_id;
    // Record pending withdrawal (negative savings)
    $stmt = $conn->prepare(
        "INSERT INTO transactions (user_id, phone_number, total_amount, savings_amount, merchant_paybill, status, merchant_reference, transaction_type, created_at)
         VALUES (?, ?, ?, ?, 'PAYHERO_WALLET', 'pending', ?, 'withdraw', NOW())"
    );
    $negative_savings = -$amount;
    $stmt->bind_param("isdds", $user_id, $phone, $amount, $negative_savings, $withdraw_ref);
    $stmt->execute();
    $withdraw_id = $conn->insert_id;
    $stmt->close();

    // B2C via PayHero
    $b2cResult = payHeroB2C($phone, $amount, $withdraw_ref, 'SaveApp Withdrawal');
    if ($b2cResult['status'] === 'success') {
        $stmt = $conn->prepare("UPDATE transactions SET status = 'completed', merchant_reference = ? WHERE id = ?");
        $stmt->bind_param("si", $b2cResult['ref'], $withdraw_id);
        $stmt->execute();
        $stmt->close();
        $conn->close();
        echo json_encode([
            'status' => 'success',
            'message' => 'Withdrawal sent! KSh ' . $amount . ' to ' . $phone,
            'withdraw_id' => $withdraw_id,
            'ref' => $b2cResult['ref'],
            'new_balance' => $balance - $amount
        ]);
    } else {
        $stmt = $conn->prepare("UPDATE transactions SET status = 'failed', error_message = ? WHERE id = ?");
        $stmt->bind_param("si", $b2cResult['message'], $withdraw_id);
        $stmt->execute();
        $stmt->close();
        $conn->close();
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => $b2cResult['message']]);
    }
    break;
    case 'POST process-paybill-payment':
        $data = json_decode(file_get_contents('php://input'), true) ?: $_POST;
        $user_id = $data['user_id'] ?? '';
        $phone_number = $data['phone_number'] ?? '';
        $amount = $data['amount'] ?? 0;
        $merchant_paybill = $data['merchant_paybill'] ?? '';
        $merchant_account = $data['merchant_account'] ?? '';
        $transaction_type = 'pay_bill';

        if (empty($user_id) || empty($phone_number) || empty($amount) || empty($merchant_paybill)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Required fields missing']);
            exit;
        }
        $phone_number = formatPhoneNumber($phone_number);
        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT * FROM roundup_settings WHERE user_id = ?");
        $stmt->bind_param("i", $user_id);
        $stmt->execute();
        $result = $stmt->get_result();
        $roundupSettings = $result->fetch_assoc();
        $stmt->close();
        $serviceChargeRate = 0.01;
        $savingsRate = 0;
        if ($roundupSettings && $roundupSettings['is_enabled']) {
            if ($roundupSettings['pay_bill_round_up']) {
                $roundingValue = parseRoundingValue($roundupSettings['rounding_value']);
                $savingsAmount = calculateRoundUpAmount($amount, $roundingValue);
                if ($roundupSettings['max_round_up'] && $savingsAmount > $roundupSettings['max_round_up']) {
                    $savingsAmount = $roundupSettings['max_round_up'];
                }
                $savingsRate = $savingsAmount / $amount;
            }
        }
        $serviceCharge = $amount * $serviceChargeRate;
        $savingsAmount = $amount * $savingsRate;
        $merchantAmount = $amount - $serviceCharge - $savingsAmount;
        $stmt = $conn->prepare(
            "INSERT INTO transactions (user_id, phone_number, total_amount, service_charge, savings_amount, merchant_amount, merchant_paybill, merchant_account, status, transaction_type) 
             VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?)"
        );
        $stmt->bind_param("isddddsss", $user_id, $phone_number, $amount, $serviceCharge, $savingsAmount, $merchantAmount, $merchant_paybill, $merchant_account, $transaction_type);
        if (!$stmt->execute()) {
            http_response_code(500);
            echo json_encode(['status' => 'error', 'message' => 'Failed to record transaction: ' . $conn->error]);
            $stmt->close();
            $conn->close();
            exit;
        }
        $transactionId = $conn->insert_id;
        $stkPushResponse = initiateSTKPush($phone_number, $amount, $transactionId);
        if (isset($stkPushResponse['ResponseCode']) && $stkPushResponse['ResponseCode'] == '0') {
            $checkoutRequestId = $stkPushResponse['CheckoutRequestID'];
            $stmt->prepare("UPDATE transactions SET checkout_request_id = ? WHERE id = ?");
            $stmt->bind_param("si", $checkoutRequestId, $transactionId);
            $stmt->execute();
            $stmt->close();
            echo json_encode([
                'status' => 'success',
                'message' => 'Payment initiated successfully',
                'transactionId' => $transactionId,
                'checkoutRequestId' => $checkoutRequestId
            ]);
        } else {
            $stmt = $conn->prepare("UPDATE transactions SET status = 'failed', error_message = ? WHERE id = ?");
            $errorMsg = $stkPushResponse['ResponseDescription'] ?? 'STK Push failed';
            $stmt->bind_param("si", $errorMsg, $transactionId);
            $stmt->execute();
            $stmt->close();
            http_response_code(400);
            echo json_encode([
                'status' => 'error',
                'message' => 'Failed to initiate payment: ' . $errorMsg
            ]);
        }
        $conn->close();
        break;

    case 'POST favourites':
        $data = json_decode(file_get_contents('php://input'), true) ?: $_POST;
        $user_id = $data['user_id'] ?? '';
        $name = $data['name'] ?? '';
        $till_number = $data['till_number'] ?? '';
        $account_number = $data['account_number'] ?? '';
        $type = $data['type'] ?? '';
        if (empty($user_id) || empty($name) || empty($till_number) || empty($type)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'user_id, name, till_number, and type are required']);
            exit;
        }
        if (!in_array($type, ['buy_goods', 'pay_bill'])) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Invalid type: must be buy_goods or pay_bill']);
            exit;
        }
        if ($type === 'pay_bill' && empty($account_number)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'account_number required for Pay Bill']);
            exit;
        }
        if (!preg_match('/^\d{6}$/', $till_number)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Till number must be 6 digits']);
            exit;
        }
        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT id FROM users WHERE id = ?");
        $stmt->bind_param("i", $user_id);
        $stmt->execute();
        $result = $stmt->get_result();
        if ($result->num_rows === 0) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Invalid user_id: User does not exist']);
            $stmt->close();
            $conn->close();
            exit;
        }
        $stmt->close();
        $stmt = $conn->prepare(
            "INSERT INTO favorites (user_id, name, till_number, account_number, type) VALUES (?, ?, ?, ?, ?)"
        );
        $stmt->bind_param("issss", $user_id, $name, $till_number, $account_number, $type);
        if ($stmt->execute()) {
            $favorite_id = $conn->insert_id;
            echo json_encode([
                'status' => 'success',
                'message' => 'Favorite saved successfully',
                'data' => [
                    'id' => $favorite_id,
                    'user_id' => $user_id,
                    'name' => $name,
                    'till_number' => $till_number,
                    'account_number' => $account_number,
                    'type' => $type
                ]
            ]);
        } else {
            $error = $conn->error;
            if (strpos($error, 'Duplicate entry') !== false) {
                http_response_code(409);
                echo json_encode(['status' => 'error', 'message' => 'This till/paybill is already saved as a favorite']);
            } else {
                http_response_code(500);
                echo json_encode(['status' => 'error', 'message' => 'Failed to save favorite: ' . $error]);
            }
        }
        $stmt->close();
        $conn->close();
        break;

    case 'GET favourites':
        $userId = $requestUri[2] ?? '';
        if (empty($userId) || !is_numeric($userId)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Valid userId required']);
            exit;
        }
        $conn = getDbConnection();
        $stmt = $conn->prepare(
            "SELECT id, name, till_number, account_number, type, created_at 
             FROM favorites 
             WHERE user_id = ? 
             ORDER BY created_at DESC"
        );
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        $result = $stmt->get_result();
        $favorites = [];
        while ($row = $result->fetch_assoc()) {
            $favorites[] = [
                'id' => (int)$row['id'],
                'name' => $row['name'],
                'till_number' => $row['till_number'],
                'account_number' => $row['account_number'],
                'type' => $row['type'],
                'created_at' => $row['created_at']
            ];
        }
        echo json_encode([
            'status' => 'success',
            'message' => empty($favorites) ? 'No favorites found' : 'Favorites retrieved successfully',
            'data' => $favorites
        ]);
        $stmt->close();
        $conn->close();
        break;

    case 'PATCH favourites':
        $favoriteId = $requestUri[2] ?? '';
        if (empty($favoriteId) || !is_numeric($favoriteId)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Valid favorite id required']);
            exit;
        }
        $data = json_decode(file_get_contents('php://input'), true) ?: $_POST;
        $name = $data['name'] ?? null;
        $till_number = $data['till_number'] ?? null;
        $account_number = $data['account_number'] ?? null;
        $type = $data['type'] ?? null;
        if (!$name && !$till_number && !$account_number && !$type) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'At least one field (name, till_number, account_number, type) must be provided']);
            exit;
        }
        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT user_id, type FROM favorites WHERE id = ?");
        $stmt->bind_param("i", $favoriteId);
        $stmt->execute();
        $result = $stmt->get_result();
        if ($result->num_rows === 0) {
            http_response_code(404);
            echo json_encode(['status' => 'error', 'message' => 'Favorite not found']);
            $stmt->close();
            $conn->close();
            exit;
        }
        $favorite = $result->fetch_assoc();
        $stmt->close();
        if ($type && !in_array($type, ['buy_goods', 'pay_bill'])) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Invalid type: must be buy_goods or pay_bill']);
            $conn->close();
            exit;
        }
        if ($till_number && !preg_match('/^\d{6}$/', $till_number)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Till number must be 6 digits']);
            $conn->close();
            exit;
        }
        if (($type === 'pay_bill' || $favorite['type'] === 'pay_bill') && $account_number === '') {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'account_number required for Pay Bill']);
            $conn->close();
            exit;
        }
        $updateFields = [];
        $params = [];
        $types = '';

        if ($name !== null) {
            $updateFields[] = 'name = ?';
            $params[] = $name;
            $types .= 's';
        }
        if ($till_number !== null) {
            $updateFields[] = 'till_number = ?';
            $params[] = $till_number;
            $types .= 's';
        }
        if ($account_number !== null) {
            $updateFields[] = 'account_number = ?';
            $params[] = $account_number;
            $types .= 's';
        }
        if ($type !== null) {
            $updateFields[] = 'type = ?';
                        $params[] = $type;
            $types .= 's';
        }
        $params[] = $favoriteId;
        $types .= 'i';

        $query = "UPDATE favorites SET " . implode(', ', $updateFields) . " WHERE id = ?";
        $stmt = $conn->prepare($query);
        $stmt->bind_param($types, ...$params);

        if ($stmt->execute()) {
            echo json_encode(['status' => 'success', 'message' => 'Favorite updated successfully']);
        } else {
            http_response_code(500);
            echo json_encode(['status' => 'error', 'message' => 'Failed to update favorite: ' . $conn->error]);
        }
        $stmt->close();
        $conn->close();
        break;

    case 'DELETE favourites':
        $favoriteId = $requestUri[2] ?? '';
        if (empty($favoriteId) || !is_numeric($favoriteId)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Valid favorite id required']);
            exit;
        }
        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT id FROM favorites WHERE id = ?");
        $stmt->bind_param("i", $favoriteId);
        $stmt->execute();
        $result = $stmt->get_result();
        if ($result->num_rows === 0) {
            http_response_code(404);
            echo json_encode(['status' => 'error', 'message' => 'Favorite not found']);
            $stmt->close();
            $conn->close();
            exit;
        }
        $stmt->close();
        $stmt = $conn->prepare("DELETE FROM favorites WHERE id = ?");
        $stmt->bind_param("i", $favoriteId);
        if ($stmt->execute()) {
            echo json_encode(['status' => 'success', 'message' => 'Favorite deleted successfully']);
        } else {
            http_response_code(500);
            echo json_encode(['status' => 'error', 'message' => 'Failed to delete favorite: ' . $conn->error]);
        }
        $stmt->close();
        $conn->close();
        break;

    case 'POST mpesa-callback':
        $callbackData = json_decode(file_get_contents('php://input'), true);
        if (DEBUG) {
            error_log("M-PESA Callback Data: " . json_encode($callbackData));
        }
        if (!isset($callbackData['Body']['stkCallback']['ResultCode'])) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Invalid callback data']);
            exit;
        }
        $resultCode = $callbackData['Body']['stkCallback']['ResultCode'];
        $resultDesc = $callbackData['Body']['stkCallback']['ResultDesc'] ?? 'No description provided';
        $checkoutRequestId = $callbackData['Body']['stkCallback']['CheckoutRequestID'] ?? '';
        $mpesaReceipt = '';
        $transactionDate = '';
        if (isset($callbackData['Body']['stkCallback']['CallbackMetadata']['Item'])) {
            foreach ($callbackData['Body']['stkCallback']['CallbackMetadata']['Item'] as $item) {
                if ($item['Name'] === 'MpesaReceiptNumber') {
                    $mpesaReceipt = $item['Value'];
                }
                if ($item['Name'] === 'TransactionDate') {
                    $transactionDate = $item['Value'];
                }
            }
        }
        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT id, user_id, merchant_paybill, merchant_account, merchant_amount, transaction_type FROM transactions WHERE checkout_request_id = ?");
        $stmt->bind_param("s", $checkoutRequestId);
        $stmt->execute();
        $result = $stmt->get_result();
        $transaction = $result->fetch_assoc();
        $stmt->close();
        if (!$transaction) {
            http_response_code(404);
            echo json_encode(['status' => 'error', 'message' => 'Transaction not found for CheckoutRequestID']);
            $conn->close();
            exit;
        }
        $transactionId = $transaction['id'];
        $merchantPaybill = $transaction['merchant_paybill'];
        $merchantAccount = $transaction['merchant_account'];
        $merchantAmount = $transaction['merchant_amount'];
        $transactionType = $transaction['transaction_type'];
        if ($resultCode == '0') {
            $b2bResult = forwardToMerchant($transactionId, $merchantPaybill, $merchantAccount, $merchantAmount);
            if ($b2bResult['status'] === 'success') {
                $stmt = $conn->prepare(
                    "UPDATE transactions SET status = 'completed', mpesa_receipt = ?, validated_at = ?, merchant_reference = ? WHERE id = ?"
                );
                $stmt->bind_param("sssi", $mpesaReceipt, $transactionDate, $b2bResult['reference'], $transactionId);
                $stmt->execute();
                $stmt->close();
                echo json_encode(['status' => 'success', 'message' => 'Payment processed successfully']);
            } else {
                $stmt = $conn->prepare("UPDATE transactions SET status = 'failed', error_message = ? WHERE id = ?");
                $errorMsg = $b2bResult['message'];
                $stmt->bind_param("si", $errorMsg, $transactionId);
                $stmt->execute();
                $stmt->close();
                http_response_code(500);
                echo json_encode(['status' => 'error', 'message' => 'Failed to forward payment to merchant: ' . $errorMsg]);
            }
        } else {
            $stmt = $conn->prepare("UPDATE transactions SET status = 'failed', error_message = ? WHERE id = ?");
            $stmt->bind_param("si", $resultDesc, $transactionId);
            $stmt->execute();
            $stmt->close();
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Payment failed: ' . $resultDesc]);
        }
        $conn->close();
        break;

    case 'POST b2b-result':
        $transactionId = $requestUri[2] ?? '';
        if (empty($transactionId)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Transaction ID required']);
            exit;
        }
        $callbackData = json_decode(file_get_contents('php://input'), true);
        if (DEBUG) {
            error_log("B2B Callback Data for Transaction $transactionId: " . json_encode($callbackData));
        }
        if (!isset($callbackData['Result']['ResultCode'])) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Invalid B2B callback data']);
            exit;
        }
        $resultCode = $callbackData['Result']['ResultCode'];
        $resultDesc = $callbackData['Result']['ResultDesc'] ?? 'No description provided';
        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT id FROM transactions WHERE id = ?");
        $stmt->bind_param("i", $transactionId);
        $stmt->execute();
        $result = $stmt->get_result();
        if ($result->num_rows === 0) {
            http_response_code(404);
            echo json_encode(['status' => 'error', 'message' => 'Transaction not found']);
            $stmt->close();
            $conn->close();
            exit;
        }
        $stmt->close();
        if ($resultCode == '0') {
            $stmt = $conn->prepare("UPDATE transactions SET merchant_status = 'completed' WHERE id = ?");
            $stmt->bind_param("i", $transactionId);
            $stmt->execute();
            $stmt->close();
            echo json_encode(['status' => 'success', 'message' => 'B2B payment processed successfully']);
        } else {
            $stmt = $conn->prepare("UPDATE transactions SET merchant_status = 'failed', merchant_error = ? WHERE id = ?");
            $stmt->bind_param("si", $resultDesc, $transactionId);
            $stmt->execute();
            $stmt->close();
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'B2B payment failed: ' . $resultDesc]);
        }
        $conn->close();
        break;

    case 'POST b2b-timeout':
        $callbackData = json_decode(file_get_contents('php://input'), true);
        if (DEBUG) {
            error_log("B2B Timeout Callback Data: " . json_encode($callbackData));
        }
        if (!isset($callbackData['Result']['ResultCode'])) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Invalid B2B timeout callback data']);
            exit;
        }
        $resultDesc = $callbackData['Result']['ResultDesc'] ?? 'No description provided';
        $transactionId = null;
        if (isset($callbackData['Result']['TransactionID'])) {
            $conn = getDbConnection();
            $stmt = $conn->prepare("SELECT id FROM transactions WHERE merchant_reference = ?");
            $stmt->bind_param("s", $callbackData['Result']['TransactionID']);
            $stmt->execute();
            $result = $stmt->get_result();
            $transaction = $result->fetch_assoc();
            $stmt->close();
            if ($transaction) {
                $transactionId = $transaction['id'];
                $stmt = $conn->prepare("UPDATE transactions SET merchant_status = 'failed', merchant_error = ? WHERE id = ?");
                $stmt->bind_param("si", $resultDesc, $transactionId);
                $stmt->execute();
                $stmt->close();
            }
            $conn->close();
        }
        echo json_encode(['status' => 'success', 'message' => 'B2B timeout processed']);
        break;
         case 'POST apply-loan':
        $data = json_decode(file_get_contents('php://input'), true) ?: $_POST;
        $user_id = $data['user_id'] ?? '';
        $amount = (float)($data['amount_requested'] ?? 0);
        $tenure = (int)($data['tenure_months'] ?? 0);

        if (empty($user_id)||$amount<=0||$tenure<=0) {
            http_response_code(400);
            echo json_encode(['status'=>'error','message'=>'Invalid input']);
            break;
        }

        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT COALESCE(SUM(savings_amount),0) AS total FROM transactions WHERE user_id=? AND status='completed'");
        $stmt->bind_param("i",$user_id);
        $stmt->execute();
        $totalSavings = (float)$stmt->get_result()->fetch_assoc()['total'];
        $stmt->close();

        if ($amount > $totalSavings*2) {
            http_response_code(400);
            echo json_encode(['status'=>'error','message'=>'Max 2× savings']);
            $conn->close();
            break;
        }

        $interest = 12.00;
        $stmt = $conn->prepare("INSERT INTO loans (user_id,amount_requested,interest_rate,tenure_months) VALUES (?,?,?,?)");
        $stmt->bind_param("iddi",$user_id,$amount,$interest,$tenure);
        if ($stmt->execute()) {
            echo json_encode(['status'=>'success','loan_id'=>$conn->insert_id]);
        } else {
            http_response_code(500);
            echo json_encode(['status'=>'error','message'=>'DB error']);
        }
        $stmt->close(); $conn->close();
        break;

 case 'POST loan-repayment':
        $data = json_decode(file_get_contents('php://input'), true) ?: $_POST;
        $loan_id = $data['loan_id'] ?? '';
        $amount = (float)($data['amount'] ?? 0);
        $phone = formatPhoneNumber($data['phone_number'] ?? '');

        if (empty($loan_id)||$amount<=0||empty($phone)) {
            http_response_code(400);
            echo json_encode(['status'=>'error','message'=>'Invalid input']);
            break;
        }

        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT status,amount_approved FROM loans WHERE id=?");
        $stmt->bind_param("i",$loan_id);
        $stmt->execute();
        $loan = $stmt->get_result()->fetch_assoc();
        $stmt->close();
        if (!$loan || $loan['status']!=='disbursed') {
            http_response_code(400);
            echo json_encode(['status'=>'error','message'=>'Loan not disbursed']);
            $conn->close();
            break;
        }

        $stmt = $conn->prepare("INSERT INTO loan_repayments (loan_id,amount) VALUES (?,?)");
        $stmt->bind_param("id",$loan_id,$amount);
        $stmt->execute();
        $repayId = $conn->insert_id;
        $stmt->close();

        $stk = initiateSTKPush($phone,$amount,"REPAY-$repayId");
        if ($stk['ResponseCode']??'' === '0') {
            $chk = $stk['CheckoutRequestID'];
            $stmt = $conn->prepare("UPDATE loan_repayments SET checkout_request_id=? WHERE id=?");
            $stmt->bind_param("si",$chk,$repayId);
            $stmt->execute();
            $stmt->close();
            echo json_encode(['status'=>'success','repayment_id'=>$repayId,'checkout'=>$chk]);
        } else {
            http_response_code(400);
            echo json_encode(['status'=>'error','message'=>$stk['ResponseDescription']??'STK failed']);
        }
        $conn->close();
        break;
case 'POST bank-loan-request':
    $data = json_decode(file_get_contents('php://input'), true);
    $user_id = $data['user_id'] ?? '';
    $amount = (float)($data['amount'] ?? 0);
    $tenure = (int)($data['tenure_months'] ?? 0);
    $loan_type = $data['loan_type'] ?? '';

    if (empty($user_id) || $amount <= 0 || $tenure <= 0 || empty($loan_type)) {
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => 'Missing required fields']);
        break;
    }

    // Optional: Add more validation (e.g., user exists, amount <= 2x savings, etc.)

    $stmt = $conn->prepare("
        INSERT INTO bank_loans 
        (user_id, amount_requested, tenure_months, loan_type, status, created_at)
        VALUES (?, ?, ?, ?, 'pending', NOW())
    ");
    $stmt->bind_param("sdis", $user_id, $amount, $tenure, $loan_type);  // FIXED HERE

    if ($stmt->execute()) {
        echo json_encode([
            'status' => 'success',
            'loan_id' => $conn->insert_id
        ]);
    } else {
        error_log("Bank loan insert error: " . $stmt->error);  // Add logging!
        http_response_code(500);
        echo json_encode(['status' => 'error', 'message' => 'Failed to submit']);
    }
    $stmt->close();
    break;

    case 'GET loan-status':
        $loanId = $resource;
        if (!is_numeric($loanId)) {
            http_response_code(400);
            echo json_encode(['status'=>'error','message'=>'Valid loanId']);
            break;
        }
        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT status,amount_ status,amount_approved FROM loans WHERE id=?");
        $stmt->bind_param("i",$loanId);
        $stmt->execute();
        $loan = $stmt->get_result()->fetch_assoc();
        $stmt->close(); $conn->close();
        if (!$loan) {
            http_response_code(404);
            echo json_encode(['status'=>'error','message'=>'Not found']);
            break;
        }
        $loan['amount_approved'] = $loan['amount_approved'] ? (float)$loan['amount_approved'] : null;
        echo json_encode(['status'=>'success','data'=>$loan]);
        break;

    case 'GET user-loans':
        $userId = $resource;
        if (!is_numeric($userId)) {
            http_response_code(400);
            echo json_encode(['status'=>'error','message'=>'Valid userId']);
            break;
        }
        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT * FROM loans WHERE user_id=? ORDER BY applied_at DESC");
        $stmt->bind_param("i",$userId);
        $stmt->execute();
        $res = $stmt->get_result();
        $loans = [];
        while ($r = $res->fetch_assoc()) {
            $r['amount_requested'] = (float)$r['amount_requested'];
            $r['amount_approved'] = $r['amount_approved'] ? (float)$r['amount_approved'] : null;
            $loans[] = $r;
        }
        echo json_encode(['status'=>'success','data'=>$loans]);
        $stmt->close(); $conn->close();
        break;
  case 'GET loan-details':
        $loanId = $resource;
        if (!is_numeric($loanId)) {
            http_response_code(400);
            echo json_encode(['status'=>'error','message'=>'Valid loanId']);
            break;
        }
        $conn = getDbConnection();
        $stmt = $conn->prepare("SELECT l.*, u.full_name, u.phone_number FROM loans l JOIN users u ON u.id=l.user_id WHERE l.id=?");
        $stmt->bind_param("i",$loanId);
        $stmt->execute();
        $loan = $stmt->get_result()->fetch_assoc();
        if (!$loan) {
            http_response_code(404);
            echo json_encode(['status'=>'error','message'=>'Not found']);
        } else {
            $loan['amount_requested'] = (float)$loan['amount_requested'];
            $loan['amount_approved'] = $loan['amount_approved'] ? (float)$loan['amount_approved'] : null;
            echo json_encode(['status'=>'success','data'=>$loan]);
        }
        $stmt->close(); $conn->close();
        break;
/* ==================== P2P: MANUAL LEND ==================== */
    case 'POST manual-lend':
        $data = json_decode(file_get_contents('php://input'), true) ?: $_POST;
        $lender_id = $data['lender_id'] ?? '';
        $loan_request_id = $data['loan_request_id'] ?? '';
        $amount = (float)($data['amount'] ?? 0);

        if (empty($lender_id) || empty($loan_request_id) || $amount <= 0) {
            http_response_code(400);
            echo json_encode(['status'=>'error','message'=>'lender_id, loan_request_id, amount required']);
            break;
        }

        $conn = getDbConnection();

        $stmt = $conn->prepare("SELECT id FROM users WHERE id=?");
        $stmt->bind_param("i",$lender_id); $stmt->execute();
        if ($stmt->get_result()->num_rows === 0) {
            echo json_encode(['status'=>'error','message'=>'Lender not found']); $stmt->close(); $conn->close(); break;
        }
        $stmt->close();

        $stmt = $conn->prepare("SELECT id, user_id, amount_requested, status FROM loans WHERE id=? AND status='pending'");
        $stmt->bind_param("i",$loan_request_id); $stmt->execute();
        $loan = $stmt->get_result()->fetch_assoc();
        if (!$loan) {
            echo json_encode(['status'=>'error','message'=>'Loan not pending']); $stmt->close(); $conn->close(); break;
        }
        $stmt->close();

        if ($amount > $loan['amount_requested']) {
            echo json_encode(['status'=>'error','message'=>'Amount too high']);
            $conn->close(); break;
        }

        $monthly_interest = $amount * (12/100/12);
        $next_due = date('Y-m-d', strtotime('+30 days'));
        $grace_until = date('Y-m-d', strtotime('+37 days'));

        $stmt = $conn->prepare("
            INSERT INTO p2p_lending 
            (loan_request_id, lender_user_id, amount_lent, tenure_months, monthly_interest, next_due_date, grace_until)
            VALUES (?, ?, ?, 12, ?, ?, ?)
        ");
        $stmt->bind_param("iidiss", $loan_request_id, $lender_id, $amount, $monthly_interest, $next_due, $grace_until);

        if ($stmt->execute()) {
            $p2p_id = $conn->insert_id;

            $stmt2 = $conn->prepare("UPDATE loans SET status='disbursed', amount_approved=?, disbursed_at=NOW() WHERE id=?");
            $stmt2->bind_param("di", $amount, $loan_request_id);
            $stmt2->execute(); $stmt2->close();

            echo json_encode([
                'status'=>'success',
                'p2p_loan_id'=>$p2p_id,
                'monthly_payment'=>round($amount/12 + $monthly_interest, 2)
            ]);
        } else {
            http_response_code(500);
            echo json_encode(['status'=>'error','message'=>'Lend failed']);
        }
        $stmt->close(); $conn->close();
        break;
        /* ==================== LOAN REQUEST (P2P) ==================== */
    case 'POST request':
        $data = json_decode(file_get_contents('php://input'), true) ?: $_POST;
        $user_id = $data['user_id'] ?? '';
        $amount = (float)($data['amount'] ?? 0);
        $tenure = (int)($data['tenure_months'] ?? 0);

        if (empty($user_id) || $amount <= 0 || $tenure <= 0) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'user_id, amount, and tenure_months are required']);
            break;
        }

        $conn = getDbConnection();

        $stmt = $conn->prepare("SELECT id FROM users WHERE id = ?");
        $stmt->bind_param("i", $user_id);
        $stmt->execute();
        if ($stmt->get_result()->num_rows === 0) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'User not found']);
            $stmt->close(); $conn->close(); break;
        }
        $stmt->close();

        $stmt = $conn->prepare("SELECT COALESCE(SUM(savings_amount), 0) AS total_savings FROM transactions WHERE user_id = ? AND status = 'completed'");
        $stmt->bind_param("i", $user_id);
        $stmt->execute();
        $totalSavings = (float)$stmt->get_result()->fetch_assoc()['total_savings'];
        $stmt->close();

        if ($amount > $totalSavings * 2) {
            http_response_code(400);
            echo json_encode([
                'status' => 'error',
                'message' => 'Loan amount exceeds limit',
                'max_allowed' => $totalSavings * 2,
                'your_savings' => $totalSavings
            ]);
            $conn->close(); break;
        }

        $stmt = $conn->prepare("
            INSERT INTO loans 
            (user_id, amount_requested, interest_rate, tenure_months, status) 
            VALUES (?, ?, 12.00, ?, 'pending')
        ");
        $stmt->bind_param("idi", $user_id, $amount, $tenure);

        if ($stmt->execute()) {
            $loan_id = $conn->insert_id;
            echo json_encode([
                'status' => 'success',
                'message' => 'Loan application submitted',
                'loan_id' => $loan_id
            ]);
        } else {
            http_response_code(500);
            echo json_encode(['status' => 'error', 'message' => 'Failed to save loan request']);
        }
        $stmt->close(); $conn->close();
        break;
        /* ==================== P2P: AUTO LEND ==================== */
    case 'POST auto-lend':
        $data = json_decode(file_get_contents('php://input'), true) ?: $_POST;
        $lender_id = $data['lender_id'] ?? '';
        $amount_to_lend = (float)($data['amount'] ?? 0);
        $max_risk_per_loan = (float)($data['max_per_loan'] ?? 5000);

        if (empty($lender_id) || $amount_to_lend <= 0) {
            http_response_code(400);
            echo json_encode(['status'=>'error','message'=>'lender_id and amount required']);
            break;
        }

        $conn = getDbConnection();

        $stmt = $conn->prepare("SELECT id FROM users WHERE id=?");
        $stmt->bind_param("i",$lender_id); $stmt->execute();
        if ($stmt->get_result()->num_rows === 0) {
            echo json_encode(['status'=>'error','message'=>'Lender not found']); $stmt->close(); $conn->close(); break;
        }
        $stmt->close();

        $stmt = $conn->prepare("
            SELECT id, amount_requested FROM loans 
            WHERE status='pending' AND amount_requested <= ?
            ORDER BY applied_at ASC
        ");
        $stmt->bind_param("d", $max_risk_per_loan);
        $stmt->execute();
        $res = $stmt->get_result();

        $lent = 0;
        $loans_funded = [];

        while ($loan = $res->fetch_assoc() && $lent < $amount_to_lend) {
            $lend_here = min($loan['amount_requested'], $amount_to_lend - $lent);
            $monthly_int = $lend_here * (12/100/12);
            $next_due = date('Y-m-d', strtotime('+30 days'));
            $grace = date('Y-m-d', strtotime('+37 days'));

            $ins = $conn->prepare("
                INSERT INTO p2p_lending 
                (loan_request_id, lender_user_id, amount_lent, tenure_months, monthly_interest, next_due_date, grace_until)
                VALUES (?, ?, ?, 12, ?, ?, ?)
            ");
            $ins->bind_param("iidiss", $loan['id'], $lender_id, $lend_here, $monthly_int, $next_due, $grace);
            if ($ins->execute()) {
                $p2p_id = $conn->insert_id;
                $loans_funded[] = ['loan_id'=>$loan['id'], 'amount'=>$lend_here, 'p2p_id'=>$p2p_id];
                $lent += $lend_here;

                $upd = $conn->prepare("UPDATE loans SET amount_requested=amount_requested-?, status=IF(amount_requested<=?, 'disbursed', status) WHERE id=?");
                $upd->bind_param("ddi", $lend_here, $lend_here, $loan['id']);
                $upd->execute(); $upd->close();
            }
            $ins->close();
        }

        echo json_encode([
            'status'=>'success',
            'total_lent'=>$lent,
            'loans_funded'=>$loans_funded
        ]);
        $stmt->close(); $conn->close();
        break;

// ==================== GET P2P LENDERS (Available to Lend) ====================
case 'GET p2p-lenders':
    $conn = getDbConnection();
    $stmt = $conn->prepare("
        SELECT u.id, u.full_name as name, u.selfie_path as avatar,
               COALESCE(SUM(t.savings_amount), 0) as available,
               8.0 as interest, 4.8 as rating
        FROM users u
        LEFT JOIN transactions t ON u.id = t.user_id AND t.status = 'completed'
        WHERE u.can_lend = 1
        GROUP BY u.id
        HAVING available > 0
        LIMIT 10
    ");
    $stmt->execute();
    $result = $stmt->get_result();
    $lenders = [];
    while ($row = $result->fetch_assoc()) {
        $row['available'] = (float)$row['available'];
        $row['interest'] = (float)$row['interest'];
        $row['rating'] = (float)$row['rating'];
        $lenders[] = $row;
    }
    $stmt->close(); $conn->close();

    echo json_encode(['status' => 'success', 'data' => $lenders]);
    break;


// ==================== GET PENDING LOAN REQUESTS (For Lenders) ====================
case 'GET pending-loans':
    $conn = getDbConnection();
    $stmt = $conn->prepare("
        SELECT l.id, l.user_id, l.amount_requested, l.tenure_months,
               u.full_name
        FROM loans l
        JOIN users u ON l.user_id = u.id
        WHERE l.status = 'pending'
        ORDER BY l.applied_at DESC
        LIMIT 20
    ");
    $stmt->execute();
    $result = $stmt->get_result();
    $loans = [];
    while ($row = $result->fetch_assoc()) {
        $row['amount_requested'] = (float)$row['amount_requested'];
        $row['tenure_months'] = (int)$row['tenure_months'];
        $loans[] = $row;
    }
    $stmt->close(); $conn->close();

    echo json_encode(['status' => 'success', 'data' => $loans]);
    break;
    
    // ==================== GET LOAN OFFERS BY TYPE ====================

    /* -------------------------------------------------
   1. GET /offers  →  return *all* active offers
   ------------------------------------------------- */
case 'GET offers':
    $conn = getDbConnection();

    $stmt = $conn->prepare("
        SELECT  id,
                title,
                max_amount,
                interest,
                max_months,
                extra_terms
        FROM    loan_offers
        WHERE   active = 1
        ORDER BY max_amount ASC
    ");
    $stmt->execute();
    $result = $stmt->get_result();

    $offers = [];
    while ($row = $result->fetch_assoc()) {
        $offers[] = [
            'id'          => (int)$row['id'],
            'title'       => $row['title'],
            'max_amount'  => (float)$row['max_amount'],
            'interest'    => (float)$row['interest'],
            'max_months'  => (int)$row['max_months'],
            'extra_terms' => $row['extra_terms'] ? json_decode($row['extra_terms'], true) : null
        ];
    }

    $stmt->close();
    $conn->close();

    echo json_encode(['status' => 'success', 'data' => $offers]);
    break;


/* -------------------------------------------------
   2. POST /apply-loan  →  user picks an offer
   ------------------------------------------------- */
case 'POST apply-loan':
    $data = json_decode(file_get_contents('php://input'), true) ?: $_POST;

    $user_id   = $data['user_id'] ?? '';
    $offer_id  = $data['offer_id'] ?? '';          // <-- NEW
    $amount    = (float)($data['amount_requested'] ?? 0);
    $tenure    = (int)($data['tenure_months'] ?? 0);

    if (empty($user_id) || empty($offer_id) || $amount <= 0 || $tenure <= 0) {
        http_response_code(400);
        echo json_encode(['status'=>'error','message'=>'user_id, offer_id, amount and tenure required']);
        break;
    }

    $conn = getDbConnection();

    // ---- 1. Validate user exists ----
    $stmt = $conn->prepare("SELECT id FROM users WHERE id = ?");
    $stmt->bind_param("i", $user_id);
    $stmt->execute();
    if ($stmt->get_result()->num_rows === 0) {
        $stmt->close(); $conn->close();
        http_response_code(400);
        echo json_encode(['status'=>'error','message'=>'User not found']);
        break;
    }
    $stmt->close();

    // ---- 2. Pull the chosen offer (and lock it) ----
    $stmt = $conn->prepare("
        SELECT max_amount, interest, max_months
        FROM loan_offers
        WHERE id = ? AND active = 1
        FOR UPDATE
    ");
    $stmt->bind_param("i", $offer_id);
    $stmt->execute();
    $offer = $stmt->get_result()->fetch_assoc();
    $stmt->close();

    if (!$offer) {
        $conn->close();
        http_response_code(400);
        echo json_encode(['status'=>'error','message'=>'Offer not found or inactive']);
        break;
    }

    // ---- 3. Offer-specific limits ----
    if ($amount > $offer['max_amount']) {
        $conn->close();
        http_response_code(400);
        echo json_encode(['status'=>'error','message'=>'Amount exceeds offer limit','max'=>$offer['max_amount']]);
        break;
    }
    if ($tenure > $offer['max_months']) {
        $conn->close();
        http_response_code(400);
        echo json_encode(['status'=>'error','message'=>'Tenure exceeds offer limit','max'=>$offer['max_months']]);
        break;
    }

    // ---- 4. Global savings-limit (2× total savings) ----
    $stmt = $conn->prepare("
        SELECT COALESCE(SUM(savings_amount),0) AS total
        FROM transactions
        WHERE user_id = ? AND status = 'completed'
    ");
    $stmt->bind_param("i", $user_id);
    $stmt->execute();
    $totalSavings = (float)$stmt->get_result()->fetch_assoc()['total'];
    $stmt->close();

    if ($amount > $totalSavings * 2) {
        $conn->close();
        http_response_code(400);
        echo json_encode([
            'status'=>'error',
            'message'=>'Loan exceeds 2× your savings',
            'max_allowed'=> $totalSavings * 2,
            'your_savings'=> $totalSavings
        ]);
        break;
    }

    // ---- 5. Insert the loan (status = pending) ----
    $stmt = $conn->prepare("
        INSERT INTO loans
        (user_id, amount_requested, interest_rate, tenure_months, status, offer_id)
        VALUES (?, ?, ?, ?, 'pending', ?)
    ");
    $stmt->bind_param(
        "iddii",
        $user_id,
        $amount,
        $offer['interest'],
        $tenure,
        $offer_id
    );

    if ($stmt->execute()) {
        $loan_id = $conn->insert_id;
        $conn->close();
        echo json_encode([
            'status'   => 'success',
            'message'  => 'Loan application submitted – status: pending',
            'loan_id'  => $loan_id,
            'terms'    => [
                'interest'    => $offer['interest'],
                'max_months'  => $offer['max_months'],
                'max_amount'  => $offer['max_amount']
            ]
        ]);
    } else {
        $conn->close();
        http_response_code(500);
        echo json_encode(['status'=>'error','message'=>'DB insert failed']);
    }
    $stmt->close();
    break;
    default:
        http_response_code(404);
        echo json_encode(['status' => 'error', 'message' => 'Endpoint not found']);
        break;
}