<?php
declare(strict_types=1);

namespace App\Utils\Transfer;

use Exception;
use Illuminate\Support\Facades\Config;
use InvalidArgumentException;
use WsdlToPhp\PackageBase\AbstractSoapClientBase;
use WsdlToPhp\PackageBase\SoapClientInterface;

abstract class SoapTransferAbstract
{
    // his客户端
    private AbstractSoapClientBase $client;

    // His接口配置数据
    private array $his_config;

    // 映射类的命名空间
    protected string $service_type_namespace;

    // 映射方法的命名空间
    protected string $struct_type_namespace;

    // 调用接口名称
    public string $transfer_name;

    // 调用接口参数
    public mixed $transfer_parameter;

    // 调用返回结果
    public mixed $transfer_response;

    // 运行时间
    public array $request_time;

    /**
     * Transfer constructor.
     * @param string $his_name
     * @throws InvalidArgumentException
     */
    public function __construct(string $his_name = "")
    {
        // 判断传入的配置名称是否存在
        $config = Config::get('hisservice.'. $his_name);
        if (!isset($config)) {
            throw new InvalidArgumentException($his_name);
        }

        // 获取配置文件中的“WSDL”文档URL
        $this->his_config = $config;
        $this->his_config['his_name'] = $his_name;

        $this->service_type_namespace = $this->his_config['service_type_namespace'];
        $this->struct_type_namespace = $this->his_config['struct_type_namespace'];

        $this->initialize();
    }

    /**
     * 初始化
     */
    public function initialize(){}

    /**
     * 获取配置
     * @param string $key
     * @return mixed|null
     */
    public function getHisConfigByKey(string $key): mixed
    {
        if (isset($this->his_config[$key])) {
            return $this->his_config[$key];
        }

        return false;
    }

    /**
     * 设置客户端选项
     * @return array
     */
    abstract public function clientOption(): array;

    /**
     * 设置返回值
     * @return string
     */
    abstract public function transferResponseStr(): string;

    /**
     * 需要调用类
     * @param string $class_name
     * @return $this
     * @throws InvalidArgumentException
     */
    public function transferClass(string $class_name): self
    {
        // 实例化具体操作类
        $class_name = $this->service_type_namespace . $class_name;

        if (!class_exists($class_name)) {
            throw new InvalidArgumentException($class_name. ' CLASS NOT FOUND.');
        }

        $this->client = new $class_name($this->clientOption());

        return $this;
    }

    /**
     * 需要调用的方法
     * @param string $method_name
     * @param array $request_data
     * @return $this
     * @throws InvalidArgumentException
     * @throws Exception
     */
    public function transferMethod(string $method_name, array $request_data = []): self
    {
        // 需要调用的方法的对象名
        $method_name_string = $this->struct_type_namespace . $method_name;

        // 记录调用的接口名称和参数
        $this->transfer_name = $method_name;
        $this->transfer_parameter = $request_data;

        if (!class_exists($method_name_string)) {
            throw new InvalidArgumentException($method_name_string. ' CLASS NOT FOUND.');
        }

        try {
            $this->request_time['start_time'] = microtime(true);
            $this->transfer_response = $this->client->{$method_name}(new $method_name_string(...$request_data));
            $this->request_time['end_time'] = microtime(true);
        } catch (Exception $e) {
            !isset($this->request_time['end_time']) && $this->request_time['end_time'] = microtime(true);
            $this->transfer_response = "{$e->getFile()}:{$e->getLine()}:{$e->getMessage()}";
            $this->recordLog();
            throw new InvalidArgumentException($e->getMessage());
        }

        // 获取soap错误
        if ($this->transfer_response === false) {
            $this->recordLog();
            $soap_fault = $this->client->getLastError();
            if (!empty($soap_fault)) {
                $soap_fault = reset($soap_fault);
                throw new Exception($soap_fault->getMessage());
            }

            throw new Exception('请求接口失败,请稍后再试');
        }

        $this->recordLog();
        return $this;

    }

    /**
     * 获取返回值
     * @param bool $is_format
     * @return mixed
     * @throws Exception
     */
    public function getResult(bool $is_format = true): mixed
    {
        $response_class_str = $this->struct_type_namespace . $this->transfer_name . 'Response';

        if (!class_exists($response_class_str)) {
            throw new Exception("Transfer Class Error: $response_class_str not found");
        }

        $res_str = $this->transferResponseStr();

        if (!property_exists($this->transfer_response, $res_str)) {
            throw new Exception("Transfer Attribute Error: transfer_response->$res_str not found");
        }

        $result_attr_str ='get'. ucfirst($res_str);
        $this->transfer_response = new $response_class_str($this->transfer_response->$res_str);
        $this->transfer_response = $this->transfer_response->$result_attr_str();

        if ($is_format) {
            return $this->responseFormat($this->transfer_response);
        }

        return $this->transfer_response;
    }

    /**
     * 响应格式化
     * @param $data
     */
    abstract public function responseFormat($data);

    /**
     * 保存日志记录
     * @param string $his_name
     * @param string $content
     */
    public function recordRequestLog(string $his_name, string $content): void
    {
        date_default_timezone_set("Asia/Shanghai");

        $dirname = $this->his_config['his_name'];
        $path = app()->storagePath(). DIRECTORY_SEPARATOR. $dirname. DIRECTORY_SEPARATOR;
        $file_path = $path. $his_name. '_log'. DIRECTORY_SEPARATOR. date('Ym'). DIRECTORY_SEPARATOR;
        $file_name = date('d'). ".log";

        !is_dir($file_path) && mkdir($file_path, 0755, true);

        $msg = "[".date('Y-m-d H:i:s')."]". PHP_EOL . $content . PHP_EOL . PHP_EOL;

        file_put_contents( $file_path. $file_name, $msg, FILE_APPEND);
    }

    /**
     * 魔术方法实现动态调用方法
     * @param $function
     * @param $args
     * @return $this
     * @throws InvalidArgumentException
     */
    public function __call($function, $args): self
    {
        IF (method_exists($this, $function)) {
            throw new InvalidArgumentException(__CLASS__ . '类的"'. $function .'"方法不存在');
        }

        $this->client = call_user_func($function, ...$args);
        return $this;
    }

    /**
     * 记录日志
     */
    public function recordLog(): void
    {
        // 判断“是否设置日志参数”和“当前调用的方法是否记录到日志”
        if (!empty($this->transfer_name)) {
            $run_time = sprintf("%.6f", ($this->request_time['end_time'] - $this->request_time['start_time']));

            if  (
                empty($this->his_config['not_log_arr']) ||
                !in_array($this->transfer_name, $this->his_config['not_log_arr'])
            ) {
                // 记录入参和结果
                $content = '[METHOD NAME] '. $this->transfer_name. PHP_EOL.
                    '[REQUEST PARAM] '. json_encode($this->transfer_parameter, JSON_UNESCAPED_UNICODE). PHP_EOL.
                    '[RESPONSE PARAM] '. json_encode($this->transfer_response, JSON_UNESCAPED_UNICODE). PHP_EOL.
                    '[RUN TIME] '. $run_time . "/s";
                $this->recordRequestLog($this->his_config['his_name'], $content);
            }
        }
    }
}