<?php
/**
 * HTTP Helper using Guzzle with fallback to wp_remote_*.
 *
 * @package PrimeLink
 */

namespace PrimeLink\Helpers;

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

use PrimeLink\Helpers\LoggerHelper;

/**
 * HTTP Helper class for making HTTP requests.
 * Uses Guzzle if available, falls back to wp_remote_*.
 */
final class HttpHelper {

    /**
     * Cached Guzzle client instance (reused for better performance).
     *
     * @var object|null
     */
    private static $guzzle_client = null;

    /**
     * Guzzle Client class name (cached).
     *
     * @var string|null
     */
    private static $guzzle_client_class = null;

    /**
     * Initialize Guzzle client class detection.
     *
     * @return string|null Guzzle Client class name or null.
     */
    private static function get_guzzle_client_class() {
        if ( self::$guzzle_client_class !== null ) {
            return self::$guzzle_client_class;
        }

        // Try prefixed Guzzle first (production), then normal (development)
        if ( class_exists( '\PrimeLink\Vendor\GuzzleHttp\Client' ) ) {
            self::$guzzle_client_class = '\PrimeLink\Vendor\GuzzleHttp\Client';
        } elseif ( class_exists( '\GuzzleHttp\Client' ) ) {
            self::$guzzle_client_class = '\GuzzleHttp\Client';
        } else {
            self::$guzzle_client_class = false;
        }

        return self::$guzzle_client_class;
    }

    /**
     * Get or create Guzzle client instance (reused for better performance).
     *
     * @param array $config Client configuration.
     * @return object|null Guzzle Client instance or null.
     */
    private static function get_guzzle_client( $config = array() ) {
        $guzzle_client_class = self::get_guzzle_client_class();
        if ( ! $guzzle_client_class ) {
            return null;
        }

        // Reuse client if config is similar (timeout is the main config we care about)
        $default_timeout = isset( $config['timeout'] ) ? (float) $config['timeout'] : 30.0;
        if ( self::$guzzle_client !== null ) {
            return self::$guzzle_client;
        }

        // Create new client with default config
        $client_config = array(
            'timeout'         => $default_timeout,
            'connect_timeout' => min( 10.0, $default_timeout ), // Connect timeout should be shorter
        );

        try {
            self::$guzzle_client = new $guzzle_client_class( $client_config );
            return self::$guzzle_client;
        } catch ( \Exception $e ) {
            LoggerHelper::warning( 'Failed to create Guzzle client', array(
                'error' => $e->getMessage(),
            ) );
            return null;
        }
    }

    /**
     * Make HTTP GET request.
     *
     * @param string $url     Request URL.
     * @param array  $args    Request arguments (headers, timeout, etc.).
     * @return array Response array with 'success', 'body', 'code', 'headers', 'error'.
     */
    public static function get( $url, $args = array() ) {
        return self::request( $url, 'GET', $args );
    }

    /**
     * Make HTTP POST request.
     *
     * @param string $url     Request URL.
     * @param array  $args    Request arguments.
     * @return array Response array.
     */
    public static function post( $url, $args = array() ) {
        return self::request( $url, 'POST', $args );
    }

    /**
     * Make HTTP PUT request.
     *
     * @param string $url     Request URL.
     * @param array  $args    Request arguments.
     * @return array Response array.
     */
    public static function put( $url, $args = array() ) {
        return self::request( $url, 'PUT', $args );
    }

    /**
     * Make HTTP request (GET, POST, PUT, PATCH, DELETE).
     *
     * @param string $url     Request URL.
     * @param string $method  HTTP method.
     * @param array  $args    Request arguments.
     * @return array Response array.
     */
    public static function request( $url, $method = 'GET', $args = array() ) {
        $defaults = array(
            'headers'        => array(),
            'body'           => null,
            'timeout'        => 30,
            'connect_timeout' => 10, // Separate connect timeout
            'retry'          => 0, // Number of retries on failure
            'retry_delay'    => 1, // Delay between retries in seconds
            'verify'         => true, // SSL verification (Guzzle only)
        );
        $args = wp_parse_args( $args, $defaults );

        // Try Guzzle first
        $guzzle_client_class = self::get_guzzle_client_class();
        if ( $guzzle_client_class ) {
            return self::request_with_guzzle( $url, $method, $args, $guzzle_client_class );
        }

        // Fallback to wp_remote_*
        return self::request_with_wp_remote( $url, $method, $args );
    }

    /**
     * Make request using Guzzle.
     *
     * @param string $url                Request URL.
     * @param string $method             HTTP method.
     * @param array  $args               Request arguments.
     * @param string $guzzle_client_class Guzzle Client class name.
     * @return array Response array.
     */
    private static function request_with_guzzle( $url, $method, $args, $guzzle_client_class ) {
        try {
            // Get or create Guzzle client (reused for better performance)
            $client = self::get_guzzle_client( array(
                'timeout' => $args['timeout'],
            ) );

            if ( ! $client ) {
                // Fallback to wp_remote_* if client creation failed
                return self::request_with_wp_remote( $url, $method, $args );
            }

            // Build request options
            $request_options = array(
                'headers' => $args['headers'],
            );

            // Add timeout per request (overrides client default if needed)
            if ( isset( $args['timeout'] ) ) {
                $request_options['timeout'] = (float) $args['timeout'];
            }
            if ( isset( $args['connect_timeout'] ) ) {
                $request_options['connect_timeout'] = (float) $args['connect_timeout'];
            }

            // SSL verification - auto-disable for local environments
            $ssl_verify = isset( $args['verify'] ) ? (bool) $args['verify'] : true;
            if ( $ssl_verify && self::is_local_environment() ) {
                $ssl_verify = false;
            }
            $request_options['verify'] = $ssl_verify;

            // Body handling
            if ( ! empty( $args['body'] ) ) {
                if ( is_array( $args['body'] ) || is_object( $args['body'] ) ) {
                    $request_options['json'] = $args['body'];
                } else {
                    $request_options['body'] = $args['body'];
                }
            }

            $retry_count = 0;
            $max_retries = max( 0, (int) $args['retry'] );

            while ( $retry_count <= $max_retries ) {
                try {
                    $response = $client->request( $method, $url, $request_options );

                    $body = (string) $response->getBody();
                    $code = $response->getStatusCode();
                    $headers = array();
                    
                    foreach ( $response->getHeaders() as $name => $values ) {
                        $headers[ $name ] = is_array( $values ) ? implode( ', ', $values ) : $values;
                    }

                    return array(
                        'success' => true,
                        'body'    => $body,
                        'code'    => $code,
                        'headers' => $headers,
                        'error'   => null,
                    );
                } catch ( \Exception $e ) {
                    // Check for SSL/certificate errors and retry with SSL verification disabled
                    $error_message = $e->getMessage();
                    $is_ssl_error = (
                        strpos( strtolower( $error_message ), 'certificate' ) !== false ||
                        strpos( strtolower( $error_message ), 'ssl' ) !== false ||
                        strpos( strtolower( $error_message ), 'curl error 77' ) !== false ||
                        strpos( strtolower( $error_message ), 'curl error 60' ) !== false
                    );
                    
                    // If SSL error and we haven't disabled verification yet, retry once with SSL disabled
                    if ( $is_ssl_error && $request_options['verify'] && $retry_count === 0 ) {
                        $request_options['verify'] = false;
                        $retry_count++; // Don't count this as a normal retry
                        continue; // Retry immediately
                    }
                    
                    // Check if this is a retryable error
                    $is_retryable = self::is_retryable_error( $e, $guzzle_client_class );
                    
                    // If this is the last retry or error is not retryable, return error
                    if ( $retry_count >= $max_retries || ! $is_retryable ) {
                        $error_message = self::get_error_message( $e, $guzzle_client_class );
                        $error_code = self::get_error_code( $e, $guzzle_client_class );
                        $error_body = self::get_error_body( $e, $guzzle_client_class );
                        
                        return array(
                            'success' => false,
                            'body'    => $error_body, // Include body for 4xx/5xx responses (contains useful error messages)
                            'code'    => $error_code,
                            'headers' => array(),
                            'error'   => $error_message,
                        );
                    }

                    // Wait before retrying
                    if ( $args['retry_delay'] > 0 ) {
                        sleep( (int) $args['retry_delay'] );
                    }
                    $retry_count++;
                }
            }
        } catch ( \Exception $e ) {
            // Fallback to wp_remote_* on Guzzle error
            LoggerHelper::warning( 'Guzzle request failed, falling back to wp_remote_*', array(
                'url'    => $url,
                'method' => $method,
                'error'  => $e->getMessage(),
            ) );
            return self::request_with_wp_remote( $url, $method, $args );
        }
    }

    /**
     * Check if an error is retryable.
     *
     * @param \Exception $e                  Exception.
     * @param string     $guzzle_client_class Guzzle Client class name.
     * @return bool True if error is retryable.
     */
    private static function is_retryable_error( $e, $guzzle_client_class ) {
        // Check for Guzzle-specific exception types
        $connect_exception_class = str_replace( 'Client', 'Exception\ConnectException', $guzzle_client_class );
        $server_exception_class = str_replace( 'Client', 'Exception\ServerException', $guzzle_client_class );
        $timeout_exception_class = str_replace( 'Client', 'Exception\RequestException', $guzzle_client_class );

        // Retry on connection errors, server errors (5xx), and timeouts
        if ( class_exists( $connect_exception_class ) && $e instanceof $connect_exception_class ) {
            return true;
        }
        if ( class_exists( $server_exception_class ) && $e instanceof $server_exception_class ) {
            return true;
        }
        if ( class_exists( $timeout_exception_class ) && $e instanceof $timeout_exception_class ) {
            // Check if it's a timeout error
            $message = strtolower( $e->getMessage() );
            if ( strpos( $message, 'timeout' ) !== false || strpos( $message, 'timed out' ) !== false ) {
                return true;
            }
        }

        // Default: don't retry on client errors (4xx)
        return false;
    }

    /**
     * Get user-friendly error message from exception.
     *
     * @param \Exception $e                  Exception.
     * @param string     $guzzle_client_class Guzzle Client class name.
     * @return string Error message.
     */
    private static function get_error_message( $e, $guzzle_client_class ) {
        $message = $e->getMessage();

        // Check for specific Guzzle exception types for better error messages
        $client_exception_class = str_replace( 'Client', 'Exception\ClientException', $guzzle_client_class );
        $server_exception_class = str_replace( 'Client', 'Exception\ServerException', $guzzle_client_class );
        $connect_exception_class = str_replace( 'Client', 'Exception\ConnectException', $guzzle_client_class );

        if ( class_exists( $client_exception_class ) && $e instanceof $client_exception_class ) {
            return sprintf( __( 'Client error: %s', 'primelink' ), $message );
        }
        if ( class_exists( $server_exception_class ) && $e instanceof $server_exception_class ) {
            return sprintf( __( 'Server error: %s', 'primelink' ), $message );
        }
        if ( class_exists( $connect_exception_class ) && $e instanceof $connect_exception_class ) {
            return sprintf( __( 'Connection error: %s', 'primelink' ), $message );
        }

        return $message;
    }

    /**
     * Get HTTP status code from exception (if available).
     *
     * @param \Exception $e                  Exception.
     * @param string     $guzzle_client_class Guzzle Client class name.
     * @return int HTTP status code or 0.
     */
    private static function get_error_code( $e, $guzzle_client_class ) {
        // Try to get status code from Guzzle response exception
        $response_exception_class = str_replace( 'Client', 'Exception\BadResponseException', $guzzle_client_class );
        if ( class_exists( $response_exception_class ) && $e instanceof $response_exception_class ) {
            if ( method_exists( $e, 'getResponse' ) ) {
                $response = $e->getResponse();
                if ( $response && method_exists( $response, 'getStatusCode' ) ) {
                    return $response->getStatusCode();
                }
            }
        }
        return 0;
    }

    /**
     * Get response body from exception (for 4xx/5xx errors).
     *
     * @param \Exception $e                  Exception.
     * @param string     $guzzle_client_class Guzzle Client class name.
     * @return string Response body or empty string.
     */
    private static function get_error_body( $e, $guzzle_client_class ) {
        // Try to get response body from Guzzle response exception
        $response_exception_class = str_replace( 'Client', 'Exception\BadResponseException', $guzzle_client_class );
        if ( class_exists( $response_exception_class ) && $e instanceof $response_exception_class ) {
            if ( method_exists( $e, 'getResponse' ) ) {
                $response = $e->getResponse();
                if ( $response && method_exists( $response, 'getBody' ) ) {
                    $body = $response->getBody();
                    if ( $body && method_exists( $body, 'getContents' ) ) {
                        return $body->getContents();
                    } elseif ( is_string( $body ) ) {
                        return $body;
                    }
                }
            }
        }
        return '';
    }

    /**
     * Make request using wp_remote_* (fallback).
     *
     * @param string $url    Request URL.
     * @param string $method HTTP method.
     * @param array  $args   Request arguments.
     * @return array Response array.
     */
    private static function request_with_wp_remote( $url, $method, $args ) {
        $wp_args = array(
            'method'  => strtoupper( $method ),
            'headers' => $args['headers'],
            'timeout' => (int) $args['timeout'],
        );

        // SSL verification - check if we should disable it
        $ssl_verify = isset( $args['verify'] ) ? (bool) $args['verify'] : true;
        
        // Auto-detect local environment and disable SSL verification if needed
        if ( $ssl_verify && self::is_local_environment() ) {
            $ssl_verify = false;
        }
        
        $wp_args['sslverify'] = $ssl_verify;

        if ( ! empty( $args['body'] ) ) {
            if ( is_array( $args['body'] ) || is_object( $args['body'] ) ) {
                $wp_args['body'] = wp_json_encode( $args['body'] );
                if ( ! isset( $wp_args['headers']['Content-Type'] ) ) {
                    $wp_args['headers']['Content-Type'] = 'application/json';
                }
            } else {
                $wp_args['body'] = $args['body'];
            }
        }

        $response = wp_remote_request( $url, $wp_args );

        // If SSL error, retry with SSL verification disabled
        if ( is_wp_error( $response ) ) {
            $error_message = $response->get_error_message();
            $error_code = $response->get_error_code();
            
            // Check for SSL/certificate errors
            if ( $ssl_verify && (
                strpos( strtolower( $error_message ), 'certificate' ) !== false ||
                strpos( strtolower( $error_message ), 'ssl' ) !== false ||
                strpos( strtolower( $error_message ), 'curl error 77' ) !== false ||
                strpos( strtolower( $error_message ), 'curl error 60' ) !== false ||
                $error_code === 'http_request_failed'
            ) ) {
                // Retry with SSL verification disabled
                $wp_args['sslverify'] = false;
                $response = wp_remote_request( $url, $wp_args );
                
                // If still error, return it
                if ( is_wp_error( $response ) ) {
                    return array(
                        'success' => false,
                        'body'    => '',
                        'code'    => 0,
                        'headers' => array(),
                        'error'   => $response->get_error_message(),
                    );
                }
            } else {
                // Return original error if not SSL-related
                return array(
                    'success' => false,
                    'body'    => '',
                    'code'    => 0,
                    'headers' => array(),
                    'error'   => $error_message,
                );
            }
        }

        $body    = wp_remote_retrieve_body( $response );
        $code    = wp_remote_retrieve_response_code( $response );
        $headers = wp_remote_retrieve_headers( $response );

        return array(
            'success' => true,
            'body'    => $body,
            'code'    => $code,
            'headers' => is_array( $headers ) ? $headers : array(),
            'error'   => null,
        );
    }

    /**
     * Check if we're in a local development environment.
     *
     * @return bool True if local environment detected.
     */
    private static function is_local_environment() {
        // Check for common local development indicators
        $site_url = get_site_url();
        $home_url = get_home_url();
        
        // Check if URL contains localhost or 127.0.0.1
        if ( strpos( $site_url, 'localhost' ) !== false ||
             strpos( $site_url, '127.0.0.1' ) !== false ||
             strpos( $home_url, 'localhost' ) !== false ||
             strpos( $home_url, '127.0.0.1' ) !== false ) {
            return true;
        }
        
        // Check for Laragon, XAMPP, MAMP, etc. in server software
        if ( isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
            $server_software = strtolower( $_SERVER['SERVER_SOFTWARE'] );
            if ( strpos( $server_software, 'laragon' ) !== false ||
                 strpos( $server_software, 'xampp' ) !== false ||
                 strpos( $server_software, 'mamp' ) !== false ||
                 strpos( $server_software, 'wamp' ) !== false ) {
                return true;
            }
        }
        
        // Check if WP_DEBUG is enabled (common in local environments)
        if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
            // Only disable SSL if we're definitely local (not production with debug on)
            if ( strpos( $site_url, '.local' ) !== false ||
                 strpos( $site_url, '.test' ) !== false ||
                 strpos( $site_url, '.dev' ) !== false ) {
                return true;
            }
        }
        
        return false;
    }
}

