<?php
namespace Api\PhpUtils\Http;

use Api\PhpUtils\Log\FileLog;
use Api\PhpUtils\Mon\MonUtil;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\TransferException;
use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\TransferStats;

class Request
{
    private $client = null;
    //最大的并发数量
    private $concurrency_num = 5;

    private $success_code = 0;
    private $fail_code = 999999;

    private $result = [];
    protected $con_result = [];

    private $log = [
        'url' => [],
        'params' => [],
        'response' => []
    ];

    public function __construct()
    {
    }

    /**
     * @param string $url 请求url
     * @param array $params 请求参数
     * @param int $timeout 自定义超时毫秒数
     * @param array $headers 自定义headers
     * @param int $retry 重试次数（默认不重试）
     * @param bool $use_mon 是否使用监控打点（默认是）
     * @param string $proxy 代理（比如http://192.11.222.124:8000）
     * @return array
     * @throws
     */
    public function get($url, $params = [], $timeout = 0, $headers = [], $retry = 0, $use_mon = true, $proxy = '')
    {
        try {
            //验参
            if (empty($url) || !is_array($params)) {
                throw new \Exception('params error');
            }
            $this->log['url'] = $url;
            $this->log['params'] = $params;
            //构造client
            $config = [];
            if (!empty($retry) && is_int($retry)) {
                $config['retry'] = $retry;
            }
            $this->client = (new Base())->initClient($config, $url, $params);
            //构造配置
            if (!empty($params)) {
                $options['query'] = $params;
            }
            if (!empty($timeout)) {
                $options['timeout'] = $timeout / 1000;
            }
            if (!empty($headers)) {
                $options['headers'] = $headers;
            }
            if (!empty($proxy)) {
                $options['proxy'] = $proxy;
            }
            $options['on_stats'] = function (TransferStats $stats) use ($use_mon, $url) {
                $this->result['http_code'] = $stats->getHandlerStat('http_code');
                if (!empty($use_mon)) {
                    //MonUtil::proxyMon($url, $stats->getHandlerStat('http_code'), round($stats->getHandlerStat('total_time'),4) * 1000);
                }
            };
            //异步get请求
            $promise = $this->client->getAsync($url, $options);
            $promise->then(
                function (Response $resp) {
                    $ret = $this->log['response'] = $resp->getBody()->getContents();
                    $ret = empty(json_decode($ret, true)) ? $ret : json_decode($ret, true);
                    $this->result['code'] = $this->success_code;
                    $this->result['response'] = $ret;
                },
                function (RequestException $e) {
                    $this->log['response'] = $e->getMessage();
                    throw new \Exception($e->getMessage());
                }
            );
            $promise->wait();
        } catch (\Exception $e) {
            $this->result['code'] = $this->fail_code;
            $this->result['msg'] = $e->getMessage();
        } finally {
            $this->addLog('get');
            return $this->result;
        }
    }
    /**
     * @param $url
     * @param array $params 请求参数
     * @param int $timeout 自定义超时毫秒数
     * @param string $content_type 默认form_params|json|multipart（文件上传）
     * @param array $headers 自定义headers
     * @param int $retry 重试次数
     * @param bool $use_mon 是否使用监控打点（默认是）
     * @param string $proxy 代理（比如http://192.11.222.124:8000）
     * @return array
     * @throws
     */
    public function post($url, $params = [], $timeout = 0, $content_type = '', $headers = [], $retry = 0, $use_mon = true, $proxy = '')
    {
        try {
            //验参
            if (empty($url) || !is_array($params)) {
                throw new \Exception('params error');
            }
            $this->log['url'] = $url;
            $this->log['params'] = $params;
            //构造client
            $config = [];
            if (!empty($retry) && is_int($retry)) {
                $config['retry'] = $retry;
            }
            $this->client = (new Base())->initClient($config, $url, $params);
            //构造配置
            if (!empty($params)) {
                if ($content_type == 'json') {
                    $options['json'] = $params;
                } elseif ($content_type == 'multipart') {
                    $options['multipart'] = $params;
                } else {
                    $options['form_params'] = $params;
                }
            }
            if (!empty($timeout)) {
                $options['timeout'] = $timeout / 1000;
            }
            if (!empty($headers)) {
                $options['headers'] = $headers;
            }
            if (!empty($proxy)) {
                $options['proxy'] = $proxy;
            }
            $options['on_stats'] = function (TransferStats $stats) use ($use_mon, $url) {
                $this->result['http_code'] = $stats->getHandlerStat('http_code');
                if (!empty($use_mon)) {
                    //MonUtil::proxyMon($url, $stats->getHandlerStat('http_code'), round($stats->getHandlerStat('total_time'),4) * 1000);
                }
            };
            //异步post请求
            $promise = $this->client->postAsync($url, $options);
            $promise->then(
                function (Response $resp) {
                    $ret = $this->log['response'] = $resp->getBody()->getContents();
                    $ret = empty(json_decode($ret, true)) ? $ret : json_decode($ret, true);
                    $this->result['code'] = $this->success_code;
                    $this->result['response'] = $ret;
                },
                function (RequestException $e) {
                    $this->log['response'] = $e->getMessage();
                    throw new \Exception($e->getMessage());
                }
            );
            $promise->wait();
        } catch (\Exception $e) {
            $this->result['code'] = $this->fail_code;
            $this->result['msg'] = $e->getMessage();
        } finally {
            $this->addLog('post');
            return $this->result;
        }
    }

    /**
     * 并发执行get请求
     * @param array $urls [0=>xxx,1=>xxx]
     * @param array $params [
     *                          0=> [
     *                                  'key1' => val1,
     *                                  'key2' => val2,
     *                              ],
     *                          1=> [
     *                                  'key1' => val1,
     *                                  'key2' => val2,
     *                              ]
     *                       ]
     * @param array $timeouts [0=>xxx,1=>xxx]
     * @param int $retry 重试次数
     * @param array $use_mons [0=>false,1=>false] 默认是true
     * @param array $proxys [0=>'http://192.11.222.124:8000', 1=>'http://192.11.222.124:8000']
     * @return array
     */
    public function concurrencyGet($urls, $params = [], $timeouts = [], $retry = 0, $use_mons = [], $proxys = [])
    {
        try {
            //验参
            if (!is_array($urls) || empty($urls) || !is_array($params)) {
                throw new \Exception('params error');
            }
            //构造client
            $config = [
                'concurrent' => true
            ];
            if (!empty($retry) && is_int($retry)) {
                $config['retry'] = $retry;
            }
            $this->client = (new Base())->initClient($config, $urls, $params);
            //构造配置
            $options = [];
            foreach ($urls as $k => $url) {
                $this->log['url'][$k] = $url;
                $options[$k]['on_stats'] = function (TransferStats $stats) use ($k, $url, $use_mons) {
                    $this->con_result[$k]['http_code'] = $stats->getHandlerStat('http_code');
                    if (!isset($use_mons[$k]) || !empty($use_mons[$k])) {
                        //MonUtil::proxyMon($url, $stats->getHandlerStat('http_code'), round($stats->getHandlerStat('total_time'),4) * 1000);
                    }
                };
            }
            if (!empty($params)) {
                foreach ($params as $k => $v) {
                    $this->log['params'][$k] = $v;
                    $options[$k]['query'] = $v;
                }
            }
            if (!empty($timeouts)) {
                foreach ($timeouts as $k => $v) {
                    $options[$k]['timeout'] = $v / 1000;
                }
            }
            if (!empty($proxys)) {
                foreach ($proxys as $k => $v) {
                    $options[$k]['proxy'] = $v;
                }
            }
            //异步并发get请求
            $client = $this->client;
            $requests = function ($urls, $options) use ($client) {
                for ($i = 0; $i < count($urls); $i++) {
                    if (!isset($urls[$i]) || !isset($options[$i])) {
                        continue;
                    }
                    $each_url = $urls[$i];
                    $each_options = $options[$i];
                    yield function () use ($client, $each_url, $each_options) {
                        return $client->getAsync($each_url, $each_options);
                    };
                }
            };
            $pool = new Pool($client, $requests($urls, $options), [
                'concurrency' => $this->concurrency_num,
                'fulfilled' => function (Response $response, $index) {
                    $ret = $response->getBody()->getContents();
                    $this->log['response'][$index] = $ret;
                    $ret = empty(json_decode($ret, true)) ? $ret : json_decode($ret, true);
                    $this->con_result[$index]['code'] = $this->success_code;
                    $this->con_result[$index]['response'] = $ret;
                },
                'rejected' => function (TransferException $te, $index) {
                    $this->con_result[$index]['code'] = $this->fail_code;
                    $this->con_result[$index]['msg'] = $te->getMessage();
                    $this->log['response'][$index] = $te->getMessage();
                },
            ]);
            $pool->promise()->wait();
        } catch (\Exception $e) {
            $this->con_result[]['code'] = $this->fail_code;
            $this->con_result[]['msg'] = $e->getMessage();
            $this->log['response'][] = $e->getMessage();
        } finally {
            $this->addLog('concurrencyGet');
            return $this->con_result;
        }
    }

    /**
     * 并发执行post请求
     * @param array $urls [ 0=>'url1', 1=>'url2']
     * @param array $params [ 0=>[k1=>v1], 1=>[k2=>v2]]
     * @param array $timeouts [ 0=>0.5, 1=>2]
     * @param array $content_types  [ 0=>'json']
     * @param array $headers [
     *                          0 => [
     *                          'Content-type' => 'application/json',
     *                          'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36'
     *                          ]
     * @param int $retry 重试次数
     * @param array $use_mons [0=>false,1=>false] 默认是true
     * @param array $proxys [0=>'http://192.11.222.124:8000', 1=>'http://192.11.222.124:8000']
     * @return array
     */
    public function concurrencyPost($urls, $params = [], $timeouts = [], $content_types = [], $headers = [], $retry = 0, $use_mons = [], $proxys = [])
    {
        try {
            //验参
            if (empty($urls) || !is_array($params)) {
                throw new \Exception('params error');
            }
            //构造client
            $config = [
                'concurrent' => true
            ];
            if (!empty($retry) && is_int($retry)) {
                $config['retry'] = $retry;
            }
            $this->client = (new Base())->initClient($config, $urls, $params);
            //构造配置
            $options = [];
            foreach ($urls as $k => $url) {
                $this->log['url'][$k] = $url;
                $options[$k]['on_stats'] = function (TransferStats $stats) use ($k, $url, $use_mons) {
                    $this->con_result[$k]['http_code'] = $stats->getHandlerStat('http_code');
                    //监控打点：模块名默认为第三方服务url 指标为请求返回的HTTP_code(默认-999)与总响应时间
                    if (!isset($use_mons[$k]) || !empty($use_mons[$k])) {
                        //MonUtil::proxyMon($url, $stats->getHandlerStat('http_code'), round($stats->getHandlerStat('total_time'),4) * 1000);
                    }
                };
            }
            if (!empty($params)) {
                foreach ($params as $k => $v) {
                    $this->log['params'][$k] = $v;
                    if (isset($content_types[$k]) && $content_types[$k] == 'json') {
                        $options[$k]['json'] = $v;
                        if (isset($headers[$k])) {
                            $options[$k]['headers'] = $headers[$k];
                        }
                    } elseif (isset($content_types[$k]) && $content_types[$k] == 'multipart') {
                        $options[$k]['multipart'] = $v;
                    } else {
                        $options[$k]['form_params'] = $v;
                    }
                }
            }
            if (!empty($timeouts)) {
                foreach ($timeouts as $k => $v) {
                    $options[$k]['timeout'] = $v / 1000;
                }
            }
            if (!empty($proxys)) {
                foreach ($proxys as $k => $v) {
                    $options[$k]['proxy'] = $v;
                }
            }
            //异步并发post请求
            $client = $this->client;
            $requests = function ($urls, $options) use ($client) {
                for ($i = 0; $i < count($urls); $i++) {
                    if (!isset($urls[$i]) || !isset($options[$i])) {
                        continue;
                    }
                    $each_url = $urls[$i];
                    $each_options = $options[$i];
                    yield function () use ($client, $each_url, $each_options) {
                        return $client->postAsync($each_url, $each_options);
                    };
                }
            };
            $pool = new Pool($client, $requests($urls, $options), [
                'concurrency' => $this->concurrency_num,
                'fulfilled' => function (Response $response, $index) {
                    $ret = $response->getBody()->getContents();
                    $this->log['response'][$index] = $ret;
                    $ret = empty(json_decode($ret, true)) ? $ret : json_decode($ret, true);
                    $this->con_result[$index]['code'] = $this->success_code;
                    $this->con_result[$index]['response'] = $ret;
                },
                'rejected' => function (TransferException $te, $index) {
                    $this->con_result[$index]['code'] = $this->fail_code;
                    $this->con_result[$index]['msg'] = $te->getMessage();
                    $this->log['response'][$index] = $te->getMessage();
                },
            ]);
            $pool->promise()->wait();
        } catch (\Exception $e) {
            $this->con_result[]['code'] = $this->fail_code;
            $this->con_result[]['msg'] = $e->getMessage();
            $this->log['response'][] = $e->getMessage();
        } finally {
            $this->addLog('concurrencyPost');
            return $this->con_result;
        }
    }

    /**
     * 并发执行post+get混合请求
     * @param array $urls [ 0=>'url1', 1=>'url2']
     * @param array $methods [ 0=>'GET', 1=>'POST']
     * @param array $params [ 0=>[k1=>v1], 1=>[k2=>v2]]
     * @param array $timeouts [ 0=>0.5, 1=>2]
     * @param array $content_types  [ 0=>'json']
     * @param array $headers [
     *                          0 => [
     *                          'Content-type' => 'application/json',
     *                          'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36'
     *                          ]
     * @param int $retry 重试次数
     * @param array $use_mons [0=>false,1=>false] 默认是true
     * @param array $proxys [0=>'http://192.11.222.124:8000', 1=>'http://192.11.222.124:8000']
     * @return array
     */
    public function concurrencyBlend($urls, $methods, $params = [], $timeouts = [], $content_types = [], $headers = [], $retry = 0, $use_mons = [], $proxys = [])
    {
        try {
            //验参
            if (empty($urls) || empty($methods) || !is_array($params)) {
                throw new \Exception('params error');
            }
            //构造client
            $config = [
                'concurrent' => true
            ];
            if (!empty($retry) && is_int($retry)) {
                $config['retry'] = $retry;
            }
            $this->client = (new Base())->initClient($config, $urls, $params);
            //构造配置
            $options = [];
            foreach ($urls as $k => $url) {
                $this->log['url'][$k] = $url;
                $options[$k]['on_stats'] = function (TransferStats $stats) use ($k, $url, $use_mons) {
                    $this->con_result[$k]['http_code'] = $stats->getHandlerStat('http_code');
                    if (!isset($use_mons[$k]) || !empty($use_mons[$k])) {
                        //MonUtil::proxyMon($url, $stats->getHandlerStat('http_code'), round($stats->getHandlerStat('total_time'),4) * 1000);
                    }
                };
            }
            if (!empty($params)) {
                foreach ($params as $k => $v) {
                    if (!isset($methods[$k])) {
                        continue;
                    }
                    $this->log['params'][$k] = $v;
                    if ($methods[$k] == 'POST') {
                        if (isset($content_types[$k]) && $content_types[$k] == 'json') {
                            $options[$k]['json'] = $v;
                            if (isset($headers[$k])) {
                                $options[$k]['headers'] = $headers[$k];
                            }
                        } elseif (isset($content_types[$k]) && $content_types[$k] == 'multipart') {
                            $options[$k]['multipart'] = $v;
                        } else {
                            $options[$k]['form_params'] = $v;
                        }
                    } else {
                        $options[$k]['query'] = $v;
                    }
                }
            }
            if (!empty($timeouts)) {
                foreach ($timeouts as $k => $v) {
                    $options[$k]['timeout'] = $v / 1000;
                }
            }
            if (!empty($proxys)) {
                foreach ($proxys as $k => $v) {
                    $options[$k]['proxy'] = $v;
                }
            }
            //异步并发请求
            $client = $this->client;
            $requests = function ($urls, $methods, $options) use ($client) {
                for ($i = 0; $i < count($urls); $i++) {
                    if (!isset($urls[$i]) || !isset($methods[$i]) || !isset($options[$i]) || !in_array($methods[$i], ['GET', 'POST'])) {
                        continue;
                    }
                    $each_url = $urls[$i];
                    $each_method = $methods[$i];
                    $each_options = $options[$i];
                    yield function () use ($client, $each_url, $each_method, $each_options) {
                        if ($each_method == 'POST') {
                            return $client->postAsync($each_url, $each_options);
                        }
                        return $client->getAsync($each_url, $each_options);
                    };
                }
            };
            $pool = new Pool($client, $requests($urls, $methods, $options), [
                'concurrency' => $this->concurrency_num,
                'fulfilled' => function (Response $response, $index) {
                    $ret = $response->getBody()->getContents();
                    $this->log['response'][$index] = $ret;
                    $ret = empty(json_decode($ret, true)) ? $ret : json_decode($ret, true);
                    $this->con_result[$index]['code'] = $this->success_code;
                    $this->con_result[$index]['response'] = $ret;
                },
                'rejected' => function (TransferException $te, $index) {
                    $this->con_result[$index]['code'] = $this->fail_code;
                    $this->con_result[$index]['msg'] = $te->getMessage();
                    $this->log['response'][$index] = $te->getMessage();
                },
            ]);
            $pool->promise()->wait();
        } catch (\Exception $e) {
            $this->con_result[]['code'] = $this->fail_code;
            $this->con_result[]['msg'] = $e->getMessage();
            $this->log['response'][] = $e->getMessage();
        } finally {
            $this->addLog('concurrencyBlend');
            return $this->con_result;
        }
    }

    private function addLog($method)
    {
        //过滤无意义的日志
        $log = true;
        $excludes = config('request', 'log.exclude');
        if (!empty($excludes)) {
            foreach ($excludes as $exclude) {
                if (is_array($this->log['url'])) {
                    foreach ($this->log['url'] as $url) {
                        if (strpos($url, $exclude) !== false) {
                            $log = false;
                            break;
                        }
                    }
                } else {
                    if (strpos($this->log['url'], $exclude) !== false) {
                        $log = false;
                        break;
                    }
                }
            }
        }
        if (!empty($log)) {
            $this->log['params'] = $this->change($this->log['params']);
            FileLog::info($method, json_encode($this->log));
        }
    }

    /**
     * 过滤resource类型字段
     * @param array $arr
     * @return array
     */
    private function change($arr)
    {
        foreach ($arr as $k => $v) {
            if (is_array($v)) {
                $arr[$k] = $this->change($v);
            } else {
                if (strpos(gettype($v), 'resource') !== false) {
                    $arr[$k] = gettype($v);
                }
            }
        }
        return $arr;
    }
}
