<?php

namespace AIPT\Core;

/**
 * Merkezi Hata İşleme Sınıfı
 * 
 * API hatalarını ve genel hataları tutarlı bir şekilde biçimlendirmek için kullanılır.
 * 
 * @package AIPT\Core
 * @since 1.0.0
 */
class ErrorHandler {

    /**
     * Maximum retry-after hint (seconds) to cap malicious or erroneous large delays.
     * 3600 = 1 hour; any provider-supplied value above this is clamped to this ceiling.
     */
    const MAX_RETRY_SECONDS = 3600;

    /**
     * Sanitize error message for safe display in UI
     * 
     * Removes HTML tags and decodes HTML entities to prevent:
     * 1. Literal HTML tags appearing in toast notifications (e.g., &lt;p&gt;)
     * 2. Double-encoded entities (e.g., &amp;amp;)
     * 
     * @param string $message Raw error message (may contain HTML or entities)
     * @return string Clean plain-text error message
     */
    public static function sanitize_error_for_display($message) {
        if (empty($message) || !is_string($message)) {
            return '';
        }
        

        $message = html_entity_decode($message, ENT_QUOTES | ENT_HTML5, 'UTF-8');

        $message = html_entity_decode($message, ENT_QUOTES | ENT_HTML5, 'UTF-8');
        

        $message = wp_strip_all_tags($message);
        

        $message = preg_replace('/[^\S\r\n]+/', ' ', $message);
        $message = trim($message, " \t");
        
        return $message;
    }
    
    /**
     * API hata mesajını biçimlendirir ve hata kaynağını vurgular
     * 
     * @param string $provider API sağlayıcı adı (OpenAI, Gemini)
     * @param string $error_message Hata mesajı
     * @return string Biçimlendirilmiş hata mesajı
     */
    public static function format_api_error_message($provider, $error_message) {

        $provider_uppercase = strtoupper($provider);
        if (strpos($error_message, $provider_uppercase . ' API ERROR') === 0) {
            return $error_message;
        }
        

        

        $error_code = '';
        if (preg_match('/API Error \((\d+)\):/', $error_message, $matches)) {
            $error_code = $matches[1];

            $error_message = preg_replace('/API Error \(\d+\):/', '', $error_message);
        }
        

        $formatted_message = $error_code 
            ? $provider_uppercase . " API ERROR ({$error_code}):" . $error_message
            : $provider_uppercase . " API ERROR:" . $error_message;
            
        return $formatted_message;
    }
    
    /**
     * URL'leri içeren hata mesajlarını daha okunabilir hale getirir
     * 
     * @param string $error_message Orijinal hata mesajı
     * @return string URL'leri belirgin hale getirilmiş hata mesajı
     */
    public static function make_urls_readable($error_message) {

        if (preg_match('/(https?:\/\/[^\s"]+)/', $error_message, $matches)) {
            $url = $matches[1];
            $error_message = str_replace($url, "\n{$url}\n", $error_message);
        }
        

        $error_message = preg_replace('/(Status:)/', "\n$1", $error_message);
        $error_message = preg_replace('/(Type:)/', "\n$1", $error_message);
        
        return $error_message;
    }
    
    /**
     * API hata yanıtlarını analiz eder ve biçimlendirilmiş hata mesajı döndürür
     * 
     * @param mixed $error_data API'den dönen hata verisi (array, string veya başka bir tür olabilir)
     * @param string $provider API sağlayıcı adı (OpenAI, Gemini)
     * @param int $response_code HTTP yanıt kodu
     * @return string Biçimlendirilmiş hata mesajı
     */
    public static function format_api_response_error($error_data, $provider, $response_code = 0) {
        $error_code = $response_code ?: 'Unknown';
        $error_message = 'Unknown API error';
        $error_status = '';
        $error_type = '';
        $error_param = '';
        $error_reason = '';
        $upstream_provider = '';

        if (is_array($error_data)) {
            if (isset($error_data['code'])) {
                $error_code = $error_data['code'];
            } elseif (isset($error_data['error_code'])) {
                $error_code = $error_data['error_code'];
            }

            if (isset($error_data['message'])) {
                $error_message = $error_data['message'];
            } elseif (isset($error_data['error_message'])) {
                $error_message = $error_data['error_message'];
            } elseif (isset($error_data['details'])) {
                $error_message = $error_data['details'];
            } elseif (isset($error_data['error'])) {
                if (is_array($error_data['error'])) {
                    if (isset($error_data['error']['code'])) {
                        $error_code = $error_data['error']['code'];
                    }
                    if (isset($error_data['error']['message'])) {
                        $error_message = $error_data['error']['message'];
                    }
                    if (isset($error_data['error']['type']) && is_string($error_data['error']['type'])) {
                        $error_type = $error_data['error']['type'];
                    }
                    if (isset($error_data['error']['status']) && is_string($error_data['error']['status'])) {
                        $error_status = $error_data['error']['status'];
                    }
                    if (isset($error_data['error']['param']) && is_string($error_data['error']['param'])) {
                        $error_param = $error_data['error']['param'];
                    }
                } else {
                    $error_message = $error_data['error'];
                }
            }

            if (empty($error_status) && isset($error_data['status'])) {
                $error_status = $error_data['status'];
            }

            if (empty($error_type) && isset($error_data['type'])) {
                $error_type = $error_data['type'];
            }

            if (empty($error_param) && isset($error_data['param']) && is_string($error_data['param'])) {
                $error_param = $error_data['param'];
            }

            if (isset($error_data['errors']) && is_array($error_data['errors'])) {
                $first_error = reset($error_data['errors']);
                if (is_array($first_error) && isset($first_error['reason']) && is_string($first_error['reason'])) {
                    $error_reason = $first_error['reason'];
                }
            }

            if (isset($error_data['metadata']) && is_array($error_data['metadata'])) {
                if (isset($error_data['metadata']['provider_name']) && is_string($error_data['metadata']['provider_name'])) {
                    $upstream_provider = $error_data['metadata']['provider_name'];
                }
                if (isset($error_data['metadata']['raw']['error']) && is_array($error_data['metadata']['raw']['error'])) {
                    $upstream = $error_data['metadata']['raw']['error'];
                    if (isset($upstream['message']) && is_string($upstream['message']) && $upstream['message'] !== '') {
                        $error_message = $upstream['message'];
                    }
                    if (empty($error_type) && isset($upstream['type']) && is_string($upstream['type'])) {
                        $error_type = $upstream['type'];
                    }
                }
            }
        } elseif (is_string($error_data)) {
            $error_message = $error_data;
        } else {
            $error_message = "Error data: " . wp_json_encode($error_data);
        }

        if (!empty($error_param) && is_string($error_message) && stripos($error_message, $error_param) === false) {
            $error_message = "Invalid parameter '{$error_param}': {$error_message}";
        }

        $error_message = self::condense_api_error_message($error_message, $error_code, $error_status);

        $detailed_error = strtoupper($provider) . " API ERROR";
        if ($error_code && $error_code !== 'Unknown') {
            $detailed_error .= " ({$error_code})";
        }
        $detailed_error .= ": {$error_message}";

        if (!empty($upstream_provider)) {
            $detailed_error .= " [via {$upstream_provider}]";
        }

        if (!empty($error_status) && !self::is_status_redundant($error_status, $error_code)) {
            $detailed_error .= "\nStatus: {$error_status}";
        }

        if (!empty($error_type)) {
            $detailed_error .= "\nType: {$error_type}";
        }

        if (!empty($error_reason) && stripos($detailed_error, $error_reason) === false) {
            $detailed_error .= "\nReason: {$error_reason}";
        }

        $detailed_error = self::make_urls_readable($detailed_error);
        $detailed_error = self::sanitize_error_for_display($detailed_error);

        return $detailed_error;
    }

    /**
     * Extract a retry-after hint (in seconds) from an API error response body.
     *
     * Looks for explicit `retry_after` fields (chatbot, providers) first, then
     * falls back to parsing the message text (e.g. "Please retry in 17s").
     *
     * @param mixed $error_data Error data from provider (array/string).
     * @return int|null Seconds to retry, or null when no hint is present.
     */
    public static function extract_retry_after($error_data): ?int {
        if (is_array($error_data)) {
            if (isset($error_data['retry_after']) && is_numeric($error_data['retry_after']) && $error_data['retry_after'] > 0) {
                return min(self::MAX_RETRY_SECONDS, max(1, (int) round((float) $error_data['retry_after'])));
            }
            if (isset($error_data['error']['retry_after']) && is_numeric($error_data['error']['retry_after']) && $error_data['error']['retry_after'] > 0) {
                return min(self::MAX_RETRY_SECONDS, max(1, (int) round((float) $error_data['error']['retry_after'])));
            }
            if (isset($error_data['data']['retry_after']) && is_numeric($error_data['data']['retry_after']) && $error_data['data']['retry_after'] > 0) {
                return min(self::MAX_RETRY_SECONDS, max(1, (int) round((float) $error_data['data']['retry_after'])));
            }

            $message = '';
            if (isset($error_data['message']) && is_string($error_data['message'])) {
                $message = $error_data['message'];
            } elseif (isset($error_data['error']['message']) && is_string($error_data['error']['message'])) {
                $message = $error_data['error']['message'];
            }

            if ($message !== '' && preg_match('/retry\s+(?:in|after)\s+([\d.]+)\s*s/i', $message, $m)) {
                $seconds = (float) $m[1];
                if ($seconds > 0) {
                    return min(self::MAX_RETRY_SECONDS, max(1, (int) round($seconds)));
                }
            }
        } elseif (is_string($error_data) && preg_match('/retry\s+(?:in|after)\s+([\d.]+)\s*s/i', $error_data, $m)) {
            $seconds = (float) $m[1];
            if ($seconds > 0) {
                return min(self::MAX_RETRY_SECONDS, max(1, (int) round($seconds)));
            }
        }

        return null;
    }
    
    /**
     * Condenses verbose API error messages into concise, user-friendly versions.
     * Handles rate limit errors with duplicate quota lines, multiple URLs, etc.
     *
     * @param string $message Raw error message from the provider
     * @param mixed  $error_code HTTP status code or provider error code
     * @param string $error_status Provider error status string (e.g. RESOURCE_EXHAUSTED)
     * @return string Condensed error message
     */
    private static function condense_api_error_message($message, $error_code = '', $error_status = '') {
        if (empty($message) || !is_string($message)) {
            return $message;
        }
        
        if (self::is_rate_limit_error($message, $error_code, $error_status)) {
            return self::condense_rate_limit_message($message);
        }
        
        return self::deduplicate_error_lines($message);
    }
    
    /**
     * Detects rate limit / quota exceeded errors from code, status, or message content.
     *
     * @param string $message Error message text
     * @param mixed  $error_code HTTP or provider error code
     * @param string $error_status Provider status string
     * @return bool
     */
    private static function is_rate_limit_error($message, $error_code = '', $error_status = '') {
        if ((string) $error_code === '429') {
            return true;
        }
        
        $upper_status = strtoupper((string) $error_status);
        if ($upper_status === 'RESOURCE_EXHAUSTED' || $upper_status === 'RATE_LIMIT_EXCEEDED') {
            return true;
        }
        
        $lower_msg = strtolower($message);
        return strpos($lower_msg, 'quota') !== false
            || strpos($lower_msg, 'rate limit') !== false
            || strpos($lower_msg, 'resource_exhausted') !== false
            || strpos($lower_msg, 'too many requests') !== false;
    }
    
    /**
     * Extracts the essential information from a verbose rate limit message:
     * core reason, retry time, and a single reference URL.
     *
     * @param string $message Verbose rate limit error message
     * @return string Condensed message
     */
    private static function condense_rate_limit_message($message) {

        $core = '';
        if (preg_match('/^(.+?)(?:\.\s*(?:For more|To monitor|Please check|\*)|\s*\*\s*Quota|\s*https?:\/\/)/is', $message, $m)) {
            $core = trim($m[1]);
            $core = rtrim($core, " ,;:");
        } elseif (preg_match('/^(.+?)(?:\.\s*\n|\n\n)/s', $message, $m)) {
            $core = trim($m[1]);
        } else {

            $core = rtrim(trim($message), " .,;:");
        }
        if (empty($core)) {
            $core = 'API rate limit exceeded';
        }
        

        $retry = '';
        if (preg_match('/retry\s+in\s+([\d.]+)\s*s/i', $message, $m)) {
            $seconds = (float) $m[1];
            $retry = $seconds < 1 ? '< 1s' : round($seconds) . 's';
        }
        

        $url = '';
        if (preg_match('/(https?:\/\/[^\s"\'<>]+)/i', $message, $m)) {
            $url = rtrim($m[1], '.,;)');
        }
        

        $result = $core;
        if ($retry) {
            $result .= ". Please retry in {$retry}";
        }
        if ($url) {
            $result .= "\nMore info: {$url}";
        }
        
        return $result;
    }
    
    /**
     * Removes duplicate lines from error messages (e.g. repeated quota metric lines).
     *
     * @param string $message Error message with potential duplicate lines
     * @return string Deduplicated message
     */
    private static function deduplicate_error_lines($message) {
        $lines = preg_split('/\r?\n/', $message);
        if (count($lines) <= 1) {
            return $message;
        }
        
        $seen = [];
        $unique = [];
        foreach ($lines as $line) {
            $normalized = trim($line);
            if ($normalized === '' || !isset($seen[$normalized])) {
                $unique[] = $line;
                if ($normalized !== '') {
                    $seen[$normalized] = true;
                }
            }
        }
        
        return implode("\n", $unique);
    }
    
    /**
     * Checks whether the error status string is redundant given the HTTP error code.
     * Avoids displaying "Status: RESOURCE_EXHAUSTED" when code is already 429.
     *
     * @param string $status Error status string
     * @param mixed  $code Error code
     * @return bool True if status is redundant
     */
    private static function is_status_redundant($status, $code) {
        $redundancy_map = [
            '429' => ['RESOURCE_EXHAUSTED', 'RATE_LIMIT_EXCEEDED', 'TOO_MANY_REQUESTS'],
            '401' => ['UNAUTHENTICATED'],
            '403' => ['PERMISSION_DENIED'],
            '404' => ['NOT_FOUND'],
            '400' => ['INVALID_ARGUMENT', 'FAILED_PRECONDITION'],
            '500' => ['INTERNAL'],
            '503' => ['UNAVAILABLE'],
        ];
        
        $code_str = (string) $code;
        $upper_status = strtoupper(trim($status));
        
        if (isset($redundancy_map[$code_str])) {
            return in_array($upper_status, $redundancy_map[$code_str], true);
        }
        
        return false;
    }
    
    /**
     * Extract HTTP status code from a formatted API error message
     *
     * Parses patterns like "GEMINI API ERROR (503): ..." to recover the original
     * HTTP status code from provider responses. Falls back to the default when no
     * status pattern is found (e.g. for non-provider errors).
     *
     * @param string $message Formatted error message from format_api_response_error()
     * @param int    $default Default status to return when extraction fails (default 500)
     * @return int HTTP status code suitable for WP_Error data array
     */
    public static function extract_http_status_from_message($message, $default = 500) {
        if (empty($message) || !is_string($message)) {
            return $default;
        }

        if (preg_match('/API ERROR \((\d{3})\)\s*:/i', $message, $matches)) {
            return (int) $matches[1];
        }

        if (preg_match('/API Error \((\d{3})\)\s*:/i', $message, $matches)) {
            return (int) $matches[1];
        }

        return $default;
    }

    /**
     * Uygun HTTP durum kodu ile bir WP_Error nesnesi döndürür
     * 
     * @param string $error_message Hata mesajı
     * @param string $error_code Hata kodu
     * @return \WP_Error
     */
    public static function create_wp_error($error_message, $error_code = 'aipt_error') {
        $status_code = 500; 
        

        if (stripos($error_message, 'Not enough credits') !== false || stripos($error_message, 'Not enough optimization requests') !== false) {
            $status_code = 403; 
        } elseif (stripos($error_message, 'Invalid') !== false) {
            $status_code = 400; 
        } elseif (stripos($error_message, 'API Error') !== false) {
            $status_code = 502; 
        }
        
        return new \WP_Error(
            $error_code,
            $error_message,
            array( 'status' => $status_code )
        );
    }
    
    /**
     * Connection/firewall block hatalarını detect eder ve kullanıcı dostu hata mesajı oluşturur
     * 
     * @param string $error_message Orijinal hata mesajı
     * @param string $provider API sağlayıcı adı
     * @return string Kullanıcı dostu hata mesajı
     */
    public static function format_connection_error($error_message, $provider) {

        $is_firewall_block = strpos($error_message, 'not valid JSON') !== false ||
                           strpos($error_message, 'firewall') !== false ||
                           strpos($error_message, 'security plugin') !== false;

        if ($is_firewall_block) {
            return sprintf(
                /* translators: %s: API provider name */
                __('%s API connection was blocked. This is usually caused by server firewall or security plugin restrictions. Please contact your hosting provider or check your security plugin settings.', 'ai-product-tools'),
                strtoupper($provider)
            );
        }

        return self::format_api_error_message($provider, $error_message);
    }

    /**
     * Hata mesajını log dosyasına kaydeder
     * 
     * @param string $message Hata mesajı
     * @param string $type Hata türü/seviyesi
     * @return void
     */
    public static function log_error($message, $type = 'error') {
		if (defined('WP_DEBUG') && \WP_DEBUG && defined('WP_DEBUG_LOG') && \WP_DEBUG_LOG) {

            $safe_message = self::sanitize_error_for_logging($message);

        }
    }

    /**
     * Sanitizes error messages to remove sensitive information before display or logging
     *
     * @param string $message Raw error message
     * @return string Sanitized error message
     */
    public static function sanitize_error_for_logging(string $message): string {

        $message = preg_replace('/([A-Za-z]:\\\\|\\\\\\\\|\/|\.\.?\/)[^\s"\'<>:*?|]+\.(php|html|js|css|json|xml|ini|log|txt)/i', '[FILE_PATH_REMOVED]', $message);
        

        $message = preg_replace('/SELECT\s+.+?\s+FROM/i', 'SELECT [QUERY_REMOVED] FROM', $message);
        $message = preg_replace('/INSERT\s+INTO\s+\S+/i', 'INSERT INTO [TABLE]', $message);
        $message = preg_replace('/UPDATE\s+\S+\s+SET/i', 'UPDATE [TABLE] SET', $message);
        $message = preg_replace('/DELETE\s+FROM\s+\S+/i', 'DELETE FROM [TABLE]', $message);
        

        $uuid_placeholder = '<<UUID_PLACEHOLDER_' . uniqid() . '>>';
        $uuids = array();
        $message = preg_replace_callback(
            '/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i',
            function ($matches) use (&$uuids, $uuid_placeholder) {
                $uuids[] = $matches[0];
                return $uuid_placeholder . (count($uuids) - 1);
            },
            $message
        );
        

        $message = preg_replace('/sk-[a-zA-Z0-9]{32,}/', '[OPENAI_KEY_REMOVED]', $message);

        $message = preg_replace('/AIza[a-zA-Z0-9_-]{32,}/', '[GOOGLE_KEY_REMOVED]', $message);

        $message = preg_replace('/AKIA[A-Z0-9]{16,}/', '[AWS_KEY_REMOVED]', $message);

        $message = preg_replace('/Bearer\s+[a-zA-Z0-9._-]{20,}/', 'Bearer [TOKEN_REMOVED]', $message);
        $message = preg_replace('/eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/', '[JWT_REMOVED]', $message);

        $message = preg_replace('/[a-f0-9]{64,}/i', '[HEX_KEY_REMOVED]', $message);
        

        foreach ($uuids as $index => $uuid) {
            $message = str_replace($uuid_placeholder . $index, $uuid, $message);
        }
        

        $message = preg_replace('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', '[IP_REMOVED]', $message);
        

        $message = preg_replace('/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/', '[EMAIL_REMOVED]', $message);
        
        return $message;
    }

    /**
     * Creates a user-safe error message (removes technical details)
     *
     * @param string $error_message Full error message
     * @return string User-safe error message
     */
    public static function get_user_safe_error(string $error_message): string {

        $error_mappings = [
            'cURL error' => __('Unable to connect to the AI service. Please try again later.', 'ai-product-tools'),
            'SSL certificate' => __('Secure connection failed. Please contact your hosting provider.', 'ai-product-tools'),
            'Connection refused' => __('AI service is temporarily unavailable. Please try again in a few minutes.', 'ai-product-tools'),
            'timeout' => __('The request took too long. Please try again with fewer products.', 'ai-product-tools'),
            'Out of memory' => __('Server resources exceeded. Please try processing fewer products at once.', 'ai-product-tools'),
            'maximum execution time' => __('Processing took too long. Please try with fewer products.', 'ai-product-tools'),
        ];

        foreach ($error_mappings as $technical => $friendly) {
            if (stripos($error_message, $technical) !== false) {
                return $friendly;
            }
        }

        if (stripos($error_message, 'API') !== false) {

            return self::sanitize_error_for_logging($error_message);
        }

        return __('An unexpected error occurred. Please try again or contact support.', 'ai-product-tools');
    }
} 
