<?php

namespace ZenoChatbot\Services;

use \WP_Error;
use \Exception;

/**
 * HTTP Client for making API requests
 *
 * @package ZenoChatbot\Services
 * @version 1.0.0
 */
class HttpClient
{
    /**
     * Default request timeout in seconds
     */
    const DEFAULT_TIMEOUT = 30;

    /**
     * Default headers for requests
     *
     * @var array
     */
    protected $defaultHeaders = [
        'Content-Type' => 'application/json',
        'Accept'       => 'application/json',
    ];

    /**
     * Request timeout
     *
     * @var int
     */
    protected $timeout;

    /**
     * Constructor
     *
     * @param array $headers Default headers to merge with requests
     * @param int $timeout Request timeout in seconds
     */
    public function __construct(array $headers = [], $timeout = self::DEFAULT_TIMEOUT)
    {
        $this->defaultHeaders = array_merge($this->defaultHeaders, $headers);
        $this->timeout = $timeout;
    }

    /**
     * Make a GET request
     *
     * @param string $url Full URL for the request
     * @param array $params Query parameters
     * @param array $headers Additional headers
     * @return array
     * @throws Exception
     */
    public function get($url, array $params = [], array $headers = [])
    {
        if (!empty($params)) {
            $separator = strpos($url, '?') === false ? '?' : '&';
            $url .= $separator . http_build_query($params);
        }

        return $this->request('GET', $url, null, $headers);
    }

    /**
     * Make a POST request
     *
     * @param string $url Full URL for the request
     * @param array|string $data Request body data
     * @param array $headers Additional headers
     * @return array
     * @throws Exception
     */
    public function post($url, $data = [], array $headers = [])
    {
        return $this->request('POST', $url, $data, $headers);
    }

    /**
     * Make a PUT request
     *
     * @param string $url Full URL for the request
     * @param array|string $data Request body data
     * @param array $headers Additional headers
     * @return array
     * @throws Exception
     */
    public function put($url, $data = [], array $headers = [])
    {
        return $this->request('PUT', $url, $data, $headers);
    }

    /**
     * Make a PATCH request
     *
     * @param string $url Full URL for the request
     * @param array|string $data Request body data
     * @param array $headers Additional headers
     * @return array
     * @throws Exception
     */
    public function patch($url, $data = [], array $headers = [])
    {
        return $this->request('PATCH', $url, $data, $headers);
    }

    /**
     * Make a DELETE request
     *
     * @param string $url Full URL for the request
     * @param array $headers Additional headers
     * @return array
     * @throws Exception
     */
    public function delete($url, array $headers = [])
    {
        return $this->request('DELETE', $url, null, $headers);
    }

    /**
     * Main request handler for all HTTP methods
     *
     * @param string $method HTTP method (GET, POST, PUT, PATCH, DELETE)
     * @param string $url Full URL for the request
     * @param array|string|null $data Request body data
     * @param array $headers Additional headers
     * @return array
     * @throws Exception
     */
    public function request($method, $url, $data = null, array $headers = [])
    {
        // Merge headers
        $headers = array_merge($this->defaultHeaders, $headers);

        // Prepare request arguments
        $args = [
            'method'      => strtoupper($method),
            'headers'     => $headers,
            'timeout'     => $this->timeout,
            'sslverify'   => true,
            'data_format' => 'body',
        ];

        // Add body data for methods that support it
        if (!in_array($method, ['GET', 'DELETE']) && $data !== null) {

            if (is_array($data) || is_object($data)) {
                $args['body'] = json_encode($data);
            } else {
                $args['body'] = $data;
            }
        }

        // Make the request
        $response = wp_remote_request($url, $args);

        // Handle the response
        return $this->handleResponse($response);
    }

    /**
     * Handle the HTTP response
     *
     * @param array|WP_Error $response
     * @return array
     * @throws Exception
     */
    protected function handleResponse($response)
    {
        // Check for WP_Error
        if (is_wp_error($response)) {
            // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Safe: values are thrown in an Exception, not output to HTML
            throw new Exception(
                'HTTP Request Failed: ' . $response->get_error_message(), // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
                $response->get_error_code() ? (int) $response->get_error_code() : 500
            );
        }

        // Get response code and body
        $statusCode = wp_remote_retrieve_response_code($response);
        $body       = wp_remote_retrieve_body($response);
        $headers    = wp_remote_retrieve_headers($response);

        // Parse JSON response if applicable
        $contentType = isset($headers['content-type']) ? $headers['content-type'] : '';
        $parsedBody  = $body;

        if (strpos($contentType, 'application/json') !== false && !empty($body)) {
            $decoded = json_decode($body, true);
            if (json_last_error() === JSON_ERROR_NONE) {
                $parsedBody = $decoded;
            }
        }

        // Check for HTTP errors
        if ($statusCode < 200 || $statusCode >= 300) {
            $errorMessage = 'HTTP Error ' . $statusCode;

            if (is_array($parsedBody)) {
                if (isset($parsedBody['error']['message'])) {
                    $errorMessage .= ': ' . $parsedBody['error']['message']; // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
                } elseif (isset($parsedBody['error'])) {
                    $errorMessage .= ': ' . (is_string($parsedBody['error']) ? $parsedBody['error'] : json_encode($parsedBody['error'])); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
                } elseif (isset($parsedBody['message'])) {
                    $errorMessage .= ': ' . $parsedBody['message']; // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
                }
            } elseif (!empty($body)) {
                $errorMessage .= ': ' . substr($body, 0, 200); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
            }

            // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Safe: Exception only
            throw new Exception($errorMessage, $statusCode);
        }

        // Return successful response
        return [
            'success'     => true,
            'status_code' => $statusCode,
            'headers'     => $headers,
            'body'        => $parsedBody,
        ];
    }


    /**
     * Create a new instance with specific configuration
     *
     * @param array $headers
     * @param int $timeout
     * @return static
     */
    public static function create(array $headers = [], $timeout = self::DEFAULT_TIMEOUT)
    {
        return new static($headers, $timeout);
    }
}
