/**
 * 模仿thinkingdata-node直接写一个log的写入，而日志直接用pinus-logger实现
 */
import { taLogger } from "../../util/logger";

const KEY_NAME_MATCH_REGEX = /^[a-zA-Z#][a-zA-Z0-9_]+$/;

let util: any = {};
util.version = '1.2.2';
util.log = function(message: string) {
    console.log(message);
}
util.each = function (obj, iterator, context) {
    if (obj === null) {
        return;
    }
    if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) {
        obj.forEach(iterator, context);
    } else if (obj.length === +obj.length) {
        for (let i = 0, l = obj.length; i < l; i++) {
            if (i in obj && iterator.call(context, obj[i], i, obj) === {}) {
                return;
            }
        }
    } else {
        for (let key in obj) {
            if (Object.prototype.hasOwnProperty.call(obj, key)) {
                if (iterator.call(context, obj[key], key, obj) === {}) {
                    return;
                }
            }
        }
    }
};
util.formatDate = function (d) {
    function pad(n) {
        return n < 10 ? '0' + n : n;
    }

    function padMilliseconds(n) {
        if (n < 10) {
            return '00' + n;
        } else if (n < 100) {
            return '0' + n;
        } else {
            return n;
        }
    }
    return d.getFullYear() + '-' +
        pad(d.getMonth() + 1) + '-' +
        pad(d.getDate()) + ' ' +
        pad(d.getHours()) + ':' +
        pad(d.getMinutes()) + ':' +
        pad(d.getSeconds()) + '.' +
        padMilliseconds(d.getMilliseconds());
};

util.searchObjDate = function (o) {
    if (util.check.isObject(o) || util.check.isArray(o)) {
        util.each(o, function (a, b) {
            if (util.check.isObject(a) || util.check.isArray(a)) {
                util.searchObjDate(o[b]);
            } else {
                if (util.check.isDate(a)) {
                    o[b] = util.formatDate(a);
                }
            }
        });
    }
};

util.check = {
    isUndefined: function (obj) {
        return obj === void 0;
    },

    isObject: function (obj) {
        return (toString.call(obj) === '[object Object]') && (obj !== null);
    },

    isEmptyObject: function (obj) {
        if (util.check.isObject(obj)) {
            for (let key in obj) {
                if (Object.prototype.hasOwnProperty.call(obj, key)) {
                    return false;
                }
            }
            return true;
        }
        return false;
    },

    isArray: function (obj) {
        return toString.call(obj) === '[object Array]';
    },

    isString: function (obj) {
        return toString.call(obj) === '[object String]';
    },

    isDate: function (obj) {
        return toString.call(obj) === '[object Date]';
    },

    isNumber: function (obj) {
        return toString.call(obj) === '[object Number]';
    },

    isBoolean: function (obj) {
        return toString.call(obj) === '[object Boolean]';
    },

    isJSONString: function (str) {
        try {
            JSON.parse(str);
        } catch (e) {
            return false;
        }
        return true;
    }
};

util.properties = {
    _strip: function (prop) {
        if (!util.check.isObject(prop)) {
            return prop;
        }
        util.each(prop, function (v, k) {
            if (!(util.check.isString(v) || util.check.isNumber(v) || util.check.isDate(v) || util.check.isBoolean(v) || util.check.isArray(v))) {
                util.log('您的数据-', k, v, '-格式不满足要求，我们已经将其删除. 属性值只支持 String, Number, Date, Boolean, Array');
                delete prop[k];
            }
        });
        return prop;
    },

    _checkPropertiesKey: function (obj) {
        let flag = true;
        util.each(obj, (v, k) => {
            if (!KEY_NAME_MATCH_REGEX.test(k)) {
                util.log('不合法的 KEY 值: ' + k);
                flag = false;
            }
        });
        return flag;
    },

    name: function (s) {
        if (!util.check.isString(s) || !KEY_NAME_MATCH_REGEX.test(s)) {
            util.log('请检查参数格式, 事件或属性名称必须是英文字母、 \'util\'或\'#\' 开头, 包含字母和数字的字符串: ' + s);
            return false;
        } else {
            return true;
        }
    },

    properties: function (p) {
        this._strip(p);
        if (p) {
            if (util.check.isObject(p)) {
                if (this._checkPropertiesKey(p)) {
                    return true;
                } else {
                    util.log('请检查参数格式, properties 的 key 只能以字母或\'#\'开头，包含数字、字母和下划线 util');
                    return false;
                }
            } else {
                util.log('properties 可以没有，但有的话必须是对象');
                return false;
            }
        } else {
            return true;
        }
    },

    propertiesMust: function (p) {
        this._strip(p);
        if (p === undefined || !util.check.isObject(p) || util.check.isEmptyObject(p)) {
            util.log('properties 必须是对象且有值');
            return false;
        } else {
            if (this._checkPropertiesKey(p)) {
                return true;
            } else {
                util.log('请检查参数格式, properties 的 key 只能以字母或\'#\'开头，包含数字、字母和下划线 util');
                return false;
            }
        }
    },

    userId: function (id) {
        if (util.check.isString(id) && /^.+$/.test(id)) {
            return true;
        } else {
            util.log('用户 id 必须是不能为空');
            return false;
        }
    }
}


const DEFAULT_PROPERTIES = {
    "#lib": "node",
    "#lib_version": util.version,
};

/**
 * 内部函数，创建 ta 实例
 */
let _createClient = function (consumer) {
    let ta: any = {};
    ta.consumer = consumer;
    ta.superProperties = {};

    ta._basicCheck = function (params) {
        if (!util.check.isObject(params)) {
            return new Error("params for track must be an object");
        }

        if (!util.properties.userId(params.accountId) && !util.properties.userId(params.distinctId)) {
            return new Error("account ID and distinct ID cannot be empty at the same time");
        }

        return undefined;
    };

    ta.track = function (params, skipLocalCheck = false) {
        let callback = params.callback || function () {
        };
        if (!ta._trackCheck(params, skipLocalCheck, callback)) {
            return;
        }
        ta._sendRequest("track", params, callback);
    };

    ta._trackCheck = function (params, skipLocalCheck, callback) {
        let err = this._basicCheck(params);
        if (err) {
            callback(err);
            return false;
        }
        if (!util.properties.name(params.event)) {
            callback(new Error("invalid event name"));
            return false;
        }
        if (!skipLocalCheck && !util.properties.properties(params.properties)) {
            callback(new Error("invalid properties"));
            return false;
        }
        return true;
    };

    ta.userSet = function (params) {
        let callback = params.callback || function () {
        };

        let err = this._basicCheck(params);
        if (err) {
            callback(err);
            return;
        }

        if (!util.properties.propertiesMust(params.properties)) {
            callback(new Error("invalid properties"));
            return;
        }

        ta._sendRequest("user_set", params, callback);
    };

    ta.userSetOnce = function (params) {
        let callback = params.callback || function () {
        };

        let err = this._basicCheck(params);
        if (err) {
            callback(err);
            return;
        }

        if (!util.properties.propertiesMust(params.properties)) {
            callback(new Error("invalid properties"));
            return;
        }

        ta._sendRequest("user_setOnce", params, callback);
    };

    ta.userAdd = function (params) {
        let callback = params.callback || function () {
        };

        let err = this._basicCheck(params);
        if (err) {
            callback(err);
            return;
        }

        if (!util.properties.propertiesMust(params.properties)) {
            callback(new Error("invalid properties"));
            return;
        }

        ta._sendRequest("user_add", params, callback);
    };

    ta.setDynamicSuperProperties = function (getDynamicProperties, callback) {
        let err;
        if (typeof getDynamicProperties === "function") {
            if (util.properties.properties(getDynamicProperties())) {
                ta.getDynamicProperties = getDynamicProperties;
            } else {
                err = new Error("Invalid return type of getDynamicProperties");
            }
        } else {
            err = new Error("getDynamicProperties must be a funciton");
        }

        if (callback && err) {
            callback(err);
        }
    };

    // 内部函数，组织数据格式，并发送给对应的 consumer 去处理
    ta._sendRequest = function (type, eventData, callback) {
        let time = util.check.isUndefined(eventData.time) || !util.check.isDate(eventData.time)
            ? new Date()
            : eventData.time;

        let data = {
            "#type": type,
            "#time": util.formatDate(time),
        };

        if (eventData.distinctId) {
            data["#distinct_id"] = eventData.distinctId;
        }

        if (eventData.accountId) {
            data["#account_id"] = eventData.accountId;
        }

        if (eventData.ip) {
            data["#ip"] = eventData.ip;
        }

        if (eventData.event) {
            data["#event_name"] = eventData.event;
        }

        if (eventData.eventId) {
            data["#event_id"] = eventData.eventId;
        }

        if (eventData.firstCheckId) {
            data["#first_check_id"] = eventData.firstCheckId;
        }

        if (eventData.uuid) {
            data["#uuid"] = eventData.uuid;
        }

        if (eventData.appId) {
            data["#app_id"] = eventData.appId;
        }

        if (type === "track" ||
            type === "track_update" ||
            type === "track_overwrite") {
            data["properties"] = { ...DEFAULT_PROPERTIES, ...ta.superProperties, ...(ta.getDynamicProperties ? ta.getDynamicProperties() : {}) };
        } else {
            data["properties"] = {};
        }

        if (
            util.check.isObject(eventData.properties) &&
            !util.check.isEmptyObject(eventData.properties)
        ) {
            data['properties'] = { ...data['properties'], ...eventData.properties || {} }
        }

        util.searchObjDate(data);

        this.consumer.add(data, callback);
    };

    return ta;
};

export function initTaLoggingMode() {
    return _createClient({
        add: (msg: string) => {
            taLogger.info(JSON.stringify(msg));
        }
    })
}