<?php


namespace App\Services;


use App\Exceptions\WarningException;
use App\OrderPackage;
use Exception;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Http;

class LogisticSFService
{
    /**
     * 顺丰字段与数据库字段的映射关心
     * @var string[]
     */
    protected $protected_switch = [
        'logistic_number' => 'mailno',
        'transfer_status' => 'remark',
        'received_at' => 'accept_time',
    ];

    /**
     * 获取顺丰快递揽收数据
     * @param array $logisticNums [logisticNums]快递单号数组
     * @return array 快递揽收信息数组
     * @throws Exception 未知的丰桥opCode
     */
    public function get(array $logisticNums): array
    {
        // 将$logisticNums以10个位单位进行分割，返回二维数组
        $logisticNums_size_10 = array_chunk($logisticNums, config('api_logistic.SF.max_size', 10));
        // 遍历二维数组批量查询顺丰接口
        //将查询到的结果整合到一起，更新order_packages.received_at
        $result = [];
        foreach ($logisticNums_size_10 as $numbers) {
            $numbersStr = implode(',', $numbers);
            $result_10 = $this->getResultFromSF(config('api_logistic.SF.head'), $numbersStr, config('api_logistic.SF.check_word'), config('api_logistic.SF.url'));
            $result = array_merge($result, $result_10);
        }
        return $result;
    }

    /**
     * @param string $head 客户号
     * @param string $numbers 快递单号字符串，多个以','分隔
     * @param string $checkWord 客户秘钥
     * @param string $url 顺丰接口地址
     * @return array
     * @throws Exception 未知的丰桥opCode
     */
    public function getResultFromSF(string $head, string $numbers, string $checkWord, string $url): array
    {
        $responseBody = get_object_vars(simplexml_load_string($this->sendHttpToSF($head, $numbers, $checkWord, $url))->Body)['RouteResponse'];
        $result = [];
        if (is_array($responseBody)) {//SF返回多个单号的查询结果
            $result = $this->transformSFMoreToArr($responseBody, $result);
        } else {
            $result[] = $this->transformSFOneToArr(get_object_vars($responseBody), []);
        }
        return $result;
    }

    /**
     * 构建顺丰xml请求体
     * @param string $head
     * @param string $number
     * @return string
     */
    private function buildXmlStr(string $head, string $number): string
    {
        return <<<xml
<?xml version="1.0" encoding="utf-8" ?>
    <Request service='RouteService' lang='zh-CN'>
    <Head>$head</Head>
    <Body>
    <RouteRequest
    tracking_number="{$number}" tracking_type='1' method_type='1'
    />
    </Body>
    </Request>
xml;
    }

    /**
     * 将单个单号的顺丰数据转换为数组
     * @param array $routeResponse
     * @param array $data
     * @return array
     * @throws Exception
     */
    public function transformSFOneToArr(array $routeResponse, array $data): array
    {

        $data['logistic_number'] = $routeResponse['@attributes'][$this->protected_switch['logistic_number']];
        try {
            $lastRoute = get_object_vars($routeResponse['Route'][count($routeResponse['Route']) - 1])['@attributes'];//获取最新的路由信息
            $data = $this->switchOpCodeToStatus($lastRoute, $data);
            $data['transfer_status'] = $this->transformRoutes($routeResponse['Route']);
            if (!array_key_exists('exception', $data)) {//当顺丰返回异常时，不需要再根据时间判断是否异常，直接用顺丰的异常就好
                $orderPackageReceivedSyncService = app('OrderPackageReceivedSyncService');
                $exceptionData = $orderPackageReceivedSyncService->setExceptionType($data, array_key_exists('accept_time',$lastRoute) ? $lastRoute['accept_time'] : null);
                $data['exception_type'] = $exceptionData['exception_type'];
                $data['exception'] = $exceptionData['exception'];
            }
            //如果没有发现额外的异常，且查询到物流轨迹，将异常置为无
            if (!array_key_exists('exception', $data)
                && !array_key_exists('exception_type', $data)
                && array_key_exists('transfer_status', $data)
            ) {
                $data['exception_type'] = '无';
                $data['exception'] = '否';
            }
        } catch (Exception $e) {
            throw new WarningException("单号没有查询到快递路由信息','LogisticSFService->transformSFOneToArr->{$data['logistic_number']}");
        } finally {
            $data['routes_length'] = array_key_exists('transfer_status', $data) ? count($data['transfer_status']) : 0;
            return $data;
        }
    }

    /**
     * 转换快递路由信息
     * @param  $routs 快递路由
     * @return array
     */
    public function transformRoutes($routs): array
    {
        $result = [];
        if (!is_array($routs)) {
            $routs = [$routs];
        }
        foreach ($routs as $route) {
            $route = get_object_vars($route)['@attributes'];
            $data['accept_time'] = $route['accept_time'];
            $data['accept_address'] = $route['accept_address'];
            $data['remark'] = $route['remark'];
            $result[] = $data;
        }
        return $result;
    }

    /**
     * 将最新路由信息转换为数组
     * @param array $lastRoute
     * @param array $data
     * @return array
     * @throws Exception
     */
    public function switchOpCodeToStatus(array $lastRoute, array $data): array
    {
        try {
            switch ($lastRoute['opcode']) {
                case 123:
                case 130:
                case 3036:
                case 31:
                case 30:
                case 36:
                    $data['status'] = '在途';
                    break;
                case 33:
                    $data['status'] = '派送异常';
                    $data['exception_type'] = '派件异常';
                    $data['exception'] = '是';
                    break;
                case 204:
                case 44:
                    $data['status'] = '派送中';
                    break;
                case 50:
                    $data['status'] = '已揽收';
                    break;
                case 607:
                case 8000:
                case 80:
                    $data['status'] = '已收件';
                    $data['received_at'] = $lastRoute[$this->protected_switch['received_at']];
                    break;
                case 648:
                case 99:
                    $data['status'] = '返回中';
                    break;
                case 70:
                    $data['status'] = '无';
                    $data['exception'] = '是';
                    $data['exception_type'] = '其他';
                    break;
                default:
                    throw new WarningException("未知的丰桥状态码: {$lastRoute['opcode']}->{json_encode($lastRoute)}");
            }
        } catch (WarningException $e) {
            $data['status'] = '其他异常';
        } finally {
            return $data;
        }
    }

    /**
     * @param string $head
     * @param string $numbers
     * @param string $checkWord
     * @param string $url
     * @return Response
     * @throws Exception
     */
    public function sendHttpToSF(string $head, string $numbers, string $checkWord, string $url): Response
    {
        $xml = $this->buildXmlStr($head, $numbers);
        $checkingJson = $xml . $checkWord;
        $verifyCode = base64_encode(md5($checkingJson, true));
        try {
            $response = Http::withHeaders(['Content-Type' => 'text/xml'])->get($url, ['xml' => $xml, 'verifyCode' => $verifyCode]);
        } catch (Exception $e) {
            throw new WarningException("HTTP请求顺丰接口异常->{$e->getMessage()}");
        }
        return $response;
    }

    /**
     * 将多个顺丰的单号转换为数组
     * @param array $responseBody
     * @param array $result
     * @return array
     * @throws Exception
     */
    public function transformSFMoreToArr(array $responseBody, array $result): array
    {
        foreach ($responseBody as $routeResponse) {
            $result[] = $this->transformSFOneToArr(get_object_vars($routeResponse), []);
        }
        return $result;
    }
}
