const core = require('@actions/core');
const github = require('@actions/github');
const { Octokit } = require('@octokit/rest');

// **************************************************************************
const ALLREACTIONS = ['+1', '-1', 'laugh', 'confused', 'heart', 'hooray', 'rocket', 'eyes'];

const { doQueryIssues } = require('./public.js');

const {
  dealStringToArr,
  dealRandomAssignees,
  testDuplicate,
  checkPermission,
} = require('./util.js');

// **************************************************************************
const token = core.getInput('token');
const octokit = new Octokit({ auth: `token ${token}` });
const context = github.context;

const contents = core.getInput('contents');

const randomTo = core.getInput('random-to');

// **************************************************************************
async function doAddAssignees(owner, repo, issueNumber, assignees) {
  const arr = dealRandomAssignees(assignees, randomTo);
  await octokit.issues.addAssignees({
    owner,
    repo,
    issue_number: issueNumber,
    assignees: arr,
  });
  core.info(`Actions: [add-assignees][${arr}] success!`);
}

async function doAddLabels(owner, repo, issueNumber, labels) {
  await octokit.issues.addLabels({
    owner,
    repo,
    issue_number: issueNumber,
    labels: dealStringToArr(labels),
  });
  core.info(`Actions: [add-labels][${labels}] success!`);
}

async function doCloseIssue(owner, repo, issueNumber) {
  await octokit.issues.update({
    owner,
    repo,
    issue_number: issueNumber,
    state: 'closed',
  });
  core.info(`Actions: [close-issue][${issueNumber}] success!`);
}

async function doCreateComment(owner, repo, issueNumber, body) {
  const { data } = await octokit.issues.createComment({
    owner,
    repo,
    issue_number: issueNumber,
    body,
  });
  core.info(`Actions: [create-comment][${body}] success!`);
  core.setOutput('comment-id', data.id);

  if (contents) {
    await doCreateCommentContent(owner, repo, data.id, dealStringToArr(contents));
  }
}

async function doCreateCommentContent(owner, repo, commentId, contents) {
  if (contents.length) {
    contents.forEach(async item => {
      if (testContent(item)) {
        await octokit.reactions.createForIssueComment({
          owner,
          repo,
          comment_id: commentId,
          content: item,
        });
        core.info(`Actions: [create-reactions][${item}] success!`);
      }
    });
  }
}

async function doCreateIssue(owner, repo, title, body, labels, assignees) {
  let params = {
    owner,
    repo,
    title,
    body,
    labels: dealStringToArr(labels),
    assignees: dealRandomAssignees(assignees, randomTo),
  };

  const { data } = await octokit.issues.create(params);
  core.info(`Actions: [create-issue][${title}] success!`);
  core.setOutput('issue-number', data.number);

  if (contents) {
    await doCreateIssueContent(owner, repo, data.number, dealStringToArr(contents));
  }
}

async function doCreateIssueContent(owner, repo, issueNumber, contents) {
  if (contents.length) {
    contents.forEach(async item => {
      if (testContent(item)) {
        await octokit.reactions.createForIssue({
          owner,
          repo,
          issue_number: issueNumber,
          content: item,
        });
        core.info(`Actions: [create-reactions][${item}] success!`);
      }
    });
  }
}

async function doCreateLabel(owner, repo) {
  const name = core.getInput('label-name');
  const color = core.getInput('label-color') || 'ededed';
  const description = core.getInput('label-desc') || '';

  if (!name) {
    core.setFailed(`This actions should input 'label-name'!`);
    return false;
  }

  try {
    await octokit.issues.createLabel({
      owner,
      repo,
      name,
      color,
      description,
    });
    core.info(`Actions: [create-label][${name}] success!`);
  } catch (err) {
    console.log(err.message);
  }
}

async function doDeleteComment(owner, repo, commentId) {
  let id = commentId;

  const outComments = core.getInput('out-comments');
  if (outComments) {
    const outCommentsArr = JSON.parse(outComments);
    core.info(`Actions: [out-comments-length][${outCommentsArr.length}] success!`);
    outCommentsArr.forEach(async item => {
      await doDelet(item.id);
    });
  } else {
    await doDelet(id);
  }

  async function doDelet(id) {
    await octokit.issues.deleteComment({
      owner,
      repo,
      comment_id: id,
    });
    core.info(`Actions: [delete-comment][${id}] success!`);
  }
}

async function doLockIssue(owner, repo, issueNumber) {
  const lockReason = core.getInput('lock-reason');
  let params = {
    owner,
    repo,
    issue_number: issueNumber,
  };
  const reasons = ['off-topic', 'too heated', 'resolved', 'spam'];
  if (lockReason && reasons.includes(lockReason)) {
    params.lock_reason = lockReason;
  }
  await octokit.issues.lock(params);
  core.info(`Actions: [lock-issue][${issueNumber}] success!`);
}

async function doMarkDuplicate(owner, repo, labels) {
  if (context.eventName != 'issue_comment') {
    core.info(`This actions only support on 'issue_comment'!`);
    return false;
  }

  if (context.payload.action == 'created' || context.payload.action == 'edited') {
    const duplicateCommand = core.getInput('duplicate-command');
    const duplicateLabels = core.getInput('duplicate-labels');
    const removeLables = core.getInput('remove-labels');
    const closeIssue = core.getInput('close-issue');
    const requirePermission = core.getInput('require-permission') || 'write';

    const commentId = context.payload.comment.id;
    const commentBody = context.payload.comment.body;
    const commentUser = context.payload.comment.user.login;
    const issueNumber = context.payload.issue.number;

    const ifCommandInput = !!duplicateCommand;

    if (
      !commentBody.includes('?') &&
      ((ifCommandInput &&
        commentBody.startsWith(duplicateCommand) &&
        commentBody.split(' ')[0] == duplicateCommand) ||
        testDuplicate(commentBody))
    ) {
      try {
        const res = await octokit.repos.getCollaboratorPermissionLevel({
          owner,
          repo,
          username: commentUser,
        });
        const { permission } = res.data;
        if (!checkPermission(requirePermission, permission)) {
          core.info(`The user ${commentUser} is not allow!`);
          return false;
        }

        if (ifCommandInput) {
          const nextBody = commentBody.replace(duplicateCommand, 'Duplicate of');
          await doUpdateComment(owner, repo, commentId, nextBody, 'replace', true);
        } else if (contents) {
          await doCreateCommentContent(owner, repo, commentId, dealStringToArr(contents));
        }

        const issue = await octokit.issues.get({
          owner,
          repo,
          issue_number: issueNumber,
        });
        let newLabels = [];
        if (issue.data.labels.length > 0) {
          newLabels = issue.data.labels
            .map(({ name }) => name)
            .filter(name => !dealStringToArr(removeLables).includes(name));
        }
        if (duplicateLabels) {
          newLabels = [...newLabels, ...dealStringToArr(duplicateLabels)];
        }
        if (labels) {
          newLabels = dealStringToArr(labels);
        }
        if (newLabels.length > 0) {
          await doSetLabels(owner, repo, issueNumber, newLabels.toString());
          core.info(`Actions: [mark-duplicate-labels][${newLabels}] success!`);
        }

        if (closeIssue == 'true') {
          await doCloseIssue(owner, repo, issueNumber);
        }
      } catch (error) {
        core.info(error.message);
      }
    } else {
      core.info(
        `This comment body should start whith 'duplicate-command' or 'Duplicate of' and not include '?'`,
      );
    }
  } else {
    core.info(`This actions only support on 'issue_comment' created or edited!`);
  }
}

async function doOpenIssue(owner, repo, issueNumber) {
  await octokit.issues.update({
    owner,
    repo,
    issue_number: issueNumber,
    state: 'open',
  });
  core.info(`Actions: [open-issue][${issueNumber}] success!`);
}

async function doRemoveAssignees(owner, repo, issueNumber, assignees) {
  await octokit.issues.removeAssignees({
    owner,
    repo,
    issue_number: issueNumber,
    assignees: dealStringToArr(assignees),
  });
  core.info(`Actions: [remove-assignees][${assignees}] success!`);
}

async function doRemoveLabels(owner, repo, issueNumber, labels) {
  const issue = await octokit.issues.get({
    owner,
    repo,
    issue_number: issueNumber,
  });

  const baseLabels = issue.data.labels.map(({ name }) => name);
  const removeLabels = baseLabels.filter(name => dealStringToArr(labels).includes(name));

  core.info(`Actions: [filter-labels][${removeLabels.join(',')}] success!`);

  for (const label of removeLabels) {
    await octokit.issues.removeLabel({
      owner,
      repo,
      issue_number: issueNumber,
      name: label,
    });
    core.info(`Actions: [remove-label][${label}] success!`);
  }

  core.info(`Actions: [remove-labels][${labels}] success!`);
}

async function doSetLabels(owner, repo, issueNumber, labels) {
  // 概率性出现问题：https://github.com/octokit/rest.js/issues/1982，规避 setLabels
  if (labels) {
    // await octokit.issues.setLabels({
    //   owner,
    //   repo,
    //   issue_number: issueNumber,
    //   labels: dealStringToArr(labels)
    // });
    const issue = await octokit.issues.get({
      owner,
      repo,
      issue_number: issueNumber,
    });
    const baseLabels = issue.data.labels.map(({ name }) => name);
    const removeLabels = baseLabels.filter(name => !dealStringToArr(labels).includes(name));
    const addLabels = dealStringToArr(labels).filter(name => !baseLabels.includes(name));

    if (removeLabels.length > 0) {
      await doRemoveLabels(owner, repo, issueNumber, removeLabels.toString());
      core.info(`Actions: [set-labels-remove][${removeLabels}] success!`);
    }

    if (addLabels.length > 0) {
      await doAddLabels(owner, repo, issueNumber, addLabels.toString());
      core.info(`Actions: [set-labels-add][${addLabels}] success!`);
    }

    core.info(`Actions: [set-labels][${labels}] success!`);
  }
}

async function doUnlockIssue(owner, repo, issueNumber) {
  await octokit.issues.unlock({
    owner,
    repo,
    issue_number: issueNumber,
  });
  core.info(`Actions: [unlock-issue][${issueNumber}] success!`);
}

async function doUpdateComment(owner, repo, commentId, body, updateMode, ifUpdateBody) {
  let id = commentId;

  const outComments = core.getInput('out-comments');
  if (outComments) {
    const outCommentsArr = JSON.parse(outComments);
    core.info(`Actions: [out-comments-length][${outCommentsArr.length}] success!`);
    outCommentsArr.forEach(async item => {
      await doComment(item.id);
    });
  } else {
    await doComment(id);
  }

  async function doComment(id) {
    const comment = await octokit.issues.getComment({
      owner,
      repo,
      comment_id: id,
    });
    const comment_body = comment.data.body;

    let params = {
      owner,
      repo,
      comment_id: id,
    };

    if (core.getInput('body') || ifUpdateBody) {
      if (updateMode === 'append') {
        params.body = `${comment_body}\n${body}`;
      } else {
        params.body = body;
      }

      await octokit.issues.updateComment(params);
      core.info(`Actions: [update-comment][${id}] success!`);
    }

    if (contents) {
      await doCreateCommentContent(owner, repo, id, dealStringToArr(contents));
    }
  }
}

async function doUpdateIssue(
  owner,
  repo,
  issueNumber,
  state,
  title,
  body,
  updateMode,
  assignees,
  labels,
) {
  const issue = await octokit.issues.get({
    owner,
    repo,
    issue_number: issueNumber,
  });
  const issue_body = issue.data.body;
  const issue_title = issue.data.title;

  let issue_labels = [];
  if (issue.data.labels.length > 0) {
    issue.data.labels.forEach(it => {
      issue_labels.push(it.name);
    });
  }

  let issue_assignees = [];
  if (issue.data.assignees.length > 0) {
    issue.data.assignees.forEach(it => {
      issue_assignees.push(it.login);
    });
  }

  let params = {
    owner,
    repo,
    issue_number: issueNumber,
    state,
  };

  params.title = core.getInput('title') ? title : issue_title;

  let next_body;
  if (core.getInput('body')) {
    if (updateMode === 'append') {
      next_body = `${issue_body}\n${body}`;
    } else {
      next_body = body;
    }
  } else {
    next_body = issue_body;
  }
  params.body = next_body;

  params.labels = labels ? dealStringToArr(labels) : issue_labels;
  params.assignees = assignees ? dealStringToArr(assignees) : issue_assignees;

  await octokit.issues.update(params);
  core.info(`Actions: [update-issue][${issueNumber}] success!`);

  if (contents) {
    await doCreateIssueContent(owner, repo, issueNumber, contents);
  }
}

async function doWelcome(owner, repo, assignees, labels, body) {
  const context = github.context;
  const isIssue = !!context.payload.issue;
  const issueContents = core.getInput('issue-contents');
  if (!isIssue) {
    core.setFailed('The event that triggered this action must be a issue. Error!');
  } else {
    const auth = context.payload.sender.login;
    core.info(`Actions: [welcome: auth=][${auth}]`);
    const issueNumber = context.issue.number;
    const issues = await doQueryIssues(owner, repo, false, 'all', auth);
    if (issues.length == 0 || (issues.length == 1 && issues[0].number == issueNumber)) {
      if (core.getInput('body')) {
        await doCreateComment(owner, repo, issueNumber, body);
      } else {
        core.info(`Actions: [welcome] no body!`);
      }

      if (assignees) {
        await doAddAssignees(owner, repo, issueNumber, assignees);
      }

      if (labels) {
        await doAddLabels(owner, repo, issueNumber, labels);
      }

      if (issueContents) {
        await doCreateIssueContent(owner, repo, issueNumber, dealStringToArr(issueContents));
      }
    } else {
      core.info(`Actions: [welcome][${auth}] is not first time!`);
    }
  }
}

// **************************************************************************
function testContent(con) {
  if (ALLREACTIONS.includes(con)) {
    return true;
  } else {
    core.setFailed('This actions not supported!');
    return false;
  }
}

// **************************************************************************
module.exports = {
  doAddAssignees,
  doAddLabels,
  doCloseIssue,
  doCreateComment,
  doCreateCommentContent,
  doCreateIssue,
  doCreateIssueContent,
  doCreateLabel,
  doDeleteComment,
  doMarkDuplicate,
  doLockIssue,
  doOpenIssue,
  doRemoveAssignees,
  doRemoveLabels,
  doSetLabels,
  doUnlockIssue,
  doUpdateComment,
  doUpdateIssue,
  doWelcome,
};
