import io from "socket.io-client";
import _ from "underscore";
import remoteService from "../oss/remoteService";
import cache from "../utils/cache";

import { SocketType, SubscribeOptions, EmitOptions } from "./index.d";
import { SdkOptions } from "../index.d";

const PAGE_TYPE = {
  DASHBOARD: "dashboard",
  DASHBOARD_PAGE: "dashboardPage",
  UNKNOWN: "unknown",
};

const getPageType = () => {
  const { pathname } = window.location;

  if (pathname.startsWith("/proxr")) {
    return PAGE_TYPE.DASHBOARD_PAGE;
  }

  if (pathname.startsWith("/dashboard")) {
    return PAGE_TYPE.DASHBOARD;
  }

  return PAGE_TYPE.UNKNOWN;
};

const getAppInfo = () => {
  const pageType = getPageType();
  const { pathname } = window.location;
  const appInfo = pathname.split("/");

  switch (pageType) {
    case PAGE_TYPE.DASHBOARD:
      return [appInfo[1], "", appInfo[2]];
    case PAGE_TYPE.DASHBOARD_PAGE:
      return [appInfo[1], appInfo[3], appInfo[2]];
    default:
      return [appInfo[1], "", ""];
  }
};

const SERVER_MATCH: { [key: string]: string } = {
  spapi: "sp",
  spnextapi: "spnext",
};

const normalizeHost = (host: string) => {
  let originHost = host;

  Object.keys(SERVER_MATCH).forEach((key: string) => {
    originHost = originHost.replace(key, SERVER_MATCH[key]);
  });

  return originHost;
};

const getSpServer = (options: SdkOptions) => {
  const windowObj = window as any;

  if (options.server) {
    return options.server;
  }

  if (windowObj.appConfig) {
    if (windowObj.appConfig.env === "windows") {
      return `${normalizeHost(windowObj.appConfig.dashboardHost)}:${
        location.port
      }`;
    } else {
      return `${normalizeHost(windowObj.appConfig.dashboardHost)}`;
    }
  }

  return "";
};

const getUserId = (options: SdkOptions) => {
  options.server = options.tokenServer || "";
  return new Promise(function (resolve, reject) {
    if (options.userId) {
      return resolve({ id: options.userId });
    }

    const spServer = getSpServer(options);
    const spUrl = new URL("/user/get", `${location.protocol}//${spServer}/`);

    remoteService
      .get(spUrl.href, "json")
      .then(function (resp) {
        resolve(resp.data);
      })
      .catch(function (e) {
        resolve({ id: "unknown" });
      });
  });
};

const getHeaders = (userId: string, nodeId: string) => {
  if (!nodeId) {
    return {
      userId: userId,
      socketId: "dashboard",
    };
  }

  return {
    userId: userId,
    socketId: nodeId,
  };
};

export default function init(options: SdkOptions) {
  let s: SocketType = {};
  let socket: SocketIOClient.Socket = null;
  let inited = false;

  let defaultOpts = {
    url: "/",
    onConnect: console.info,
    onDisconnect: console.info,
  };
  options = Object.assign(defaultOpts, options);

  cache.initEvent();

  function initSocket(opts: SdkOptions, userId: string, nodeId: string) {
    socket = io(opts.url, {
      ...opts,
      query: getHeaders(userId, nodeId),
    });

    socket.on("connect", function () {
      console.log("socket connected", socket.id);

      if (!inited) {
        send({
          eventName: "system",
          data: JSON.stringify({
            data: "dashboardRefresh",
            socketId: socket.id,
          }),
        });
        inited = true;
      }

      if (opts.onConnect) {
        opts.onConnect();
      }
    });
    socket.on("disconnect", function () {
      console.log("socket disconnected");
      if (opts.onDisconnect) {
        opts.onDisconnect();
      }
    });

    return socket;
  }

  function getSocket(opts: SdkOptions) {
    return new Promise((resolve, reject) => {
      if (socket == null) {
        getUserId(opts).then(function (resp: any) {
          const [, nodeId] = getAppInfo();

          initSocket(opts, resp.id, nodeId);

          resolve(socket);
        });
      } else {
        resolve(socket);
      }
    });
  }

  function handleCacheData(opts: any, data: any) {
    cache.set(opts.eventName, data);
  }

  function checkSocketIdInData(data: any, socketId: string) {
    if (data.extra && _.isString(data.extra)) {
      data.extra = JSON.parse(data.extra);
    }

    const targetSocketId = _.get(data, ["extra", "global", "socketId"], "");

    return !targetSocketId || `${targetSocketId}` === `${socketId}`;
  }

  function subscribe(opts: SubscribeOptions) {
    let defaultOpts = {
      eventName: "call",
    };
    opts = Object.assign(defaultOpts, opts);
    if (!opts.callback) {
      throw Error("Callback is not defined.");
    }

    cache.addHandler(opts.eventName, opts.callback);

    getSocket(options).then(function (socket: SocketIOClient.Socket) {
      socket.on(opts.eventName, (data: any, ...extra: any) => {
        if (checkSocketIdInData(data, socket.id)) {
          opts.callback.call(null, data, ...extra);
        }
      });

      // socket.on(opts.eventName, function (data: any) {
      //   if (checkSocketIdInData(data, socket.id)) {
      //     handleCacheData(opts, data);
      //   }
      // });
    });
  }

  function send(opts: EmitOptions) {
    let defaultOpts = {
      eventName: "call",
    };
    opts = Object.assign(defaultOpts, opts);
    if (!opts.data) {
      throw Error("No data to be send.");
    }
    getSocket(options).then(function (socket: SocketIOClient.Socket) {
      if (_.isObject(opts) && _.isObject(opts.data)) {
        if (!opts.data) {
          opts.data = {};
        }
        if (!opts.data.extra) {
          opts.data.extra = {};
        }
        if (!opts.data.extra.global) {
          opts.data.extra.global = {};
        }
        opts.data.extra.global.socketId = socket.id;
      }

      socket.emit(opts.eventName, opts.data);
    });
  }

  function sendFile(oss_path: string, files: File[] = []) {
    if (s.oss) {
      return new Promise((resolve, reject) => {
        try {
          const filePromise = [];
          const keys: string[] = [];
          for (let curr = 0; curr < files.length; curr++) {
            const key = oss_path + files[curr].name;
            keys.push(key);
            filePromise.push(s.oss.putObject(key, files[curr]));
          }

          Promise.all(filePromise)
            .then(
              (results) => {
                const failed = results.find(
                  (result) => result.success === false
                );
                if (failed) {
                  reject(failed.msg);
                } else {
                  send({ eventName: "fileUpload", data: keys });
                  resolve(keys);
                }
              },
              (error) => {
                reject(error);
              }
            )
            .catch((err) => {
              reject(err);
            });
        } catch (err) {
          reject(err);
        }
      });
    }
  }

  function throttle(throttleOpts: any) {
    let defaultThrottleOpts = {
      argsMergeFunc: function (args: any[]) {
        return args[args.length - 1];
      },
    };
    throttleOpts = Object.assign(defaultThrottleOpts, throttleOpts);

    if (!throttleOpts.timeout) {
      return throttleOpts.func;
    }

    let throttleMapping: any = {};

    return function (opts: any) {
      let _this = this;
      let defaultOpts = {
        eventName: "call",
      };
      opts = Object.assign(defaultOpts, opts);
      if (!opts.data) {
        throw Error("No data to be send.");
      }

      throttleMapping[opts.eventName] = throttleMapping[opts.eventName] || {
        args: [],
      };
      throttleMapping[opts.eventName].args.push(opts);
      if (throttleMapping[opts.eventName].timer) {
        clearTimeout(throttleMapping[opts.eventName].timer);
      }
      return new Promise(function (resolve, reject) {
        throttleMapping[opts.eventName].timer = setTimeout(function () {
          try {
            let mergedOpts = throttleOpts.argsMergeFunc(
              throttleMapping[opts.eventName].args
            );
            let result = throttleOpts.func.call(_this, mergedOpts);
            resolve(result);
          } catch (err) {
            reject(err);
          } finally {
            throttleMapping[opts.eventName].args = [];
          }
        }, throttleOpts.timeout);
      });
    };
  }

  function setUserId(userId: string) {
    if (socket) {
      socket.disconnect();
    }

    return getSocket({ ...options, userId });
  }

  if (options.path === undefined) {
    return Promise.resolve(null);
  }

  s = {
    socket,
    subscribe: subscribe,
    send: send,
    sendFile: sendFile,
    throttle: throttle,
    defaultThrottleSend: throttle({ func: send, timeout: 1000 }),
    setUserId,
  };

  return new Promise((resolve, reject) => {
    getSocket(options).then(
      (socket) => {
        (window as any).spSocket = s;
        (window as any).socket = socket;
        resolve(s);
      },
      (err) => reject(err)
    );
  });
}
