<?php
/**
 * PGA Payment Gateway - Official PHP SDK
 *
 * @version 1.0.0
 * @author PGA Payment Gateway
 * @license MIT
 *
 * Cara penggunaan:
 *
 * ```php
 * require_once 'PGAClient.php';
 *
 * $pga = new PGAClient('PGA_YOUR_API_KEY', 'your_secret_key');
 *
 * // Buat pembayaran QRIS
 * $result = $pga->createQrisPayment(50000, 'testnara', [
 *     'order_id' => 'INV-001',
 *     'description' => 'Pembayaran Order #001'
 * ]);
 *
 * if ($result['success']) {
 *     echo "Payment URL: " . $result['data']['payment_url'];
 * }
 * ```
 */
namespace App\Services;

use Exception;
use InvalidArgumentException;

class PGAClient
{
    const VERSION = '1.0.0';

    /**
     * API Base URL
     */
    private string $baseUrl;

    /**
     * API Key
     */
    private string $apiKey;

    /**
     * Secret Key
     */
    private string $secretKey;

    /**
     * Request timeout in seconds
     */
    private int $timeout = 30;

    /**
     * Verify SSL certificate
     */
    private bool $verifySSL = true;

    /**
     * Last request info for debugging
     */
    private array $lastRequest = [];

    /**
     * Last response info for debugging
     */
    private array $lastResponse = [];

    /**
     * Initialize PGA Client
     *
     * @param string $apiKey Your API Key (starts with PGA_)
     * @param string $secretKey Your Secret Key
     * @param string $baseUrl API Base URL (optional, for sandbox testing)
     */
    public function __construct(string $apiKey, string $secretKey, string $baseUrl = 'https://api.x-pay.asia')
    {
        if (empty($apiKey) || empty($secretKey)) {
            throw new InvalidArgumentException('API Key and Secret Key are required');
        }

        if (!str_starts_with($apiKey, 'PGA_')) {
            throw new InvalidArgumentException('Invalid API Key format. API Key should start with PGA_');
        }

        $this->apiKey = $apiKey;
        $this->secretKey = $secretKey;
        $this->baseUrl = rtrim($baseUrl, '/');
    }

    // =========================================================================
    // PAYMENT METHODS
    // =========================================================================

    /**
     * Create QRIS Payment
     *
     * @param int $amount Amount in IDR (min: 10000, max: 50000000)
     * @param string $customerId Customer identifier (username, email, phone, or unique ID)
     * @param array $options Additional options:
     *   - order_id: Your order/invoice reference
     *   - description: Payment description
     *   - callback_url: Override default callback URL
     * @return array Response with success status and data
     *
     * @example
     * ```php
     * $result = $pga->createQrisPayment(50000, 'testnara', [
     *     'order_id' => 'INV-2024-001',
     *     'description' => 'Payment for Order #001'
     * ]);
     * ```
     */
    public function createQrisPayment(int $amount, string $customerId, array $options = []): array
    {
        // Validate amount
        if ($amount < 10000) {
            return $this->errorResponse('Amount must be at least Rp 10.000');
        }
        if ($amount > 50000000) {
            return $this->errorResponse('Amount cannot exceed Rp 50.000.000');
        }

        // Validate customer ID
        if (empty($customerId)) {
            return $this->errorResponse('Customer ID is required');
        }

        $data = [
            'amount' => $amount,
            'customer_id' => $customerId,
        ];

        // Add optional fields
        if (!empty($options['order_id'])) {
            $data['order_id'] = $options['order_id'];
        }
        if (!empty($options['description'])) {
            $data['description'] = $options['description'];
        }
        if (!empty($options['callback_url'])) {
            if (!filter_var($options['callback_url'], FILTER_VALIDATE_URL)) {
                return $this->errorResponse('Invalid callback URL format');
            }
            $data['callback_url'] = $options['callback_url'];
        }

        return $this->request('POST', '/api/v1/payment/qris', $data);
    }

    /**
     * Get Payment Status
     *
     * @param string $transactionId Transaction ID (TRXxxxxx) or your order_id
     * @return array Response with payment details
     *
     * @example
     * ```php
     * $status = $pga->getPaymentStatus('TRX20240115ABC123');
     * // or
     * $status = $pga->getPaymentStatus('INV-2024-001'); // your order_id
     * ```
     */
    public function getPaymentStatus(string $transactionId): array
    {
        if (empty($transactionId)) {
            return $this->errorResponse('Transaction ID is required');
        }

        return $this->request('GET', '/api/v1/payment/' . urlencode($transactionId));
    }

    // =========================================================================
    // BALANCE METHODS
    // =========================================================================

    /**
     * Get Merchant Balance
     *
     * @return array Response with available and pending balance
     *
     * @example
     * ```php
     * $balance = $pga->getBalance();
     * echo "Available: " . $balance['data']['available_formatted'];
     * echo "Pending: " . $balance['data']['pending_formatted'];
     * ```
     */
    public function getBalance(): array
    {
        return $this->request('GET', '/api/v1/balance');
    }

    // =========================================================================
    // CALLBACK VERIFICATION
    // =========================================================================

    /**
     * Verify Callback Signature
     *
     * Use this method to verify that callback/webhook came from PGA server.
     *
     * @param string $payload Raw JSON payload from callback
     * @param string $signature Signature from X-Callback-Signature header
     * @return bool True if signature is valid
     *
     * @example
     * ```php
     * $payload = file_get_contents('php://input');
     * $signature = $_SERVER['HTTP_X_CALLBACK_SIGNATURE'] ?? '';
     *
     * if (!$pga->verifyCallbackSignature($payload, $signature)) {
     *     http_response_code(401);
     *     die('Invalid signature');
     * }
     *
     * $data = json_decode($payload, true);
     * // Process callback...
     * ```
     */
    public function verifyCallbackSignature(string $payload, string $signature): bool
    {
        if (empty($payload) || empty($signature)) {
            return false;
        }

        // Parse and sort payload for consistent signature
        $data = json_decode($payload, true);
        if ($data === null) {
            return false;
        }

        ksort($data);
        $sortedPayload = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

        $expectedSignature = hash_hmac('sha256', $sortedPayload, $this->secretKey);

        return hash_equals($expectedSignature, $signature);
    }

    /**
     * Parse and Verify Callback
     *
     * Convenience method that verifies signature and returns parsed data.
     *
     * @param string|null $payload Raw payload (null to read from php://input)
     * @param string|null $signature Signature (null to read from header)
     * @return array Parsed callback data or error
     *
     * @example
     * ```php
     * $callback = $pga->parseCallback();
     *
     * if (!$callback['success']) {
     *     http_response_code(401);
     *     die($callback['error']);
     * }
     *
     * $transactionStatus = $callback['data']['transaction_status'];
     * $orderId = $callback['data']['order_id'];
     * ```
     */
    public function parseCallback(?string $payload = null, ?string $signature = null): array
    {
        // Read from input if not provided
        if ($payload === null) {
            $payload = file_get_contents('php://input');
        }
        if ($signature === null) {
            $signature = $_SERVER['HTTP_X_CALLBACK_SIGNATURE'] ?? '';
        }

        // Verify signature
        if (!$this->verifyCallbackSignature($payload, $signature)) {
            return $this->errorResponse('Invalid callback signature');
        }

        // Parse data
        $data = json_decode($payload, true);
        if ($data === null) {
            return $this->errorResponse('Invalid JSON payload');
        }

        return [
            'success' => true,
            'data' => $data,
        ];
    }

    // =========================================================================
    // UTILITY METHODS
    // =========================================================================

    /**
     * Check API Health
     *
     * @return array Health status
     */
    public function healthCheck(): array
    {
        return $this->request('GET', '/api/v1/health', [], false);
    }

    /**
     * Set request timeout
     *
     * @param int $seconds Timeout in seconds
     * @return self
     */
    public function setTimeout(int $seconds): self
    {
        $this->timeout = max(5, min(120, $seconds));
        return $this;
    }

    /**
     * Disable SSL verification (NOT recommended for production)
     *
     * @param bool $verify Whether to verify SSL certificate
     * @return self
     */
    public function setVerifySSL(bool $verify): self
    {
        $this->verifySSL = $verify;
        return $this;
    }

    /**
     * Get last request info (for debugging)
     *
     * @return array
     */
    public function getLastRequest(): array
    {
        return $this->lastRequest;
    }

    /**
     * Get last response info (for debugging)
     *
     * @return array
     */
    public function getLastResponse(): array
    {
        return $this->lastResponse;
    }

    // =========================================================================
    // INTERNAL METHODS
    // =========================================================================

    /**
     * Make API Request
     */
    private function request(string $method, string $endpoint, array $data = [], bool $auth = true): array
    {
        $url = $this->baseUrl . $endpoint;
        $timestamp = time();
        $body = !empty($data) ? json_encode($data) : '';

        // Build headers
        $headers = [
            'Content-Type: application/json',
            'Accept: application/json',
            'User-Agent: PGA-PHP-SDK/' . self::VERSION,
        ];

        // Add authentication headers
        if ($auth) {
            $nonce = '';

            // Nonce required for mutating requests
            if (in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'])) {
                $nonce = bin2hex(random_bytes(16));
            }

            // Generate signature
            $signatureData = $this->apiKey . $timestamp . $nonce . $body;
            $signature = hash_hmac('sha256', $signatureData, $this->secretKey);

            $headers[] = 'X-API-Key: ' . $this->apiKey;
            $headers[] = 'X-Timestamp: ' . $timestamp;
            $headers[] = 'X-Signature: ' . $signature;

            if (!empty($nonce)) {
                $headers[] = 'X-Nonce: ' . $nonce;
            }
        }

        // Store request info for debugging
        $this->lastRequest = [
            'method' => $method,
            'url' => $url,
            'headers' => $headers,
            'body' => $body,
            'timestamp' => $timestamp,
        ];

        // Initialize cURL
        $ch = curl_init();

        $curlOptions = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => $this->timeout,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_SSL_VERIFYPEER => $this->verifySSL,
            CURLOPT_SSL_VERIFYHOST => $this->verifySSL ? 2 : 0,
        ];

        // Set method-specific options
        switch ($method) {
            case 'POST':
                $curlOptions[CURLOPT_POST] = true;
                $curlOptions[CURLOPT_POSTFIELDS] = $body;
                break;
            case 'PUT':
            case 'PATCH':
            case 'DELETE':
                $curlOptions[CURLOPT_CUSTOMREQUEST] = $method;
                $curlOptions[CURLOPT_POSTFIELDS] = $body;
                break;
            case 'GET':
            default:
                // GET is default
                break;
        }

        curl_setopt_array($ch, $curlOptions);

        // Execute request
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        $errno = curl_errno($ch);

        curl_close($ch);

        // Store response info for debugging
        $this->lastResponse = [
            'http_code' => $httpCode,
            'body' => $response,
            'error' => $error,
            'errno' => $errno,
        ];

        // Handle cURL errors
        if ($errno) {
            return $this->errorResponse('Connection error: ' . $error, $errno);
        }

        // Parse response
        $result = json_decode($response, true);

        if ($result === null && !empty($response)) {
            return $this->errorResponse('Invalid JSON response from server', $httpCode);
        }

        // Handle HTTP errors
        if ($httpCode >= 400) {
            $errorMessage = $result['error'] ?? $result['message'] ?? 'Request failed';
            return $this->errorResponse($errorMessage, $httpCode);
        }

        return $result ?? ['success' => true];
    }

    /**
     * Create error response
     */
    private function errorResponse(string $message, int $code = 0): array
    {
        return [
            'success' => false,
            'error' => $message,
            'code' => $code,
        ];
    }
}

// =========================================================================
// HELPER CLASS: PGA Exception
// =========================================================================

class PGAException extends Exception
{
    private array $response;

    public function __construct(string $message, int $code = 0, array $response = [])
    {
        parent::__construct($message, $code);
        $this->response = $response;
    }

    public function getResponse(): array
    {
        return $this->response;
    }
}
