'use strict';

const Service = require('egg').Service;
const puppeteer = require('puppeteer');
const fs = require('fs');
const path = require('path');

const validate = result => {
  if (result.code !== 2000) throw new Error(result && result.msg || '服务器异常');
  return result.data;
};

class HomeService extends Service {
  async getMails() {
    const { ctx } = this;
    const limit = 1200;
    ctx.logger.info(`${ctx.app.config.legal.host}/api/email/get/${limit}`);
    const { data: result } = await ctx.curl(`${ctx.app.config.legal.host}/api/email/get/${limit}`, {
      dataType: 'json',
      method: 'POST',
      timeout: [ 30000, 30000 ],
    });

    return validate(result);
  }

  async updateStatus(mails) {
    const { ctx } = this;
    ctx.logger.info(`${ctx.app.config.legal.host}/api/email/callback`);
    const { data: result } = await ctx.curl(`${ctx.app.config.legal.host}/api/email/callback`, {
      contentType: 'json',
      dataType: 'json',
      method: 'POST',
      data: mails,
      timeout: [ 30000, 30000 ],
    });

    return validate(result);
  }

  async schedule() {
    const { ctx, app } = this;
    const resource = 'locks:puppeteer';
    const ttl = 60 * 1000;
    let lock = null;
    ctx.logger.info('发送任务开始');

    try {
      lock = await app.redlock9.lock(resource, ttl);
      ctx.logger.info('获取锁成功');
      const accounts = await this.getMailsAccount();
      if (accounts.length) {
        const mails = await this.getMails();
        const divideMailLength = Math.ceil(mails.length / accounts.length);
        const divideMails = accounts.map((v, index) => ({ ...v, mails: mails.slice(divideMailLength * index, divideMailLength * (index + 1)) }));
        const results = await Promise.all(divideMails.map(v => this.sendMail(v)));
        this.ctx.logger.info(results.flat());
        await this.updateStatus(results.flat());
      }
      if (lock) lock.unlock();
    } catch (e) {
      ctx.logger.error((e && e.message) || '出现未知错误');
      if (!lock) {
        ctx.logger.info('未获取到锁');
        return;
      }
      lock.unlock();
    }
  }

  async getMailsAccount() {
    const { ctx } = this;
    return ctx.app.config.tecent.filter(account => {
      const mailsAmountPath = `../public/data/mails_amount_${account.index}.json`;
      const { MAX_AMOUNT, CURRENT_AMOUNT } = JSON.parse(fs.readFileSync(path.join(__dirname, mailsAmountPath)).toString());
      return CURRENT_AMOUNT < MAX_AMOUNT;
    });
  }

  async sendMail({ mails = [], account, password, index }) {
    const { ctx, service } = this;
    const result = [];
    let browser = null;
    const cookiePath = `../public/data/cookies_account_${index}.json`;
    const mailsAmountPath = `../public/data/mails_amount_${index}.json`;
    const browserConfig = {
      args: [ '--no-sandbox', '--disable-setuid-sandbox' ],
      headless: true,
      defaultViewport: {
        width: 1024,
        height: 1000,
      },
    };
    if (!mails.length) return result;

    // 超额限制
    const { MAX_AMOUNT, CURRENT_AMOUNT } = JSON.parse(fs.readFileSync(path.join(__dirname, mailsAmountPath)).toString());
    if (CURRENT_AMOUNT > MAX_AMOUNT) {
      ctx.logger.error('发信数量已超额');
      return result;
    }

    if (CURRENT_AMOUNT + mails.length > MAX_AMOUNT) {
      mails = mails.slice(0, MAX_AMOUNT - CURRENT_AMOUNT);
    }

    // 获取cookie
    let cookie = fs.readFileSync(path.join(__dirname, cookiePath)).toString();

    try {
      browser = await puppeteer.launch(browserConfig);
      const page = await browser.newPage();
      if (cookie) await ctx.helper.addCookies(JSON.parse(cookie), page);
      await page.goto('https://exmail.qq.com');
      ctx.logger.info('tecent mail: 进入https://exmail.qq.com');
      const btnlogin = await page.$('.index_topbar_btn_login');
      const btlogin = await page.$('#btlogin');

      // 未登录状态先进行登录
      if (btnlogin || btlogin) {
        ctx.logger.info('tecent mail: 未登录进行登录');
        if (btnlogin) {
          await page.goto('https://exmail.qq.com/login');
          await page.click('.js_show_pwd_panel');
        }
        await page.$eval('#inputuin', input => { input.value = ''; });
        await page.type('#inputuin', account);
        await page.type('#pp', password);
        await page.click('#auto_login_in_five_days_pwd');
        await page.click('#btlogin');
        await page.waitForNavigation({ waitUntil: 'load' }); // 等待页面加载出来，等同于window.onload
        let VerifyArea = await page.$('#VerifyArea');
        if (VerifyArea) {
          let j = 0;
          ctx.logger.warn('tecent mail: 登录需要输入验证码!');
          while (VerifyArea && j < 100) {
            j++;
            ctx.logger.warn(`tecent mail: 第${j}次输入验证码!`);
            await ctx.helper.writeTecentLoginCode(page, account, password);
            await page.waitFor(5000);
            VerifyArea = await page.$('#VerifyArea');
          }
          if (j === 100) throw new Error('验证码尝试过多');
        }
        cookie = await page.cookies('https://exmail.qq.com');
        fs.writeFileSync(path.join(__dirname, cookiePath), JSON.stringify(cookie));
      }
      ctx.logger.info('tecent mail: 已登录');
      for (let i = 0; i < mails.length; i++) {
        const email = mails[i].email;
        ctx.logger.info(`tecent mail: 准备发送第${i + 1}封邮件 mail: ${email}, id: ${mails[i].id}, account: ${account}`);
        await page.click('#composebtn');
        const mainFrame = await page.frames().find(f => f.name() === 'mainFrame');
        await mainFrame.waitFor(3000);
        await mainFrame.waitFor('#subject');
        await mainFrame.waitFor('.addr_text>input');
        await mainFrame.type('.addr_text>input', email);
        await mainFrame.type('#subject', mails[i].subject);
        const bodyFrame = await mainFrame.childFrames()[2];
        await bodyFrame.type('body', mails[i].emailContent);
        await mainFrame.click('input[name="sendbtn"]');

        await mainFrame.waitFor(3000);
        const isNeedCode = await page.$('#QMVerify_s_vfcode');
        if (isNeedCode) {
          fs.writeFileSync(path.join(__dirname, mailsAmountPath), JSON.stringify({ MAX_AMOUNT, CURRENT_AMOUNT: MAX_AMOUNT }));
          throw new Error('发送邮件需要输入验证码！');
        }

        const isAccountValid = await page.$('#QMconfirm_s__title_');
        if (isAccountValid) {
          const dialogInfo = await page.$eval('.dialog_f_c', el => el.innerHTML);
          await page.click('#QMconfirm_s_confirm');
          result.push({ id: mails[i].id, code: 1, msg: '投递失败' });
          await page.click('#composebtn');
          await page.waitFor(3000);
          await page.click('#composeExitAlert_s_btn_delete_save');
          ctx.logger.error(dialogInfo || '邮件地址不正确');
          continue;
        }

        await mainFrame.waitFor('#readSendbox>a');
        await mainFrame.click('#readSendbox>a');
        await mainFrame.waitFor(5000);
        const mailSendStatusFrame = await mainFrame.childFrames().find(f => f.name() === 'mailSendStatus');
        await mailSendStatusFrame.waitFor('#statusbtn');
        await mailSendStatusFrame.waitFor('#clickstu');
        await mailSendStatusFrame.click('#clickstu');
        let sendStatus = await mailSendStatusFrame.$eval('#statusbtn', el => el.innerHTML);
        const receiver = await mailSendStatusFrame.$eval('.oneline', el => el.innerHTML);

        if (sendStatus === '正在投递') {
          await mailSendStatusFrame.waitFor(60000);
          const clickStatus = await mailSendStatusFrame.$eval('#clickstu', el => el.innerHTML);
          if (clickStatus === '[查看详情]') await mailSendStatusFrame.click('#clickstu');
          await mailSendStatusFrame.click('#refreshbutton');
          await mailSendStatusFrame.waitFor(5000);
          sendStatus = await mailSendStatusFrame.$eval('#statusbtn', el => el.innerHTML);
        }

        await mailSendStatusFrame.click('#statusbtn');

        if (sendStatus !== '投递成功') {
          result.push({ id: mails[i].id, code: 1, msg: '投递失败' });
          ctx.logger.error(sendStatus || '投递失败');
          continue;
        }

        if (!receiver.includes(email) && !receiver.includes(email.toLowerCase())) {
          ctx.logger.error('收件箱不一致, 投递失败');
          continue;
        }

        const mainFrameContainer = await page.$('#mainFrameContainer');
        const imageName = `${email}_${Date.now()}.png`;
        await mainFrameContainer.screenshot({ path: `./app/public/images/${imageName}` });
        const imagePath = await service.fdfs.upload(path.join(__dirname, `../public/images/${imageName}`));
        result.push({ id: mails[i].id, code: 0, url: imagePath });
        await mainFrame.waitFor(parseInt((Math.random() * 3 + 1) * 1000, 10));
      }
    } catch (e) {
      await service.dingTalk.push(e && e.message);
      ctx.logger.error(e);
    }

    fs.writeFileSync(path.join(__dirname, mailsAmountPath), JSON.stringify({ MAX_AMOUNT, CURRENT_AMOUNT: CURRENT_AMOUNT + result.length }));

    if (browser) await browser.close();

    return result;
  }

  async publisher() {
    // const ch = await this.app.amqplib.createChannel();
    // await ch.assertQueue(queueName, { durable: false });
    // for (let i = 0; i < 5; i++) {
    //   await ch.sendToQueue(queueName, Buffer.from(`第${i}条消息`));
    // }
    // await ch.close();
  }

  async consumer() {
    // let ch;
    // try {
    //   ch = await this.app.amqplib.createChannel();
    //   await ch.assertQueue(queueName, { durable: false });
    //   await ch.prefetch(1, false);
    //   let i = 0;
    //   await ch.consume(queueName, msg => {
    //     try {
    //       i++;
    //       console.log('consumer', msg.content.toString());
    //       if (i === 1) { throw new Error('出现异常'); }
    //       ch.ack(msg);
    //     } catch (err) {
    //       this.ctx.logger.error(err);
    //       ch.close();
    //     }
    //   }, { noAck: false });
    // } catch (err) {
    //   this.ctx.logger.error(err);
    //   if (ch) ch.close();
    // }
  }

  async refreshMailsAmount() {
    this.ctx.app.config.tecent.forEach(v => {
      const mailsAmountPath = `../public/data/mails_amount_${v.index}.json`;
      const { MAX_AMOUNT } = JSON.parse(fs.readFileSync(path.join(__dirname, mailsAmountPath)).toString());
      fs.writeFileSync(path.join(__dirname, mailsAmountPath), JSON.stringify({ MAX_AMOUNT, CURRENT_AMOUNT: 0 }));
    });
  }
}

module.exports = HomeService;
