import APP from '@/services';
import { getUserId } from '@/services/bus/account';
import { getErrorInfoByCode } from '@/services/bus/error';
import { funCode } from '@/services/funcode/index';
import ProtobufCtr from '@/services/socket/protobuf/index';
import { Callback } from '@/utils/websocket';
import { Package50 } from '@/utils/websocket/package';
import { v4 as uuidv4 } from 'uuid';
import * as type from './interface';
import { CommonSearchParam, ParseRsp, SoleSearchParam } from './interface';
import { IMessageHead } from './proto.d';
import { getProtoHeadParam, HeadEnum } from './protoHeader';

/**
 * 构建proto50 报文
 * 具体某个proto查询参数 SoleSearchParam
 * 通用查询查询参数 CommonSearchParam
 * @param param SoleSearchParam | CommonSearchParam
 */
export function buildProtoReq50(param: SoleSearchParam | CommonSearchParam): Package50 {
    if (Reflect.has(param, 'funCodeName')) {
        return buildSoleProtoReq50(param as SoleSearchParam);
    } else {
        return buildCommonProtoReq50(param as CommonSearchParam);
    }
}

/**
 * 解析proto50 报文
 */
export function parseProtoRsp50(rspPackage: any, funCodeName?: string): ParseRsp {
    if (funCodeName) {
        // 解析funCodeName 相对应的报文
        return parseSoleProtoRsp(rspPackage, funCodeName);
    } else {
        // 解析通用查询报文
        return parseCommonProtoRsp(rspPackage);
    }
}

/**
 * 生成 Package50 报文
 * @param funCodeName
 * @param req
 */
function setPackage50(funCodeName: string, req: any): Package50 {
    const buffer = new Uint8Array(req.toArrayBuffer());
    const reqPackage = new Package50([funCode[funCodeName], buffer], undefined);
    return reqPackage;
}

/**
 * 获取proto实例
 * @param protobufName
 */
function getProtoInstence(protobufName: string) {
    return ProtobufCtr.generateProtoInstence(protobufName);
}

/**
 * 构建单独的proto
 * @param protobufName
 * @param reqParams
 */
function buildSoleProtoReq(protobufName: string, reqParams: type.ReqParams) {
    // 登录报文
    const Req = getProtoInstence(protobufName);

    // 此处处理 一：为了 不需要单独写 构建报文函数，二：是 处理 报文中例如 id 和 ID 各种大写混乱的情况
    const ReqKeys = Object.keys(Req);
    const ReqKeysMap = new Map();
    ReqKeys.forEach((e) => {
        ReqKeysMap.set(e.toLowerCase(), e);
    });
    // 此处需要待优化，proto嵌套的时候存在问题
    const paramKeys = Object.keys(reqParams);
    for (let i = 0; i < paramKeys.length; i++) {
        const item = paramKeys[i];
        const lowerKey = item.toLowerCase();
        if (ReqKeysMap.has(lowerKey)) {
            Req[ReqKeysMap.get(lowerKey)] = reqParams[item];
        } else {
            console.error(`$报文包中不存在 ${item} 属性}`);
        }
    }
    return Req;
}

/**
 * 构建消息头部
 * @param funCodeName funCodeName
 * @param params IMessageHead
 */
function buildMsgHead(funCodeName: string, params: IMessageHead) {
    const code = funCode[funCodeName];
    if (!code) console.error('没有找到对应的funCode值,请配置');
    const messageHead = getProtoInstence('MessageHead');
    const head = {
        FunCode: code,
        UUID: uuidv4(),
        UserID: getUserId(),
    };
    Object.assign(messageHead, head, params);
    return messageHead;
}

/**
 * 构建单独的proto请求 Package50 报文
 * @param params SoleSearchParam
 */
function buildSoleProtoReq50(params: SoleSearchParam): Package50 {
    const { protobufName, funCodeName, reqParams, headerEnum, headParam } = params;
    // // // 消息头
    // const messageHead = buildMsgHead(funCodeName, headParam);
    // proto头部参数，站在用户角度，参数数据可以通用获取，但站在管理端的角度，则需要不能通用获取，而是需用选中对应数据中传入进来
    // 登录报文
    const header = headParam ? buildMsgHead(funCodeName, headParam) : getProtoHeadParam(funCodeName, headerEnum);
    const Req = buildSoleProtoReq(protobufName, reqParams);
    Req.Header = header
    return setPackage50(funCodeName, Req);
}

/**
 * 构建通用查询 Package50 报文
 * @param param CommonSearchParam
 */
function buildCommonProtoReq50(param: CommonSearchParam): Package50 {
    const { senNames, statement, params } = param;
    let queryReq = null;
    let funCodeName = '';
    // 加密信息
    if (senNames && senNames.length > 0) {
        queryReq = getProtoInstence('QueryCommonSenReq');
        queryReq.setSenNames(senNames);
        funCodeName = 'QueryCommonSenReq';
    } else {
        queryReq = getProtoInstence('QueryCommonReq');
        funCodeName = 'QueryCommonReq';
    }
    // 消息头
    const messageHead = getProtoHeadParam(funCodeName, 0);
    queryReq.setStatement(statement);
    // 构建查询参数
    const paramValues = [];
    for (const key in params) {
        const value = params[key];
        const Req = getProtoInstence('ParamValue');
        const hasId = (id: string): boolean => key.toLowerCase() === id;
        // 如果有accountid则需加到head中
        if (hasId('accountid') || hasId('buyaccountid') || hasId('sellaccountid')) {
            messageHead.AccountID = value;
        }
        Req.setKey(key);
        Req.setValue(value);
        paramValues.push(Req);
    }

    queryReq.setParamValues(paramValues);
    queryReq.setHeader(messageHead);
    return setPackage50(funCodeName, queryReq);
}

function isErrer(rspPackage: any, funCodeName: string): void {
    if (APP.Socket.trade.connState !== 2) {
        console.error(`交易服务未连接！`);
    }
    // 判断回复FunCode是否正确
    if (rspPackage.funCode !== funCode[funCodeName]) {
        console.error(`服务返回【${funCodeName}: ${funCode[funCodeName]}】解析包错误！`);
    }
    // 判断是否有数据体
    if (!rspPackage.content) {
        console.error(`服务未返回数据体!`);
    }
}

/**
 * 解析 funCodeName 对应的报文
 * @param rspPackage
 * @param funCodeName
 */
function parseSoleProtoRsp(rspPackage: any, funCodeName: string): ParseRsp {
    isErrer(rspPackage, funCodeName);
    const reqPackage = ProtobufCtr.generateProtoContent(funCodeName, rspPackage.content);
    const { RetCode, Status, RetDesc } = reqPackage;
    console.warn(`解析${funCodeName}对应的报文错误：RetCode: ${RetCode}: RetDesc${RetDesc}`)
    if (RetCode !== null) {
        if (RetCode === 0) {
            return { isSuccess: true, result: reqPackage };
        } if (RetCode === -1) { // 管理端错误消息
            return { isSuccess: false, result: RetDesc };
        } else {
            const errMsg = getErrorInfoByCode(String(RetCode));
            const result = errMsg ? errMsg : `未知错误:${RetCode}`;
            return { isSuccess: false, result };
        }
    } else {
        // 银行 业务 以 Status 作为判断依据
        console.log(`Status:${Status}`);
        if (Status === 0) {
            return { isSuccess: true, result: getErrorInfoByCode(Status) };
        } else {
            return { isSuccess: false, result: getErrorInfoByCode(Status) };
        }
    }
}

/**
 * 解析通用查询报文
 * @param rspParam
 */
function parseCommonProtoRsp(rspParam: any): ParseRsp {
    //是否加密
    const funCodeName = rspParam.isSen ? 'QueryCommonSenRsp' : 'QueryCommonRsp';
    const { isSuccess, result } = parseSoleProtoRsp(rspParam, funCodeName);
    if (isSuccess) {
        // 组装数据
        const { FieldInfos, RowValues } = result.Rsps[0];
        // 此处只是简单处理
        const arr = RowValues.map((item: any) => {
            const result: any = {};
            FieldInfos.forEach((el: any, i: number) => {
                const { FieldName, FieldType } = el;
                const dateType = ['date', 'time', 'datetime', 'timestamp'];
                let value = item.RowValues[i];
                if (dateType.includes(lower(FieldType))) {
                    // 解决safari时间问题
                    value = value.replace(/-/g, '/').replace('.0', '');
                }
                result[lower(FieldName)] = value;
            });
            return result;
        });
        return { isSuccess: true, result: arr };
    } else {
        return { isSuccess, result };
    }
}

/**
 * 解析通知
 * @param rspPackage 回应包
 * @param funCodeName  funcode 名
 */
export const noticeParseRsp = (rspPackage: any, funCodeName: string) => {
    isErrer(rspPackage, funCodeName);
    const reqPackage = ProtobufCtr.generateProtoContent(funCodeName, rspPackage.content);
    const { RetCode } = reqPackage;
    let errMsg = getErrorInfoByCode(String(RetCode));
    if (errMsg === '') {
        errMsg = `未知错误:${RetCode}`;
    }
    return errMsg;
};

function lower(value: string): string {
    return value.toLowerCase();
}

/**
 * 此方法站在 用户角度
 * @param param proto除去head 剩下参数
 * @param headerEnum 自定义的proto参数枚举值，减小重复抒写 默认0
 * @param reqFuncodeName proto 发送请求定义的头名字，特别需要注意，自已的funCodeName要与这个一致
 * @param rspFuncodeName proto 响应请求定义的头名字
 * @returns
 */
export function protoMiddleware<T>(param: T, reqName: string, rspName: string, headerEnum: HeadEnum = 0): Promise<any> {
    return new Promise((resolve, reject) => {
        const params = {
            protobufName: reqName,
            funCodeName: reqName,
            reqParams: param,
            headerEnum,
        };
        const package50 = buildProtoReq50(params);
        const code = rspName ? funCode[rspName] : undefined
        APP.sendTradingServer(package50, code, {
            onSuccess: (res) => {
                const { isSuccess, result } = parseProtoRsp50(res, rspName);
                if (isSuccess) {
                    resolve(result);
                } else {
                    reject(result);
                }
            },
            onFail: (err) => {
                console.log(`proto:${reqName}错误消息：${err}`)
                reject(err.message)
            },
        } as Callback);
    });
}

/**
 * 此方法站在 管理端角度
 * @param param proto除去head 剩下参数
 * @param headParam IMessageHead
 * @param reqFuncodeName proto 发送请求定义的头名字，特别需要注意，自已的funCodeName要与这个一致
 * @param rspFuncodeName proto 响应请求定义的头名字
 * @returns
 */
export function protoMiddleware_manager<T>(param: T, reqName: string, rspName: string, headParam: IMessageHead): Promise<any> {
    return new Promise((resolve, reject) => {
        const params = {
            protobufName: reqName,
            funCodeName: reqName,
            reqParams: param,
            headParam,
        };
        const package50 = buildProtoReq50(params);
        APP.sendTradingServer(package50, undefined, {
            onSuccess: (res) => {
                const { isSuccess, result } = parseProtoRsp50(res, rspName);
                if (isSuccess) {
                    resolve(result);
                } else {
                    reject(result);
                }
            },
            onFail: (err) => reject(err.message),
        } as Callback);
    });
}