import axios from 'axios';
import { marked } from 'marked';
import markdownToTxt from 'markdown-to-txt';

export interface NoticeOptions {
  /**
   * bark通知方式的参数配置
   */
  bark?: {
    /**
     * url 用于点击通知后跳转的地址
     */
    url?: string;
  };
  /**
   * IFTTT通知方式的参数配置
   */
  ifttt?: {
    value1?: string;
    value2?: string;
    value3?: string;
  };
  /**
   * Discord通知方式的参数配置
   */
  discord?: {
    userName?: string;
    avatarUrl?: string;
  };
  /**
   * WxPusher通知方式的参数配置
   */
  wxpusher?: {
    uids?: string[];
    url?: string;
    verifyPay?: boolean;
  };
  /**
   * QMsg酱通知方式的参数配置
   */
  qmsg?: {
    qq?: string;
    url?: string;
    group?: boolean;
    bot?: string;
  };
  dingtalk?: {
    /**
     * 消息类型，目前支持 text、markdown。不设置，默认为 text。
     */
    msgtype?: string;
  };
}
export interface CommonOptions {
  token: string;
  title?: string;
  content: string;
  /**
   * 扩展选项
   */
  options?: NoticeOptions;
}

export type ChannelType =
  | 'qmsg'
  | 'serverchan'
  | 'serverchain'
  | 'pushplus'
  | 'pushplushxtrip'
  | 'dingtalk'
  | 'wecom'
  | 'bark'
  | 'gocqhttp'
  | 'atri'
  | 'pushdeer'
  | 'igot'
  | 'telegram'
  | 'feishu'
  | 'ifttt'
  | 'wecombot'
  | 'discord'
  | 'wxpusher';

function checkParameters(options: any, requires: string[] = []) {
  requires.forEach((require) => {
    if (!options[require]) {
      throw new Error(`${require} is required`);
    }
  });
}

function getHtml(content: string) {
  return marked.parse(content);
}

function getTxt(content: string) {
  return markdownToTxt(content);
}

function getTitle(content: string) {
  return getTxt(content).split('\n')[0];
}

function removeUrlAndIp(content: string) {
  const urlRegex = /(https?:\/\/[^\s]+)/g;
  const ipRegex = /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/g;
  // 邮箱正则表达式来自 https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email#validation
  const mailRegExp = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*/g;
  return content
    .replace(urlRegex, '')
    .replace(ipRegex, '')
    .replace(mailRegExp, '');
}

/**
 * https://qmsg.zendee.cn/
 */
async function noticeQmsg(options: CommonOptions) {
  checkParameters(options, ['token', 'content']);
  const url = options?.options?.qmsg?.url || 'https://qmsg.zendee.cn';
  let msg = getTxt(options.content);
  if (options.title) {
    msg = `${options.title}\n${msg}`;
  }
  // 移除网址和 IP 以避免 Qmsg 酱被 Tencent 封号
  msg = removeUrlAndIp(msg);
  const param = new URLSearchParams({ msg });
  const qq = options?.options?.qmsg?.qq || false;
  if (qq) {
    param.append('qq', qq);
  }
  const bot = options?.options?.qmsg?.bot || false;
  if (bot) {
    param.append('bot', bot);
  }
  const group = options?.options?.qmsg?.group || false;
  const response = await axios.post(`${url}/${group ? 'group' : 'send'}/${options.token}`, param.toString(), {
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  });
  return response.data;
}

/**
 * https://github.com/Tianli0/push-bot-api/
 */
async function noticeAtri(options: CommonOptions) {
  checkParameters(options, ['token', 'content']);
  const url = 'http://pushoo.tianli0.top/';
  let message = getTxt(options.content);
  if (options.title) {
    message = `${options.title}\n${message}`;
  }
  const param = new URLSearchParams({
    user_id: options.token,
    message,
  });
  const response = await axios.post(url, param.toString(), {
    headers: { 'X-Requested-By': 'pushoo' },
  });
  return response.data;
}

/**
 * Turbo: https://sct.ftqq.com/
 * V3: https://sc3.ft07.com/
 */
async function noticeServerChan(options: CommonOptions) {
  checkParameters(options, ['token', 'content']);
  let url: string;
  let param: URLSearchParams;
  if (options.token.startsWith('sctp')) {
    url = `https://${options.token.match(/^sctp(\d+)t/)[1]}.push.ft07.com/send`;
    param = new URLSearchParams({
      title: options.title || getTitle(options.content),
      desp: options.content,
    });
  } else if (options.token.substring(0, 3).toLowerCase() === 'sct') {
    url = 'https://sctapi.ftqq.com';
    param = new URLSearchParams({
      title: options.title || getTitle(options.content),
      desp: options.content,
    });
  } else {
    url = 'https://sc.ftqq.com';
    param = new URLSearchParams({
      text: options.title || getTitle(options.content),
      desp: options.content,
    });
  }
  const response = await axios.post(`${url}/${options.token}.send`, param.toString(), {
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  });
  return response.data;
}

/**
 * https://www.pushplus.plus/
 */
async function noticePushPlus(options: CommonOptions) {
  checkParameters(options, ['token', 'content']);
  const ppApiUrl = 'http://www.pushplus.plus/send';
  const ppApiParam = {
    token: options.token,
    title: options.title || getTitle(options.content),
    content: options.content,
    template: 'markdown',
  };
  const response = await axios.post(ppApiUrl, ppApiParam);
  return response.data;
}

/**
 * https://pushplus.hxtrip.com/
 */
async function noticePushPlusHxtrip(options: CommonOptions) {
  checkParameters(options, ['token', 'content']);
  const ppApiUrl = 'http://pushplus.hxtrip.com/send';
  const ppApiParam = {
    token: options.token,
    title: options.title || getTitle(options.content),
    content: getHtml(options.content),
    template: 'html',
  };
  const response = await axios.post(ppApiUrl, ppApiParam);
  return response.data;
}

/**
 * 文档: https://open.dingtalk.com/document/group/custom-robot-access
 * 教程: https://blog.ljcbaby.top/article/Twikoo-DingTalk/
 */
async function noticeDingTalk(options: CommonOptions) {
  checkParameters(options, ['token', 'content']);
  let url = 'https://oapi.dingtalk.com/robot/send?access_token=';
  if (options.token.substring(0, 4).toLowerCase() === 'http') {
    url = options.token;
  } else {
    url += options.token;
  }

  const msgtype = options.options?.dingtalk?.msgtype || 'text';
  const content = msgtype === 'text'
    ? (options.title ? `${options.title}\n` : '') + getTxt(options.content)
    : options.content;

  const msgBody = {
    msgtype,
  };

  if (msgtype === 'text') {
    msgBody[msgtype] = { content };
  } else if (msgtype === 'markdown') {
    msgBody[msgtype] = { title: options.title || getTitle(options.content), text: content };
  }
  const response = await axios.post(url, msgBody);
  return response.data;
}

/**
 * 文档: https://developer.work.weixin.qq.com/document/path/90236
 * 教程: https://sct.ftqq.com/forward
 */
async function noticeWeCom(options: CommonOptions) {
  checkParameters(options, ['token', 'content']);
  const [corpid, corpsecret, agentid, touser = '@all'] = options.token.split('#');
  checkParameters(
    {
      corpid,
      corpsecret,
      agentid,
    },
    ['corpid', 'corpsecret', 'agentid'],
  );
  // 获取 Access Token
  let accessToken;
  try {
    const accessTokenRes = await axios.get(
      `https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${corpid}&corpsecret=${corpsecret}`,
    );
    accessToken = accessTokenRes.data.access_token;
  } catch (e) {
    console.error('获取企业微信 access token 失败，请检查 token', e);
    return {};
  }
  // 发送消息
  const url = `https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${accessToken}`;
  let content = getTxt(options.content);
  if (options.title) {
    content = `${options.title}\n${content}`;
  }
  const param = {
    touser,
    msgtype: 'text',
    agentid,
    text: { content },
  };
  const response = await axios.post(url, param);
  return response.data;
}

/**
 * https://github.com/Finb/Bark
 */
async function noticeBark(options: CommonOptions) {
  checkParameters(options, ['token', 'content']);
  let url = 'https://api.day.app/';
  if (options.token.substring(0, 4).toLowerCase() === 'http') {
    url = options.token;
  } else {
    url += options.token;
  }
  if (!url.endsWith('/')) url += '/';
  const title = encodeURIComponent(options.title || getTitle(options.content));
  const content = encodeURIComponent(getTxt(options.content));
  const params = new URLSearchParams({
    url: options?.options?.bark?.url || '',
  });
  const response = await axios.get(`${url}${title}/${content}/`, { params });
  return response.data;
}

/**
 * 文档: https://docs.go-cqhttp.org/api/
 * 教程: https://twikoo.js.org/QQ_API.html
 */
async function noticeGoCqhttp(options: CommonOptions) {
  checkParameters(options, ['token', 'content']);
  const url = options.token;
  let message = getTxt(options.content);
  if (options.title) {
    message = `${options.title}\n${message}`;
  }
  const param = new URLSearchParams({ message });
  const response = await axios.post(url, param.toString());
  return response.data;
}

async function noticePushdeer(options: CommonOptions) {
  checkParameters(options, ['token', 'content']);
  const url = 'https://api2.pushdeer.com/message/push';
  const response = await axios.post(url, {
    pushkey: options.token,
    text: options.title || getTitle(options.content),
    desp: options.content,
  });
  return response.data;
}

async function noticeIgot(options: CommonOptions) {
  checkParameters(options, ['token', 'content']);
  const url = `https://push.hellyw.com/${options.token}`;
  const response = await axios.post(url, {
    title: options.title || getTitle(options.content),
    content: getTxt(options.content),
  });
  return response.data;
}

/**
 * 文档: https://core.telegram.org/method/messages.sendMessage
 * 教程: https://core.telegram.org/bots#3-how-do-i-create-a-bot
 */
async function noticeTelegram(options: CommonOptions) {
  checkParameters(options, ['token', 'content']);
  const [tgToken, chatId] = options.token.split('#');
  checkParameters(
    {
      tgToken,
      chatId,
    },
    ['tgToken', 'chatId'],
  );
  let text = options.content.replace(/([*_])/g, '\\$1'); // * 和 _ 似乎需要转义，否则会抛出 400 Bad Request 以及消息显示不正常
  if (options.title) {
    text = `${options.title}\n\n${text}`;
  }
  const response = await axios.post(`https://api.telegram.org/bot${tgToken}/sendMessage`, {
    text,
    chat_id: chatId,
    parse_mode: 'Markdown',
  });
  return response.data;
}

/**
 * https://www.feishu.cn/hc/zh-CN/articles/360024984973
 */
async function noticeFeishu(options: CommonOptions) {
  checkParameters(options, ['token', 'content']);
  const v1 = 'https://open.feishu.cn/open-apis/bot/hook/';
  const v2 = 'https://open.feishu.cn/open-apis/bot/v2/hook/';
  let url;
  let params;
  if (options.token.substring(0, 4).toLowerCase() === 'http') {
    url = options.token;
  } else {
    url = v2 + options.token;
  }
  if (url.substring(0, v1.length) === v1) {
    params = {
      title: options.title || getTitle(options.content),
      text: getTxt(options.content),
    };
  } else {
    let text = getTxt(options.content);
    if (options.title) {
      text = `${options.title}\n${text}`;
    }
    params = {
      msg_type: 'text',
      content: { text },
    };
  }
  const response = await axios.post(url, params);
  return response.data;
}

/**
 * https://ifttt.com/maker_webhooks
 * http://ift.tt/webhooks_faq
 */
async function noticeIfttt(options: CommonOptions) {
  checkParameters(options, ['token', 'content']);

  const [token, eventName] = options.token.split('#');
  checkParameters(
    {
      token,
      eventName,
    },
    ['token', 'eventName'],
  );

  const url = `https://maker.ifttt.com/trigger/${eventName}/with/key/${token}`;

  const response = await axios.post(
    url,
    {
      value1: options.options?.ifttt?.value1 || getTxt(options.title),
      value2: options.options?.ifttt?.value2 || getTxt(options.content),
      value3: options.options?.ifttt?.value3,
    },
    {
      headers: { 'Content-Type': 'application/json' },
    },
  );
  return response.data;
}

/**
 * 文档: https://developer.work.weixin.qq.com/document/path/91770
 * 教程: https://developer.work.weixin.qq.com/tutorial/detail/54
 */
async function noticeWecombot(options: CommonOptions) {
  checkParameters(options, ['token', 'content']);
  const url = `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${options.token}`;
  const content = getTxt(options.content);

  const response = await axios.post(
    url,
    {
      msgtype: 'text',
      text: {
        content,
      },
    },
    {
      headers: { 'Content-Type': 'application/json' },
    },
  );

  return response.data;
}

/**
 * 文档：https://discord.com/developers/docs/resources/webhook#execute-webhook
 */
async function noticeDiscord(options: CommonOptions) {
  checkParameters(options, ['token', 'content']);
  const url = options.token.startsWith('https://')
    ? options.token
    : `https://discord.com/api/webhooks/${options.token.replace(/#/, '/')}`;

  const response = await axios.post(
    url,
    {
      content: options.content,
      username: options.options?.discord?.userName,
      avatar_url: options.options?.discord?.avatarUrl,
    },
    {
      headers: { 'Content-Type': 'application/json' },
    },
  );
  return `Delivered successfully, code ${response.status}.`;
}

/**
 * WXPusher 推送
 * 教程：https://wxpusher.zjiecode.com/admin/
 * 文档: https://wxpusher.zjiecode.com/docs/#/
 */
async function noticeWxPusher(options: CommonOptions) {
  checkParameters(options, ['token', 'content']);
  const url = 'http://wxpusher.zjiecode.com/api/send/message';
  const [appToken, topicIds] = options.token.split('#');
  checkParameters({ appToken, topicIds }, ['appToken', 'topicIds']);

  const response = await axios.post(
    url,
    {
      appToken,
      content: options.content,
      summary: options.title || getTitle(options.content),
      contentType: 3,
      topicIds: topicIds.split(',').map((id) => Number(id)),
      uids: options?.options?.wxpusher?.uids || [],
      url: options?.options?.wxpusher?.url || '',
      verifyPayload: options?.options?.wxpusher?.verifyPay || false,
    },
    {
      headers: {
        'Content-Type': 'application/json',
      },
    },
  );
  return response.data;
}

async function notice(channel: ChannelType, options: CommonOptions) {
  try {
    let data: any;
    const noticeFn = {
      qmsg: noticeQmsg,
      serverchan: noticeServerChan,
      serverchain: noticeServerChan,
      pushplus: noticePushPlus,
      pushplushxtrip: noticePushPlusHxtrip,
      dingtalk: noticeDingTalk,
      wecom: noticeWeCom,
      bark: noticeBark,
      gocqhttp: noticeGoCqhttp,
      atri: noticeAtri,
      pushdeer: noticePushdeer,
      igot: noticeIgot,
      telegram: noticeTelegram,
      feishu: noticeFeishu,
      ifttt: noticeIfttt,
      wecombot: noticeWecombot,
      discord: noticeDiscord,
      wxpusher: noticeWxPusher,
    }[channel.toLowerCase()];
    if (noticeFn) {
      data = await noticeFn(options);
    } else {
      throw new Error(`<${channel}> is not supported`);
    }
    console.debug(`[PUSHOO] Send to <${channel}> result:`, data);
    return data;
  } catch (e) {
    console.error('[PUSHOO] Got error:', e.message);
    return { error: e };
  }
}

export default notice;

export {
  notice,
  noticeQmsg,
  noticeServerChan,
  noticePushPlus,
  noticePushPlusHxtrip,
  noticeDingTalk,
  noticeWeCom,
  noticeBark,
  noticeGoCqhttp,
  noticeAtri,
  noticePushdeer,
  noticeIgot,
  noticeTelegram,
  noticeFeishu,
  noticeIfttt,
  noticeWecombot,
  noticeDiscord,
  noticeWxPusher,
};
