<?php
/**
 * Store Paystack Callback Handler
 * Handles Paystack payment callbacks for store orders
 * Automatically processes orders when payment is confirmed
 */

session_start();
require_once 'config.php';
require_once 'config_paystack.php';
require_once 'app/classes/StoreManager.php';
require_once 'app/classes/ExternalAPIService.php';
require_once 'app/classes/BalanceHistory.php';
require_once 'app/classes/ExternalAPIPricingManager.php';

use App\classes\ExternalAPIPricingManager;

// SECURITY: Rate limiting to prevent abuse (simple in-memory approach)
// For production, consider using Redis or database-based rate limiting
$ipAddress = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
static $rateLimitCache = [];
$cacheKey = 'paystack_callback_' . $ipAddress;
$currentTime = time();
$timeWindow = 60;
$maxAttempts = 10;

// Clean old entries (older than timeWindow)
if (isset($rateLimitCache[$cacheKey])) {
    $rateLimitCache[$cacheKey] = array_filter($rateLimitCache[$cacheKey], function($timestamp) use ($currentTime, $timeWindow) {
        return ($currentTime - $timestamp) < $timeWindow;
    });
} else {
    $rateLimitCache[$cacheKey] = [];
}

// Check if limit exceeded
if (count($rateLimitCache[$cacheKey]) >= $maxAttempts) {
    error_log("SECURITY: Rate limit exceeded for IP: $ipAddress - " . count($rateLimitCache[$cacheKey]) . " attempts in $timeWindow seconds");
    http_response_code(429);
    echo 'Too many requests';
    exit;
}

// Add current request to cache
$rateLimitCache[$cacheKey][] = $currentTime;

$reference = isset($_GET['reference']) ? trim($_GET['reference']) : '';
if ($reference === '') {
    http_response_code(400);
    echo 'Missing payment reference';
    exit;
}

// SECURITY: Validate reference format (alphanumeric and hyphens only)
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $reference)) {
    error_log("SECURITY: Invalid reference format - Reference: $reference, IP: $ipAddress");
    http_response_code(400);
    echo 'Invalid payment reference format';
    exit;
}

// Get order by Paystack reference (check all statuses first to handle duplicates)
$orderStmt = $conn->prepare("
    SELECT o.id, o.order_reference, o.store_id, o.agent_id, o.network_type, 
           o.package_size, o.beneficiary_number, o.amount, o.status, o.payment_method,
           s.id as store_id, s.owner_id, s.store_slug
    FROM orders o
    INNER JOIN stores s ON o.store_id = s.id
    WHERE o.paystack_reference = ?
    LIMIT 1
");
$orderStmt->bind_param("s", $reference);
$orderStmt->execute();
$orderResult = $orderStmt->get_result();
$order = $orderResult->fetch_assoc();
$orderStmt->close();

if (!$order) {
    error_log("Store Paystack callback: Order not found for reference: $reference");
    http_response_code(404);
    echo 'Order not found';
    exit;
}

// CRITICAL: Check if store is active - cannot process orders for inactive stores
$storeStatusStmt = $conn->prepare("SELECT status FROM stores WHERE id = ? LIMIT 1");
$storeStatusStmt->bind_param("i", $order['store_id']);
$storeStatusStmt->execute();
$storeStatusResult = $storeStatusStmt->get_result();
$storeStatusData = $storeStatusResult->fetch_assoc();
$storeStatusStmt->close();

if (!$storeStatusData || $storeStatusData['status'] !== 'active') {
    error_log("Store Paystack callback: Store is inactive - Order ID: {$order['id']}, Store ID: {$order['store_id']}, Reference: $reference");
    http_response_code(403);
    echo 'Store is currently inactive and not accepting orders';
    exit;
}

// Get store slug for redirect
$storeSlug = $order['store_slug'] ?? '';

// Store the store slug in session so customers can always return to the correct store
if (!empty($storeSlug)) {
    $_SESSION['last_store_slug'] = $storeSlug;
}

// Check if order was already processed (idempotency check)
if ($order['status'] !== 'pending') {
    error_log("Store Paystack callback: Order already processed - Order ID: {$order['id']}, Status: {$order['status']}, Reference: $reference");
    // Redirect back to store page
    if ($storeSlug) {
        $statusParam = $order['status'] === 'processing' || $order['status'] === 'completed' ? 'payment_success' : 'payment_error';
        header('Location: store.php?slug=' . urlencode($storeSlug) . '&payment=' . $statusParam . '&ref=' . urlencode($order['order_reference']));
    } else {
        header('Location: order-status.php?ref=' . urlencode($order['order_reference']) . '&status=already_processed');
    }
    exit;
}

// Verify payment with Paystack using store-specific credentials
$verify = paystackVerifyTransaction($reference, $order['store_id']);
$ok = $verify['success'] && (($verify['data']['data']['status'] ?? '') === 'success');

if (!$ok) {
    $errorMsg = $verify['error'] ?? 'Payment verification failed';
    $status = $verify['data']['data']['status'] ?? 'unknown';
    error_log("Store Paystack callback: Payment verification failed - Reference: $reference, Status: $status, Error: $errorMsg");
    http_response_code(400);
    echo 'Payment verification failed. Status: ' . $status;
    exit;
}

$paymentData = $verify['data']['data'] ?? [];
$amountPaid = intval($paymentData['amount'] ?? 0);
$totalAmountGHS = $amountPaid / 100;

// Verify amount matches
$orderAmount = floatval($order['amount']);
if (abs($totalAmountGHS - $orderAmount) > 0.50) {
    error_log("Amount mismatch - Order: $orderAmount, Paid: $totalAmountGHS, Reference: $reference");
    http_response_code(400);
    echo 'Payment amount mismatch';
    exit;
}

// Helper function to get admin price if ExternalAPIPricingManager doesn't have it
function getAdminPriceForPackage($conn, $packageSize, $networkType = 'mtn') {
    // Parse package size to GB
    $packageSizeGb = 0;
    if (preg_match('/(\d+(?:\.\d+)?)\s*GB/i', $packageSize, $matches)) {
        $packageSizeGb = (float)$matches[1];
    } elseif (preg_match('/(\d+(?:\.\d+)?)\s*MB/i', $packageSize, $matches)) {
        $packageSizeGb = round(((float)$matches[1]) / 1024, 4);
    } else {
        $num = floatval(preg_replace('/[^0-9.]/', '', $packageSize));
        $packageSizeGb = $num > 0 ? $num : 0;
    }
    
    $stmt = $conn->prepare("
        SELECT price_per_gb FROM agent_pricing_rules 
        WHERE network_type = ? AND role = 'agent' AND is_active = 1 
        AND min_size_gb <= ? AND max_size_gb >= ?
        ORDER BY min_size_gb DESC
        LIMIT 1
    ");
    $stmt->bind_param("sdd", $networkType, $packageSizeGb, $packageSizeGb);
    $stmt->execute();
    $result = $stmt->get_result();
    $row = $result->fetch_assoc();
    $stmt->close();
    
    if ($row) {
        return round($row['price_per_gb'] * $packageSizeGb, 2);
    }
    return null;
}

// Start transaction
$conn->begin_transaction();

try {
    // Update order payment status
    $updateOrderStmt = $conn->prepare("
        UPDATE orders 
        SET status = 'processing', 
            payment_method = 'paystack',
            status_updated_at = NOW()
        WHERE id = ? AND status = 'pending'
    ");
    $updateOrderStmt->bind_param("i", $order['id']);
    
    if (!$updateOrderStmt->execute()) {
        throw new Exception("Failed to update order status: " . $updateOrderStmt->error);
    }
    
    if ($updateOrderStmt->affected_rows === 0) {
        // Order was already processed
        $conn->commit();
        header('Location: order-status.php?ref=' . urlencode($order['order_reference']) . '&status=already_processed');
        exit;
    }
    $updateOrderStmt->close();
    
    // Check if order is from a sub-agent (not the store owner)
    $orderAgentId = $order['agent_id'];
    $storeOwnerId = $order['owner_id'];
    $isSubAgent = ($orderAgentId != $storeOwnerId);
    
    // Calculate pricing for both sub-agent (if applicable) and store owner
    $subAgentAmount = 0;
    $storeOwnerAmount = 0;
    
    // Always calculate admin base pricing for store owner (what store owner pays)
    $externalPricingManager = new ExternalAPIPricingManager($conn);
    $adminPrice = $externalPricingManager->getAdminPrice($order['package_size'], $order['network_type']);
    
    // If admin price not found, try helper function
    if ($adminPrice === null) {
        $adminPrice = getAdminPriceForPackage($conn, $order['package_size'], $order['network_type']);
    }
    
    // Store owner always pays admin base pricing
    $storeOwnerAmount = ($adminPrice !== null && $adminPrice > 0) ? floatval($adminPrice) : $orderAmount;
    
    if ($isSubAgent) {
        // Sub-agent order: Use store owner's sub-agent pricing from store_pricing table
        require_once 'app/classes/StorePricingManager.php';
        $storePricingManager = new StorePricingManager($conn);
        $priceInfo = $storePricingManager->calculatePrice($order['store_id'], $order['package_size'], $order['network_type']);
        
        if ($priceInfo['price'] !== null && $priceInfo['price'] > 0) {
            $subAgentAmount = $priceInfo['price'];
        } else {
            // Fallback: if store pricing not found, use admin price
            $subAgentAmount = ($adminPrice !== null && $adminPrice > 0) ? floatval($adminPrice) : $orderAmount;
        }
    }
    
    // Lock and check balances
    if ($isSubAgent) {
        // Lock and check sub-agent's balance
        $subAgentBalanceStmt = $conn->prepare("SELECT balance FROM agents WHERE id = ? FOR UPDATE");
        $subAgentBalanceStmt->bind_param("i", $orderAgentId);
        $subAgentBalanceStmt->execute();
        $subAgentBalanceResult = $subAgentBalanceStmt->get_result();
        $subAgentData = $subAgentBalanceResult->fetch_assoc();
        $subAgentBalanceStmt->close();
        
        $subAgentCurrentBalance = floatval($subAgentData['balance'] ?? 0);
        
        if ($subAgentCurrentBalance < $subAgentAmount) {
            $conn->rollback();
            error_log("Insufficient balance for order - Sub-agent: $orderAgentId, Required: $subAgentAmount, Available: $subAgentCurrentBalance");
            header('Location: order-status.php?ref=' . urlencode($order['order_reference']) . '&status=insufficient_balance');
            exit;
        }
    }
    
    // Lock and check store owner's balance (always needed)
    $ownerBalanceStmt = $conn->prepare("SELECT balance FROM agents WHERE id = ? FOR UPDATE");
    $ownerBalanceStmt->bind_param("i", $storeOwnerId);
    $ownerBalanceStmt->execute();
    $ownerBalanceResult = $ownerBalanceStmt->get_result();
    $ownerData = $ownerBalanceResult->fetch_assoc();
    $ownerBalanceStmt->close();
    
    $ownerCurrentBalance = floatval($ownerData['balance'] ?? 0);
    
    if ($ownerCurrentBalance < $storeOwnerAmount) {
        $conn->rollback();
        error_log("Insufficient balance for order - Store owner: $storeOwnerId, Required: $storeOwnerAmount, Available: $ownerCurrentBalance");
        header('Location: order-status.php?ref=' . urlencode($order['order_reference']) . '&status=insufficient_balance');
        exit;
    }
    
    // Deduct sub-agent's balance (if sub-agent)
    if ($isSubAgent) {
        $subAgentNewBalance = round($subAgentCurrentBalance - $subAgentAmount, 2);
        $updateSubAgentStmt = $conn->prepare("UPDATE agents SET balance = ? WHERE id = ?");
        $updateSubAgentStmt->bind_param("di", $subAgentNewBalance, $orderAgentId);
        if (!$updateSubAgentStmt->execute()) {
            $updateSubAgentStmt->close();
            throw new Exception("Failed to update sub-agent balance: " . $updateSubAgentStmt->error);
        }
        $updateSubAgentStmt->close();
        
        // Log sub-agent balance history
        try {
            \App\classes\BalanceHistory::log($conn, $orderAgentId, -1 * $subAgentAmount, 'order', $order['id'], $order['beneficiary_number'], $subAgentCurrentBalance, $subAgentNewBalance);
        } catch (Exception $e) {
            error_log("CRITICAL: Failed to log sub-agent balance history for order {$order['id']}: " . $e->getMessage());
        }
    }
    
    // Deduct store owner's balance (always at admin base pricing)
    $ownerNewBalance = round($ownerCurrentBalance - $storeOwnerAmount, 2);
    $updateOwnerStmt = $conn->prepare("UPDATE agents SET balance = ? WHERE id = ?");
    $updateOwnerStmt->bind_param("di", $ownerNewBalance, $storeOwnerId);
    
    if (!$updateOwnerStmt->execute()) {
        $updateOwnerStmt->close();
        throw new Exception("Failed to update store owner balance: " . $updateOwnerStmt->error);
    }
    $updateOwnerStmt->close();
    
    // Log store owner balance history
    try {
        \App\classes\BalanceHistory::log($conn, $storeOwnerId, -1 * $storeOwnerAmount, 'order', $order['id'], $order['beneficiary_number'], $ownerCurrentBalance, $ownerNewBalance);
    } catch (Exception $e) {
        error_log("CRITICAL: Failed to log store owner balance history for order {$order['id']}: " . $e->getMessage());
    }
    
    // Process with external API if needed
    $apiResponse = null;
    $apiSuccess = false;
    
    function shouldUseExternalAPI($networkType) {
        global $conn;
        $stmt = $conn->prepare("SELECT config_value FROM payment_config WHERE config_key = 'api_enabled'");
        $stmt->execute();
        $result = $stmt->get_result();
        $row = $result->fetch_assoc();
        $stmt->close();
        $apiEnabled = $row && (bool)$row['config_value'];
        return $apiEnabled && $networkType === 'mtn';
    }
    
    if (shouldUseExternalAPI($order['network_type'])) {
        $apiService = new ExternalAPIService($order['network_type']);
        $capacity = $apiService->convertPackageSize($order['package_size']);
        $cleanPhone = $apiService->validatePhoneNumber($order['beneficiary_number']);
        
        if ($cleanPhone) {
            $apiResponse = $apiService->purchaseData($cleanPhone, $order['network_type'], $capacity, 'wallet', $order['id']);
            $apiSuccess = $apiResponse['success'] ?? false;
            
            if ($apiSuccess) {
                $externalApiReference = $apiResponse['data']['data']['transactionReference'] ?? null;
                $purchaseId = $apiResponse['data']['data']['purchaseId'] ?? null;
                $dataWebsiteTransactionId = $purchaseId ? (string)$purchaseId : null;
                $api_response_data = json_encode($apiResponse['data']);
                
                $checkColStmt = $conn->query("SHOW COLUMNS FROM orders LIKE 'external_transaction_id'");
                $hasExternalId = $checkColStmt && $checkColStmt->num_rows > 0;
                if ($checkColStmt) $checkColStmt->close();
                
                if ($hasExternalId) {
                    $updateApiStmt = $conn->prepare("
                        UPDATE orders 
                        SET external_transaction_id = ?, 
                            external_reference = ?, 
                            api_response_data = ?, 
                            status = 'processing',
                            status_updated_at = NOW() 
                        WHERE id = ?
                    ");
                    $updateApiStmt->bind_param("sssi", $dataWebsiteTransactionId, $externalApiReference, $api_response_data, $order['id']);
                } else {
                    $updateApiStmt = $conn->prepare("
                        UPDATE orders 
                        SET status = 'processing',
                            status_updated_at = NOW() 
                        WHERE id = ?
                    ");
                    $updateApiStmt->bind_param("i", $order['id']);
                }
                
                $updateApiStmt->execute();
                $updateApiStmt->close();
            } else {
                // API failed - refund balances (refund both sub-agent and store owner if applicable)
                if ($isSubAgent) {
                    // Refund sub-agent
                    $subAgentRefundBalance = round($subAgentNewBalance + $subAgentAmount, 2);
                    $refundSubAgentStmt = $conn->prepare("UPDATE agents SET balance = ? WHERE id = ?");
                    $refundSubAgentStmt->bind_param("di", $subAgentRefundBalance, $orderAgentId);
                    $refundSubAgentStmt->execute();
                    $refundSubAgentStmt->close();
                    
                    // Log sub-agent refund
                    try {
                        \App\classes\BalanceHistory::log($conn, $orderAgentId, $subAgentAmount, 'refund', $order['id'], 'API call failed', $subAgentNewBalance, $subAgentRefundBalance);
                    } catch (Exception $e) {
                        error_log("Failed to log sub-agent refund balance history for order {$order['id']}: " . $e->getMessage());
                    }
                }
                
                // Refund store owner
                $ownerRefundBalance = round($ownerNewBalance + $storeOwnerAmount, 2);
                $refundOwnerStmt = $conn->prepare("UPDATE agents SET balance = ? WHERE id = ?");
                $refundOwnerStmt->bind_param("di", $ownerRefundBalance, $storeOwnerId);
                $refundOwnerStmt->execute();
                $refundOwnerStmt->close();
                
                // Log store owner refund
                try {
                    \App\classes\BalanceHistory::log($conn, $storeOwnerId, $storeOwnerAmount, 'refund', $order['id'], 'API call failed', $ownerNewBalance, $ownerRefundBalance);
                } catch (Exception $e) {
                    error_log("Failed to log store owner refund balance history for order {$order['id']}: " . $e->getMessage());
                }
                
                $updateFailStmt = $conn->prepare("UPDATE orders SET status = 'failed', status_updated_at = NOW() WHERE id = ?");
                $updateFailStmt->bind_param("i", $order['id']);
                $updateFailStmt->execute();
                $updateFailStmt->close();
                
                $conn->commit();
                // Redirect back to store page
                if ($storeSlug) {
                    header('Location: store.php?slug=' . urlencode($storeSlug) . '&payment=api_failed&ref=' . urlencode($order['order_reference']));
                } else {
                    header('Location: order-status.php?ref=' . urlencode($order['order_reference']) . '&status=api_failed');
                }
                exit;
            }
        }
    } else {
        // No external API - just mark as processing
        $updateProcessingStmt = $conn->prepare("UPDATE orders SET status = 'processing', status_updated_at = NOW() WHERE id = ?");
        $updateProcessingStmt->bind_param("i", $order['id']);
        $updateProcessingStmt->execute();
        $updateProcessingStmt->close();
    }
    
    // Commit transaction
    $conn->commit();
    
    // Log success
    error_log("SUCCESS: Store order payment processed - Order ID: {$order['id']}, Reference: $reference, Store: {$order['store_id']}, API Sent: " . ($apiSuccess ? 'Yes' : 'No'));
    
    // Redirect back to store page with success message
    if ($storeSlug) {
        $redirectStatus = $apiSuccess ? 'payment_success' : 'payment_processing';
        header('Location: store.php?slug=' . urlencode($storeSlug) . '&payment=' . $redirectStatus . '&ref=' . urlencode($order['order_reference']));
    } else {
        $redirectStatus = $apiSuccess ? 'success' : 'processing';
        header('Location: order-status.php?ref=' . urlencode($order['order_reference']) . '&status=' . $redirectStatus);
    }
    exit;
    
} catch (Exception $e) {
    $conn->rollback();
    error_log("Store Paystack callback error: " . $e->getMessage() . " - Reference: $reference");
    // Redirect back to store page with error
    if (isset($order['store_slug']) && !empty($order['store_slug'])) {
        header('Location: store.php?slug=' . urlencode($order['store_slug']) . '&payment=payment_error&ref=' . urlencode($order['order_reference'] ?? ''));
    } else {
        header('Location: order-status.php?ref=' . urlencode($order['order_reference'] ?? '') . '&status=error');
    }
    exit;
}
?>

