<?php

namespace App\Platform\Tools;

use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\RetryMiddleware;
use App\Utils\Log;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Hyperf\Guzzle\CoroutineHandler;

/**
 * Tools client - send request to wpic tools
 */
class HttpClient
{
    const MAX_RETRIES = 3;

    use RequestTrait;

    static function create(): ClientInterface
    {

        $stack = HandlerStack::create(new CoroutineHandler());
        
        // 添加自动重试中间件
        $stack->push(Middleware::retry(self::retryPolicy(), self::retryDelay()));

        $config = [
            'headers' => [
                'Accept' => 'application/json; charset=utf-8',
                'Content-Type' => 'application/json; charset=utf-8',
                'Hash' => self::getHash()
            ],
            'base_uri' => self::getHost(),
            'connect_timeout' => 10,
            'timeout' => 60,
            'handler' => $stack,
            'debug' => true
        ];

        return new Client($config);
    }
    
    /**
     * 重试策略
     *
     * @return callable
     */
    public static function retryPolicy(): callable
    {
        return function (int $retries, RequestInterface $request, ResponseInterface $response = null, \Throwable $exception = null): bool {
            // 记录异常信息
            if ($exception) {
                dump($exception->getMessage());
                Log::error("重试异常: " . $exception->getMessage());
            }
            
            // 检查是否为连接异常或请求异常（包括超时）
            $should_retry = false;
            
            if ($exception) {
                // 处理连接异常（包括连接超时）
                if ($exception instanceof ConnectException) {
                    $should_retry = true;
                }
                // 处理请求异常（包括请求超时）
                elseif ($exception instanceof RequestException) {
                    // 如果是超时导致的请求异常
                    if (strpos($exception->getMessage(), 'timed out') !== false) {
                        $should_retry = true;
                    }
                    // 如果有响应，检查状态码
                    elseif ($exception->hasResponse()) {
                        $status_code = $exception->getResponse()->getStatusCode();
                        $should_retry = in_array($status_code, [429, 500, 502, 503, 504]);
                    }
                }
            }
            
            // 首先检查重试次数
            if ($retries >= self::MAX_RETRIES) {
                return false;
            }
            
            // 检查状态码 (需要先确保 $response 不为 null)
            $status_code_error = $response && in_array($response->getStatusCode(), [429, 500, 502, 503, 504]);
            
            // 如果是应该重试的异常、状态码错误或响应为 null，则重试
            return $should_retry || $status_code_error || is_null($response);
        };
    }
    
    /**
     * 重试延迟策略
     *
     * @return callable
     */
    public static function retryDelay(): callable
    {
        return function (int $retries, ResponseInterface $response = null): int {
            $delay = 0;
            
            if (is_null($response) || !$response->hasHeader('Retry-After')) {
                $delay = RetryMiddleware::exponentialDelay($retries) * 5;
            } else {
                $retryAfter = $response->getHeaderLine('Retry-After');
                $delay = $retryAfter;
                
                if (!is_numeric($retryAfter)) {
                    $delay = (new \DateTime($retryAfter))->getTimestamp() - time();
                }
                $delay = (int)$delay * 1000;
            }
            
            $seconds = $delay / 1000;
            $message = "请求失败, 此为第 $retries 次尝试，{$seconds} 秒之后重试";
            
            // 如果有响应，添加状态码信息
            if ($response) {
                $code = $response->getStatusCode();
                dump($response->getBody()->getContents());
                $message = "请求失败, 远程服务器响应码为 $code, 此为第 $retries 次尝试，{$seconds} 秒之后重试";
            }
            
            dump($message);
            Log::error($message);
            
            return $delay;
        };
    }

    static function get(string $path, array $query = []): array
    {
        try{
            $response = static::create()->request('GET', $path, ['query' => $query]);
            // dump($response);
            return static::parse($response);
        }catch(\Throwable  $exception) {
          dump($exception->getMessage());
          dump($exception->getTraceAsString());
          exit(1);
        }
    }

    static function post(string $path, array $query = [], array $data = []): array
    {
        try{
            $response = static::create()->request('POST', $path, ['query' => $query, 'body' => json_encode($data)]);
            // dump($response);
            // dump(static::parse($response));
            return static::parse($response);
        }catch(\Throwable  $exception){
          dump($exception->getMessage());
          dump($exception->getTraceAsString());
          exit(1);
        }
    }

    static function put(string $path, array $query = [], array $data = [])
    {
        try{
            $response = static::create()->request('PUT', $path, ['query' => $query, 'body' => json_encode($data)]);
            // dump($response);
            return static::parse($response);
        }catch(\Throwable  $exception){
          dump($exception->getMessage());
          dump($exception->getTraceAsString());
          exit(1);
        }
    }
    
    static function delete(string $path)
    {
        try{
            $response = static::create()->request('DELETE', $path);
            // dump($response);
            return static::parse($response);
        }catch(\Throwable  $exception){
            dump($exception->getMessage());
            dump($exception->getTraceAsString());
            exit(1);
        }
    }
    
    protected static function parse(ResponseInterface $response): array
    {

        $http_code = $response->getStatusCode();
        $body = $response->getBody();
        // dump($body->getContents());
        // when deal with stream object, need to move to pointer to the ram head
        $body->rewind();
        $content = $body->getContents();
        $payload = \json_decode($content, 1);
        if ($http_code == 200) {
            return $payload;
        }
    }

}
