<?php

if (!defined('ABSPATH')) {
    exit;
}

require_once CREGIS_PAYMENT_PLUGIN_DIR . 'includes/class-cregis-logger.php';

class Cregis_API {
    private $project_id;
    private $secret_key;
    private $api_mode;
    private $api_base_url;
    private $logger;

    /**
     * Mapping of display token names to API token names
     */
    private $token_name_mapping = array(
        'Base ETH' => 'Base',
        'Arbitrum One ETH' => 'Arbitrum One',
        'Optimism ETH' => 'Optimism',
    );

    public function __construct($project_id, $secret_key, $api_mode = 'test', $api_url = null, $debug = false) {
        $this->project_id = $project_id;
        $this->secret_key = $secret_key;
        $this->api_mode = $api_mode;
        $this->api_base_url = $api_url ?: CREGIS_PAYMENT_API_BASE_URL;
        $this->logger = new Cregis_Logger($debug);
    }
    
    /**
     * Normalize a token name to the API expected format
     */
    private function normalize_token_name($token) {
        if (isset($this->token_name_mapping[$token])) {
            return $this->token_name_mapping[$token];
        }
        return $token;
    }

    /**
     * Normalize token names in an array
     */
    private function normalize_tokens($tokens) {
        if (empty($tokens)) {
            return $tokens;
        }
        return array_map(array($this, 'normalize_token_name'), $tokens);
    }

    /**
     * Normalize token names (public static, for use outside the class)
     */
    public static function normalize_token_names($tokens) {
        $mapping = array(
            'Base ETH' => 'Base',
            'Arbitrum One ETH' => 'Arbitrum One',
            'Optimism ETH' => 'Optimism',
        );
        if (empty($tokens)) {
            return $tokens;
        }
        return array_map(function($token) use ($mapping) {
            return isset($mapping[$token]) ? $mapping[$token] : $token;
        }, $tokens);
    }

    /**
     * Generate signature for API requests
     */
    private function generate_signature($data) {
        // Create a copy of the data to avoid modifying the original
        $data_copy = $data;
        
        // Remove sign parameter if present
        if (isset($data_copy['sign'])) {
            unset($data_copy['sign']);
        }
        
        // Sort parameters by dictionary order
        ksort($data_copy);
        
        // Build signature string
        $sign_str = '';
        foreach ($data_copy as $key => $value) {
            if ($value !== '' && $value !== null) {
                // Convert value to string if not already
                if (!is_string($value)) {
                    if (is_bool($value)) {
                        $value = $value ? 'true' : 'false';
                    } elseif (is_array($value)) {
                        $value = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
                    } else {
                        // For numeric values, convert directly to string
                        $value = (string)$value;
                    }
                }
                // Ensure no extra spaces or formatting issues
                $value = trim($value);
                
                // For URL parameters, ensure they are not escaped
                $url_params = array('success_url', 'cancel_url', 'callback_url');
                if (in_array($key, $url_params)) {
                    // Use the original URL value without any escaping
                    // This ensures the signature matches what Cregis expects
                }
                
                // Append key and value to signature string
                $sign_str .= $key . $value;
            }
        }
        
        // Prepend secret key
        $sign_str = $this->secret_key . $sign_str;
        
        // Log the signature string for debugging
        $this->logger->debug(sprintf('Signature string: %s', $sign_str));
        
        // Generate MD5 hash and return lowercase
        $signature = strtolower(md5($sign_str));
        
        // Log the generated signature
        $this->logger->debug(sprintf('Generated signature: %s', $signature));
        
        return $signature;
    }
    
    /**
     * Make API request
     */
    private function request($endpoint, $data, $method = 'POST') {
        $url = $this->api_base_url . $endpoint;
        
        $timestamp = round(microtime(true) * 1000); // 毫秒级时间戳
        $nonce = substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'), 0, 6); // 6位随机字符串
        
        // Create a clean copy of data to avoid modifying the original
        $clean_data = array();
        foreach ($data as $key => $value) {
            if ($value !== '' && $value !== null) {
                // Ensure proper formatting for all values
                if ($key === 'tokens' && is_array($value)) {
                    // Normalize token names to API expected format
                    $clean_data[$key] = json_encode($this->normalize_tokens($value), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
                } elseif (is_array($value)) {
                    $clean_data[$key] = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
                } elseif (is_int($value)) {
                    // Keep integers as integers (not cast to string)
                    $clean_data[$key] = $value;
                } elseif (!is_string($value)) {
                    $clean_data[$key] = (string)$value;
                } else {
                    // Trim any extra spaces
                    $clean_data[$key] = trim($value);
                }
            }
        }
        
        $request_data = array_merge($clean_data, array(
            'pid' => $this->project_id,
            'timestamp' => $timestamp,
            'nonce' => $nonce
        ));
        
        // Generate signature using the original URL values (without JSON encoding)
        $sign = $this->generate_signature($request_data);
        $request_data['sign'] = $sign;
        
        // Create the JSON body with JSON_UNESCAPED_SLASHES to prevent URL escaping
        $json_body = json_encode($request_data, JSON_UNESCAPED_SLASHES);
        
        $args = array(
            'method' => $method,
            'headers' => array(
                'Content-Type' => 'application/json',
                'X-Cregis-Project-Id' => $this->project_id
            ),
            'body' => $json_body,
            'timeout' => 30
        );
        
        // Log API request with full details
        $this->logger->debug(sprintf('API Request URL: %s', $url));
        $this->logger->debug(sprintf('API Request Method: %s', $method));
        $this->logger->debug(sprintf('API Request Headers: %s', json_encode($args['headers'])));
        $this->logger->debug(sprintf('API Request Data (including public params): %s', $json_body));
        $this->logger->log_api_request($endpoint, $data);
        
        $response = wp_remote_request($url, $args);
        
        if (is_wp_error($response)) {
            $error_message = $response->get_error_message();
            $this->logger->error(sprintf('API request failed: %s', $error_message));
            $this->logger->error(sprintf('Failed URL: %s', $url));
            $this->logger->error(sprintf('Failed Request Data: %s', json_encode($data)));
            throw new Exception($error_message);
        }
        
        $body = wp_remote_retrieve_body($response);
        $result = json_decode($body, true);
        
        // Log API response
        $this->logger->log_api_response($endpoint, $result);
        
        if (isset($result['code']) && $result['code'] !== '00000') {
            $error_code = isset($result['code']) ? $result['code'] : 'unknown';
            
            // Try different possible field names for error message
            $error_message = '';
            if (isset($result['message']) && !empty($result['message'])) {
                $error_message = $result['message'];
            } elseif (isset($result['msg']) && !empty($result['msg'])) {
                $error_message = $result['msg'];
            } elseif (isset($result['error']) && !empty($result['error'])) {
                $error_message = $result['error'];
            } elseif (isset($result['error_message']) && !empty($result['error_message'])) {
                $error_message = $result['error_message'];
            } else {
                // If no message field found, use the full response as error message
                $error_message = json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            }
            
            $detailed_message = sprintf('API error (code: %s): %s', $error_code, $error_message);
            $this->logger->error($detailed_message);
            $this->logger->error(sprintf('Error URL: %s', $url));
            $this->logger->error(sprintf('Error Request Data: %s', $json_body));
            $this->logger->error(sprintf('Error Response: %s', json_encode($result)));
            // Throw just the raw API message for display to user
            throw new Exception($error_message);
        }
        
        return $result;
    }
    
    /**
     * Create checkout order
     */
    public function create_checkout($order_data) {
        return $this->request('/api/v2/checkout', $order_data);
    }
    
    /**
     * Get order info
     */
    public function get_order_info($order_id) {
        return $this->request('/api/v2/order/info', array('order_id' => $order_id));
    }
    
    /**
     * Get supported order currencies
     */
    public function get_order_currencies() {
        return $this->request('/api/v2/order/currency', array());
    }
    
    /**
     * Get supported payment currencies
     */
    public function get_pay_currencies() {
        return $this->request('/api/v2/pay/currency', array());
    }
    
    /**
     * Verify webhook signature
     */
    public function verify_webhook_signature($data, $signature) {
        // Create a copy of the data
        $data_copy = $data;
        
        // Remove sign parameter if present
        if (isset($data_copy['sign'])) {
            unset($data_copy['sign']);
        }
        
        // Sort parameters by dictionary order
        ksort($data_copy);
        
        // Build signature string
        $sign_str = '';
        foreach ($data_copy as $key => $value) {
            if ($value !== '' && $value !== null) {
                // For data field, use it as-is since Cregis sends it as JSON string
                if ($key === 'data') {
                    // Ensure data is a string
                    if (!is_string($value)) {
                        if (is_array($value)) {
                            $value = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
                        } else {
                            $value = (string)$value;
                        }
                    }
                } else {
                    // Convert value to string if not already
                    if (!is_string($value)) {
                        if (is_bool($value)) {
                            $value = $value ? 'true' : 'false';
                        } elseif (is_array($value)) {
                            $value = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
                        } else {
                            // For numeric values, convert directly to string
                            $value = (string)$value;
                        }
                    }
                }
                // Do not remove spaces as requested - spaces do not affect signature
                
                $sign_str .= $key . $value;
            }
        }
        
        // Prepend secret key
        $sign_str = $this->secret_key . $sign_str;
        
        // Log the signature string for debugging
        $this->logger->debug(sprintf('Webhook signature string: %s', $sign_str));
        $this->logger->debug(sprintf('Expected signature: %s', strtolower(md5($sign_str))));
        $this->logger->debug(sprintf('Received signature: %s', $signature));
        
        // Generate MD5 hash and return lowercase
        $expected_sign = strtolower(md5($sign_str));
        return $signature === $expected_sign;
    }
}
