<?php
/**
 * Created by PhpStorm.
 * User: Zero
 * Date: 2020/8/18
 * Time: 8:18
 */

namespace Meibuyu\Micro\Shopify\lib;

use Exception;
use Meibuyu\Micro\Shopify\tools\CurlHttpRequestJson;
use Meibuyu\Micro\Shopify\tools\HttpRequestJson;
use Psr\Http\Message\ResponseInterface;

abstract class AbstractShopify
{

    /**
     * 资源id
     * @var int
     */
    public $id = null;

    protected $httpHeaders = [];

    protected $resourceUrl;
    protected $resourceKey;
    protected $pluralizeKey;

    /**
     * 无count方法
     * @var boolean
     */
    public $countEnabled = true;

    // 子集资源
    protected $childResource = [];

    // 自定义请求方法
    protected $customGetActions = [];
    protected $customPostActions = [];
    protected $customPutActions = [];
    protected $customDeleteActions = [];

    private $config;

    /**
     * @var HttpRequestJson|CurlHttpRequestJson
     */
    private $httpRequestJson;

    /**
     * AbstractShopify constructor.
     * @param $config
     * @param null $id
     * @param string $parentResourceUrl
     * @throws Exception
     */
    public function __construct($config, $id = null, $parentResourceUrl = null)
    {
        $this->config = $config;
        $this->id = $id;
        $this->pluralizeKey = $this->pluralizeKey ?: $this->resourceKey . 's';
        $this->resourceUrl = ($parentResourceUrl ? "$parentResourceUrl/" : $config['api_url']) . $this->pluralizeKey . ($this->id ? "/{$this->id}" : '');
        $this->httpRequestJson = make(CurlHttpRequestJson::class);
        if (isset($config['is_private_app']) && $config['is_private_app'] == false) {
            // 如果不是私人应用,则使用访问令牌
            if (isset($config['access_token'])) {
                $this->httpHeaders['X-Shopify-Access-Token'] = $config['access_token'];
            } elseif (!isset($config['access_token'])) {
                throw new Exception("请设置access_token值");
            }
        } else {
            if (isset($config['api_password'])) {
                $this->httpHeaders['X-Shopify-Access-Token'] = $config['api_password'];
            } elseif (!isset($config['api_password'])) {
                throw new Exception("请设置api_password值");
            }
        }
    }

    /**
     * 调用子集资源
     * @param $childName
     * @return mixed
     */
    public function __get($childName)
    {
        return $this->$childName();
    }

    /**
     * 调用自定义方法
     * @param $name
     * @param $arguments
     * @return mixed
     * @throws Exception
     */
    public function __call($name, $arguments)
    {
        if (ctype_upper($name[0])) {
            $childKey = array_search($name, $this->childResource);
            if ($childKey === false) {
                throw new Exception(" $name 不属于 {$this->getResourceName()}");
            }
            $childClassName = !is_numeric($childKey) ? $childKey : $name;
            $childClass = __NAMESPACE__ . "\\" . $childClassName;
            $resourceID = !empty($arguments) ? $arguments[0] : null;
            return new $childClass($this->config, $resourceID, $this->resourceUrl);
        } else {
            $actionMaps = [
                'post' => 'customPostActions',
                'put' => 'customPutActions',
                'get' => 'customGetActions',
                'delete' => 'customDeleteActions',
            ];
            //Get the array key for the action in the actions array
            foreach ($actionMaps as $httpMethod => $actionArrayKey) {
                $actionKey = array_search($name, $this->$actionArrayKey);
                if ($actionKey !== false) break;
            }
            if ($actionKey === false) {
                throw new Exception("No action named $name is defined for " . $this->getResourceName());
            }
            //If any associative key is given to the action, then it will be considered as the method name,
            //otherwise the action name will be the method name
            $customAction = !is_numeric($actionKey) ? $actionKey : $name;
            //Get the first argument if provided with the method call
            $methodArgument = !empty($arguments) ? $arguments[0] : [];
            //Url parameters
            $urlParams = [];
            //Data body
            $dataArray = [];
            //Consider the argument as url parameters for get and delete request
            //and data array for post and put request
            if ($httpMethod == 'post' || $httpMethod == 'put') {
                $dataArray = $methodArgument;
            } else {
                $urlParams = $methodArgument;
            }
            $url = $this->generateUrl($urlParams, null, $customAction);
            if ($httpMethod == 'post' || $httpMethod == 'put') {
                return $this->$httpMethod($dataArray, $url, false);
            } else {
                return $this->$httpMethod($dataArray, $url);
            }
        }
    }

    private function getResourceName()
    {
        return substr(get_called_class(), strrpos(get_called_class(), '\\') + 1);
    }

    public function generateUrl($urlParams = [], $id = null, $customAction = null)
    {
        $resourceUrl = $this->resourceUrl;
        if ($id) {
            if ($this->id) {
                if ($id !== $this->id) {
                    $resourceUrl = str_replace($this->id, $id, $this->resourceUrl);
                }
            } else {
                $resourceUrl = $this->resourceUrl . "/$id";
            }
        }
        return $resourceUrl . ($customAction ? "/$customAction" : '') . '.json' . (!empty($urlParams) ? '?' . http_build_query($urlParams) : '');
    }

    /**
     * 获取数量
     * @param array $urlParams
     * @return mixed
     * @throws Exception
     */
    public function count($urlParams = [])
    {
        if (!$this->countEnabled) {
            throw new Exception("当前类{$this->getResourceName()}不支持count()方法");
        }
        $url = $this->generateUrl($urlParams, null, 'count');
        return $this->get([], $url, 'count');
    }

    /**
     * @param array $urlParams
     * @param null $url
     * @param null $dataKey
     * @return mixed
     * @throws Exception
     */
    public function get($urlParams = [], $url = null, $dataKey = null)
    {
        if (!$url) $url = $this->generateUrl($urlParams);
        $response = $this->httpRequestJson->get($url, $this->httpHeaders);
        if (!$dataKey) $dataKey = $this->id ? $this->resourceKey : $this->pluralizeKey;
        return $this->processResponse($response, $dataKey);
    }

    /**
     * 分页
     * @param null $url
     * @param array $urlParams
     * @return mixed ['data' => [数据], 'next_link' => '下一页链接']
     * @throws Exception
     */
    public function page($url = null, $urlParams = [])
    {
        if (!$url) $url = $this->generateUrl($urlParams);
        $response = $this->httpRequestJson->get($url, $this->httpHeaders);
        return $this->processPageResponse($response, $this->pluralizeKey);
    }

    /**
     * 根据id获取一条数据
     * @param $id
     * @param array $urlParams
     * @return mixed
     * @throws Exception
     */
    public function show($id, $urlParams = [])
    {
        $url = $this->generateUrl($urlParams, $id);
        $response = $this->httpRequestJson->get($url, $this->httpHeaders);
        return $this->processResponse($response, $this->resourceKey);
    }

    /**
     * @param $dataArray
     * @param null $url
     * @param bool $wrapData
     * @return mixed
     * @throws Exception
     */
    public function post($dataArray, $url = null, $wrapData = true)
    {
        if (!$url) $url = $this->generateUrl();
        if ($wrapData && !empty($dataArray)) $dataArray = $this->wrapData($dataArray);
        $response = $this->httpRequestJson->post($url, $dataArray, $this->httpHeaders);
        return $this->processResponse($response, $this->resourceKey);
    }

    /**
     * @param int|string $id
     * @param $dataArray
     * @param null $url
     * @param bool $wrapData
     * @return mixed
     * @throws Exception
     */
    public function put($id, $dataArray, $url = null, $wrapData = true)
    {
        if (!$url) $url = $this->generateUrl([], $id);
        if ($wrapData && !empty($dataArray)) $dataArray = $this->wrapData($dataArray);
        $response = $this->httpRequestJson->put($url, $dataArray, $this->httpHeaders);
        return $this->processResponse($response, $this->resourceKey);
    }

    /**
     * @param int|string $id
     * @param array $urlParams
     * @param null $url
     * @return mixed
     * @throws Exception
     */
    public function delete($id = null, $urlParams = [], $url = null)
    {
        if (!$url) $url = $this->generateUrl($urlParams, $id);
        $response = $this->httpRequestJson->delete($url, $this->httpHeaders);
        return $this->processResponse($response);
    }

    protected function wrapData($dataArray, $dataKey = null)
    {
        if (!$dataKey) $dataKey = $this->resourceKey;
        return [$dataKey => $dataArray];
    }

    protected function castString($array)
    {
        if (!is_array($array)) return (string)$array;
        $string = '';
        $i = 0;
        foreach ($array as $key => $val) {
            $string .= ($i === $key ? '' : "$key - ") . $this->castString($val) . ', ';
            $i++;
        }
        $string = rtrim($string, ', ');
        return $string;
    }

    /**
     * 处理响应
     * @param array $response
     * @param null $dataKey
     * @return mixed
     * @throws Exception
     */
    public function processResponse($response, $dataKey = null)
    {
        [$code, , $content] = $response;
        $content = json_decode($content, true);
        if (isset($content['errors'])) {
            throw new Exception($this->castString($content['errors']), $code);
        }
        if ($dataKey && isset($content[$dataKey])) {
            return $content[$dataKey];
        } else {
            return $content;
        }
    }

    /**
     * 处理响应
     * @param ResponseInterface $response
     * @param null $dataKey
     * @return mixed
     * @throws Exception
     */
    public function processPageResponse($response, $dataKey = null)
    {
        [$code, $headers, $content] = $response;
        $content = json_decode($content, true);
        $link = $this->getLink($headers);
        if (isset($content['errors'])) {
            throw new Exception($this->castString($content['errors']), $code);
        }
        if ($dataKey && isset($content[$dataKey])) {
            $data = $content[$dataKey];
        } else {
            $data = $content;
        }
        return ['data' => $data, 'next_link' => $link];
    }

    public function getLink($header, $type = 'next')
    {
        if (!empty($header['x-shopify-api-version'][0]) && $header['x-shopify-api-version'][0] < '2019-07') {
            return null;
        }
        if (!empty($header['link'][0])) {
            $headerLinks = $header['link'][0];
            if (stristr($headerLinks, '; rel="' . $type . '"') > -1) {
                $headerLinks = explode(',', $headerLinks);
                foreach ($headerLinks as $headerLink) {
                    if (stristr($headerLink, '; rel="' . $type . '"') === -1) {
                        continue;
                    }

                    $pattern = '#<(.*?)>; rel="' . $type . '"#m';
                    preg_match($pattern, $headerLink, $linkResponseHeaders);
                    if ($linkResponseHeaders) {
                        return $linkResponseHeaders[1];
                    }
                }
            }
        }
        return null;
    }

}