<?php
/**
 * BillDesk Payment Gateway API Endpoint
 * Handles payment order creation with JWE/JWS encryption
 * Returns JSON response
 */

header('Content-Type: application/json');

// Include database configuration
$db_host = 'localhost';
$db_user = 'root';
$db_password = '';
$db_name = 'billdesk_db';

$conn = new mysqli($db_host, $db_user, $db_password, $db_name);
if ($conn->connect_error) {
    echo json_encode(['status' => 'error', 'message' => "Connection failed: " . $conn->connect_error]);
    exit;
}
$conn->set_charset("utf8mb4");

// Include Composer autoloader for required packages
require __DIR__ . '/../vendor/autoload.php';

// Retrieve form parameters from GET or POST
// Handle JSON input as well
$input = json_decode(file_get_contents('php://input'), true);

// ============================================================
// ENVIRONMENT CONFIGURATION
// Set $useUAT = true for testing, false for production
// ============================================================
// Check if environment is specified in payload
$useUAT = isset($input['env']) && strtolower($input['env']) === 'uat';

if ($useUAT) {
    // UAT (Test) Environment
    define('CREATE_ORDER_URL', 'https://uat1.billdesk.com/u2/payments/ve1_2/orders/create');
    define('PAYMENT_FORM_URL', 'https://uat1.billdesk.com/u2/web/v1_2/embeddedsdk');
} else {
    // Production Environment
    define('CREATE_ORDER_URL', 'https://api.billdesk.com/payments/ve1_2/orders/create');
    define('PAYMENT_FORM_URL', 'https://api.billdesk.com/web/v1_2/embeddedsdk');
}

// Use keys from payload if available, otherwise use defaults
$merchantId = $input['merchant_id'] ?? ($useUAT ? 'BDUAT2K526' : 'SHAKUNIYA');
$clientId   = $input['client_id']   ?? ($useUAT ? 'bduat2k526' : 'shakuniyasj');
$keyId      = $input['key_id']      ?? ($useUAT ? 'apLkPDnO9NYf' : 'l6SVGjSf5Gun');
$sharedKey  = $input['shared_key']  ?? ($useUAT ? 'xzYsZwfLeMDYzIweWT8i90QIWymCBXSn' : '17mAMIqwOBvS9YIQEzeP7VStbgvXUuWK');
$signingKey = $input['signing_key'] ?? ($useUAT ? 'RlKxmMPu6RB1BGaLKGxnnPXon8ynoSKh' : 'z1XIAONLs2YecHz1CYiVlysiJzVzT6h8');

$amount    = $input['tramt'] ?? ($_GET['tramt'] ?? ($_POST['tramt'] ?? 0));
$name      = $input['csname'] ?? ($_GET['csname'] ?? ($_POST['csname'] ?? ""));
$email     = $input['csemail'] ?? ($_GET['csemail'] ?? ($_POST['csemail'] ?? ""));
$number    = $input['csnumber'] ?? ($_GET['csnumber'] ?? ($_POST['csnumber'] ?? ""));
$address   = $input['csaddress'] ?? ($_GET['csaddress'] ?? ($_POST['csaddress'] ?? ""));
$productid = $input['prodid'] ?? ($_GET['prodid'] ?? ($_POST['prodid'] ?? 0));

/**
 * Encode data to base64url format (RFC 4648)
 */
function base64url_encode(string $data): string {
    return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}

/**
 * Decode base64url to binary data
 */
function base64url_decode(string $data): string {
    $padded = $data . str_repeat('=', (4 - strlen($data) % 4) % 4);
    return base64_decode(strtr($padded, '-_', '+/'));
}

/**
 * Generate unique identifiers
 */
$orderId   = 'ORD' . time();
$traceid   = bin2hex(random_bytes(16));
$timestamp = time();

/**
 * Step 1: Insert order into database with customer details
 */
$stmt = $conn->prepare("INSERT INTO sales_orders(name, email, number, address, product_id, orderid) VALUES (?, ?, ?, ?, ?, ?)");

if ($stmt) {
    $stmt->bind_param('ssssss', $name, $email, $number, $address, $productid, $orderId);
    $stmt->execute();
    
    if ($stmt->error) {
        error_log("Database insert error: " . $stmt->error);
    }
    $stmt->close();
} else {
    error_log("Statement preparation failed: " . $conn->error);
}

/**
 * Step 2: Create JSON payload
 */
$payload = json_encode([
    'mercid'     => $merchantId,
    'orderid'    => $orderId,
    'amount'     => $amount . '.00',
    'order_date' => date('Y-m-d\TH:i:sP'),
    'currency'   => '356', // INR
    'ru'         => 'http://localhost/billdesk/return.php',
    'itemcode'   => 'DIRECT',
    'additional_info' => [
        'additional_info1' => $name,
        'additional_info2' => $email,
        'additional_info3' => $productid,
    ],
    'device'     => [
        'init_channel'  => 'internet',
        'ip'            => $_SERVER['REMOTE_ADDR'] ?? '',
        'user_agent'    => $_SERVER['HTTP_USER_AGENT'] ?? '',
        'accept_header' => $_SERVER['HTTP_ACCEPT'] ?? '',
    ],
]);

/**
 * Step 3: Create JWE (JSON Web Encryption) - RFC 7516
 */
function createJWE(string $payload, string $key, string $keyId, string $clientId): string {
    $header = [
        'alg' => 'dir',
        'enc' => 'A256GCM',
        'kid' => $keyId,
        'clientid' => $clientId,
    ];
    
    $protectedHeader = base64url_encode(json_encode($header));
    $encryptedKey = '';
    $iv = openssl_random_pseudo_bytes(12);
    $aad = $protectedHeader;
    $tag = '';
    $ciphertext = openssl_encrypt(
        $payload,
        'aes-256-gcm',
        $key,
        OPENSSL_RAW_DATA,
        $iv,
        $tag,
        $aad
    );
    
    if ($ciphertext === false) {
        throw new Exception('JWE encryption failed: ' . openssl_error_string());
    }
    
    return implode('.', [
        $protectedHeader,
        base64url_encode($encryptedKey),
        base64url_encode($iv),
        base64url_encode($ciphertext),
        base64url_encode($tag)
    ]);
}

/**
 * Step 4: Create JWS (JSON Web Signature) - RFC 7515
 */
function createJWS(string $payload, string $key, string $keyId, string $clientId): string {
    $header = [
        'alg' => 'HS256',
        'kid' => $keyId,
        'clientid' => $clientId,
    ];
    
    $headerEncoded = base64url_encode(json_encode($header));
    $payloadEncoded = base64url_encode($payload);
    $signingInput = $headerEncoded . '.' . $payloadEncoded;
    $signature = hash_hmac('sha256', $signingInput, $key, true);
    
    return $signingInput . '.' . base64url_encode($signature);
}

/**
 * Step 5: Verify JWS signature and extract payload
 */
function verifyJWS(string $jws, string $key): string {
    $parts = explode('.', $jws);
    if (count($parts) !== 3) {
        throw new Exception('Invalid JWS format');
    }
    
    list($headerB64, $payloadB64, $signatureB64) = $parts;
    
    $signingInput = $headerB64 . '.' . $payloadB64;
    $signature = base64url_decode($signatureB64);
    $expectedSignature = hash_hmac('sha256', $signingInput, $key, true);
    
    if (!hash_equals($expectedSignature, $signature)) {
        throw new Exception('Invalid JWS signature');
    }
    
    return base64url_decode($payloadB64);
}

/**
 * Step 6: Decrypt JWE and extract payload
 */
function decryptJWE(string $jwe, string $key): string {
    $parts = explode('.', $jwe);
    if (count($parts) !== 5) {
        throw new Exception('Invalid JWE format');
    }
    
    list($protectedHeader, $encryptedKeyB64, $ivB64, $ciphertextB64, $tagB64) = $parts;
    
    $iv = base64url_decode($ivB64);
    $ciphertext = base64url_decode($ciphertextB64);
    $tag = base64url_decode($tagB64);
    $aad = $protectedHeader;
    
    $plaintext = openssl_decrypt(
        $ciphertext,
        'aes-256-gcm',
        $key,
        OPENSSL_RAW_DATA,
        $iv,
        $tag,
        $aad
    );
    
    if ($plaintext === false) {
        throw new Exception('JWE decryption failed: ' . openssl_error_string());
    }
    
    return $plaintext;
}

try {
    // Create JWE from payload
    $jweToken = createJWE($payload, $sharedKey, $keyId, $clientId);

    // Sign the JWE with JWS
    $signedToken = createJWS($jweToken, $signingKey, $keyId, $clientId);

    /**
     * Log the API request
     */
    /* $logStmt = $conn->prepare("INSERT INTO billdesk_api_log(identifier, data, type) VALUES (?, ?, ?)");
    if ($logStmt) {
        $identifier = "BD-Traceid: $traceid, BD-Timestamp: $timestamp";
        $type = 'create_api_request_string';
        $logStmt->bind_param('sss', $identifier, $signedToken, $type);
        $logStmt->execute();
        $logStmt->close();
    } */

    /**
     * Step 7: Send request to BillDesk API
     */
    $ch = curl_init(CREATE_ORDER_URL);

    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $signedToken,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_TIMEOUT        => 30,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/jose',
            'Accept: application/jose',
            "BD-Traceid: $traceid",
            "BD-Timestamp: $timestamp"
        ],
    ]);

    $encryptedResponse = curl_exec($ch);

    if (curl_errno($ch)) {
        throw new Exception('cURL Error: ' . curl_error($ch));
    }

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

    /**
     * Log the API response
     */
    /* $logStmt = $conn->prepare("INSERT INTO billdesk_api_log(identifier, data, type) VALUES (?, ?, ?)");
    if ($logStmt) {
        $identifier = "BD-Traceid: $traceid, BD-Timestamp: $timestamp";
        $type = 'create_api_response_string';
        $logStmt->bind_param('sss', $identifier, $encryptedResponse, $type);
        $logStmt->execute();
        $logStmt->close();
    } */

    // Validate response
    if (empty($encryptedResponse)) {
        throw new Exception("Empty response from BillDesk API. HTTP Code: " . $httpCode);
    }

    /**
     * Helper function to try to decrypt BillDesk response
     */
    function tryDecryptResponse($response, $signingKey, $sharedKey) {
        if (substr_count($response, '.') !== 2) return null;
        $parts = explode('.', $response);
        if (count($parts) !== 3) return null;
        
        try {
            $jwePayload = base64url_decode($parts[1]);
            if (substr_count($jwePayload, '.') !== 4) return null;
            
            $jweParts = explode('.', $jwePayload);
            if (count($jweParts) !== 5) return null;
            
            list($protectedHeader, $encryptedKeyB64, $ivB64, $ciphertextB64, $tagB64) = $jweParts;
            
            $iv = base64url_decode($ivB64);
            $ciphertext = base64url_decode($ciphertextB64);
            $tag = base64url_decode($tagB64);
            $aad = $protectedHeader;
            
            $plaintext = openssl_decrypt(
                $ciphertext,
                'aes-256-gcm',
                $sharedKey,
                OPENSSL_RAW_DATA,
                $iv,
                $tag,
                $aad
            );
            
            if ($plaintext === false) return null;
            return json_decode($plaintext, true);
        } catch (Exception $e) {
            return null;
        }
    }

    if ($httpCode !== 200 && $httpCode !== 201) {
        $decryptedError = tryDecryptResponse($encryptedResponse, $signingKey, $sharedKey);
        
        if ($decryptedError) {
            echo json_encode([
                'status' => 'error',
                'message' => $decryptedError['message'] ?? 'Unknown error',
                'error_code' => $decryptedError['error_code'] ?? $decryptedError['status'] ?? 'Unknown',
                'full_response' => $decryptedError
            ]);
            exit;
        }
        
        $errorData = json_decode($encryptedResponse, true);
        if ($errorData && isset($errorData['error_code'])) {
            echo json_encode([
                'status' => 'error',
                'message' => $errorData['message'] ?? 'Unknown error',
                'error_code' => $errorData['error_code']
            ]);
            exit;
        }
        
        echo json_encode([
            'status' => 'error',
            'message' => 'BillDesk API returned HTTP ' . $httpCode,
            'raw_response' => substr($encryptedResponse, 0, 200)
        ]);
        exit;
    }

    /**
     * Step 8: Decrypt and verify response
     */
    $paymentLink = '';
    $rdata = '';
    $bdorderid = '';

    // First check if response is HTML
    if (strpos($encryptedResponse, '<html') !== false || strpos($encryptedResponse, '<!DOCTYPE') !== false) {
        throw new Exception('BillDesk returned HTML error page - WAF rejection or server error.');
    }
    
    // Check if response looks like a JWS token
    if (substr_count($encryptedResponse, '.') < 2) {
        $responseData = json_decode($encryptedResponse, true);
        if ($responseData && isset($responseData['payment_link'])) {
            $paymentLink = $responseData['payment_link'];
        } else {
            throw new Exception('Invalid response format: expected JWS token or JSON with payment_link');
        }
    } else {
        $jwePayload = verifyJWS($encryptedResponse, $signingKey);
        $decryptedPayload = decryptJWE($jwePayload, $sharedKey);
        $data = json_decode($decryptedPayload, true);
        
        if (!is_array($data)) {
            throw new Exception('Invalid JSON in decrypted payload');
        }
        
        if (isset($data['links']) && is_array($data['links']) && count($data['links']) > 1) {
            $rdata = $data['links'][1]['parameters']['rdata'] ?? '';
            $bdorderid = $data['links'][1]['parameters']['bdorderid'] ?? '';
            
            $updateStmt = $conn->prepare("UPDATE sales_orders SET bdorderid = ?, rdata = ? WHERE orderid = ?");
            if ($updateStmt) {
                $updateStmt->bind_param('sss', $bdorderid, $rdata, $orderId);
                $updateStmt->execute();
                $updateStmt->close();
            }
            
            $paymentLink = $data['links'][1]['uri'] ?? $data['links'][1]['url'] ?? '';
        } else {
            throw new Exception('Unexpected response structure from BillDesk');
        }
    }
    
    if (empty($paymentLink) && !empty($rdata)) {
        echo json_encode([
            'status' => 'success',
            'type' => 'form_submit',
            'url' => PAYMENT_FORM_URL,
            'bdorderid' => $bdorderid,
            'merchantid' => $merchantId,
            'rdata' => $rdata
        ]);
        exit;
    }
    
    if (empty($paymentLink)) {
        throw new Exception('No payment link extracted from response');
    }

    echo json_encode([
        'status' => 'success',
        'type' => 'redirect',
        'url' => $paymentLink
    ]);

} catch (Exception $e) {
    echo json_encode([
        'status' => 'error',
        'message' => $e->getMessage()
    ]);
}
