package com.sunyard.chsm.sdf.adapter;

import com.sun.jna.Native;
import com.sun.jna.ptr.PointerByReference;
import com.sunyard.chsm.sdf.context.AlgId;
import com.sunyard.chsm.sdf.context.SdrCode;
import com.sunyard.chsm.sdf.context.SunyardAlgId;
import com.sunyard.chsm.sdf.lib.SdfLibrary;
import com.sunyard.chsm.sdf.lib.SunyardSdfLibrary;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ClassUtils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author liulu
 * @since 2024/11/4
 */
@Slf4j
public class SunyardJnaSdfAdaptor extends JnaSdfAdaptor {

    private static final Map<String, SunyardSdfLibrary> SDF_LIB_MAP = new ConcurrentHashMap<>();


    private final String ip;
    private final Integer port;
    private final Integer connTimeout;
    private final Integer dealTimeout;

    public SunyardJnaSdfAdaptor(String ip) {
        this(ip, 8889, "sdf");
    }

    public SunyardJnaSdfAdaptor(String ip, int port) {
        this(ip, port, "sdf");
    }

    public SunyardJnaSdfAdaptor(String ip, int port, String libName) {
        this(ip, port, libName, 3000, 3000);
    }

    public SunyardJnaSdfAdaptor(String ip, int port, String libName, int connTimeout, int dealTimeout) {
        super((SdfLibrary) Proxy.newProxyInstance(ClassUtils.getDefaultClassLoader(),
                new Class[]{SdfLibrary.class},
                new InvocationHandler() {
                    private final SunyardSdfLibrary sunyardSdfLibrary;

                    {
                        sunyardSdfLibrary = SDF_LIB_MAP.computeIfAbsent(libName, k -> Native.load(libName, SunyardSdfLibrary.class));
                    }

                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object res = method.invoke(sunyardSdfLibrary, args);
                        if (method.getName().startsWith("SDF_") && res instanceof Integer) {
                            int resInt = (Integer) res;
                            if (resInt != SdrCode.SDR_OK.getCode()) {
                                log.warn("调用异常: {}, 返回值: {} -{}", method.getName(), Integer.toHexString(resInt), resInt);
                                SdrCode sdrCode = SdrCode.of((Integer) res);
                                throw new IllegalArgumentException(sdrCode != null ? sdrCode.getMsg() : "调用sdf接口异常: " + Integer.toHexString(resInt));
                            }
                        }
                        return res;
                    }
                }
        ));
        this.ip = ip;
        this.port = port;
        this.connTimeout = connTimeout;
        this.dealTimeout = dealTimeout;
    }

    @Override
    public String openDevice() {
        SunyardSdfLibrary sunyardSdfLibrary = (SunyardSdfLibrary) sdfLibrary;
        PointerByReference phDeviceHandle = new PointerByReference();
        sunyardSdfLibrary.SDF_OpenDevice(phDeviceHandle, safeStringBytes(ip), port, connTimeout, dealTimeout, 0);
        String key = UUID.randomUUID().toString();
        DEVICE_HANDLE_CONTEXT.put(key, phDeviceHandle.getValue());
        return key;
    }

    @Override
    protected int getAlgId(AlgId alg) {
        return SunyardAlgId.valueOf(alg.name()).getValue();
    }

    @Override
    protected void checkRes(int res) {

    }

    public static byte[] safeStringBytes(String str) {
        if (null == str || str.isEmpty()) {
            return new byte[]{0x30};
        }
        int len = str.getBytes().length;
        byte[] ret = new byte[len + 1];
        System.arraycopy(str.getBytes(), 0, ret, 0, len);
        return ret;
    }


    @RequiredArgsConstructor
    public static class CheckProxy implements InvocationHandler {
        private final SdfLibrary sdfLibrary;

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return null;
        }
    }

}
