/*
 * Copyright (C) 2019-2022 Open StApps
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <https://www.gnu.org/licenses/>.
 */
import {
  Api,
  AccessLevel,
  MembershipScope,
  MergeRequestMergeStatus,
  MergeRequestState,
  Scope,
  User,
} from '@openstapps/gitlab-api';
import {Logger} from '@openstapps/logger';
import {WebClient} from '@slack/web-api';
import {CONCURRENCY, GROUPS, MAX_DEPTH_FOR_REMINDER, NOTE_PREFIX, SLACK_CHANNEL} from '../configuration.js';
import {mapAsyncLimit} from '@openstapps/collection-utils';

/**
 * Remind people of open merge requests
 * @param api GitLab API to make requests with
 */
export async function remind(api: Api): Promise<void> {
  // get a list of open merge requests
  const allMergeRequests = await api.getMergeRequests(
    MembershipScope.GROUPS,
    GROUPS[0],
    MergeRequestState.OPENED,
  );

  const mergeRequests = allMergeRequests.filter(mergeRequest => {
    const parts = mergeRequest.web_url.split('/');

    // remove protocol, server name and main group
    parts.splice(0, parts.indexOf('gitlab.com') + 1 + 1);

    // remove merge_requests and INDEX parts
    parts.splice(-1 - 1);

    return parts.length <= MAX_DEPTH_FOR_REMINDER;
  });

  Logger.info(`Found ${mergeRequests.length} open merge requests.`);

  // instantiate slack client
  const client =
    process.env.SLACK_API_TOKEN === undefined ? undefined : new WebClient(process.env.SLACK_API_TOKEN);

  // get members of the main group
  const members = await api.getMembers(MembershipScope.GROUPS, GROUPS[0]);

  // filter members with at least maintainer status
  const maintainers = members.filter(member => member.access_level >= AccessLevel.Maintainer);

  // extract maintainer's usernames
  const maintainerUsernames = maintainers.map(maintainer => maintainer.username);

  // sort maintainer's usernames alphabetically
  maintainerUsernames.sort((a, b) => {
    return a.localeCompare(b);
  });

  Logger.info(`Found ${maintainers.length} maintainer(s).`);

  await mapAsyncLimit(
    mergeRequests,
    async mergeRequest => {
      // check if merge request is WIP
      if (mergeRequest.work_in_progress) {
        Logger.info(`Merge request '${mergeRequest.title}' is WIP.`);

        return;
      }

      // get merge request approval
      const approval = await api.getMergeRequestApproval(mergeRequest.project_id, mergeRequest.iid);

      // get merge request discussions
      const discussions = await api.getMergeRequestDiscussions(mergeRequest.project_id, mergeRequest.iid);

      // check if at least one of the discussions is unresolved
      const hasUnresolvedDiscussions = discussions.some(discussion => {
        return discussion.notes.some(note => {
          return note.resolvable && (note.resolved === undefined || !note.resolved);
        });
      });

      if (hasUnresolvedDiscussions) {
        let recipient = mergeRequest.author.username;

        if (mergeRequest.assignee !== undefined && mergeRequest.assignee !== null) {
          recipient = mergeRequest.assignee.username;
        }

        // create note in merge request
        await api.createNote(
          mergeRequest.project_id,
          Scope.MERGE_REQUESTS,
          mergeRequest.iid,
          `${NOTE_PREFIX} Please resolve pending discussions, @${recipient}!`,
        );

        return;
      }

      if (approval.merge_status === MergeRequestMergeStatus.CAN_BE_MERGED) {
        if (approval.approvals_left > 0) {
          Logger.warn(`Merge request '${mergeRequest.title}' needs more approvals!`);

          // get possible appropers, prefixed with '@' and joined with commas
          const possibleApprovers = maintainerUsernames
            .filter(username => {
              if (mergeRequest!.assignee.username === username) {
                return false;
              }
              if (username.includes('openstapps') || username.includes('kphilipp')) {
                return false;
              }

              if (approval.approved_by.length === 0) {
                return true;
              }

              return approval.approved_by.find(
                (approver: {
                  /**
                   * Possible approver
                   */
                  user: User;
                }) => {
                  return approver.user.username !== username;
                },
              );
            })
            .map(username => `@${username}`)
            .join(' ');

          // send message to slack
          await client?.chat.postMessage({
            channel: SLACK_CHANNEL,
            text: `Merge request '${mergeRequest.title}' needs more approvals! See ${mergeRequest.web_url}!`,
          });

          // assign reviewers
          await api.createNote(
            mergeRequest.project_id,
            Scope.MERGE_REQUESTS,
            mergeRequest.iid,
            `/assign_reviewer ${possibleApprovers}`,
          );
        } else {
          Logger.log(`Merge request '${mergeRequest.title}' is ready to be merged!`);

          // send message to slack
          await client?.chat.postMessage({
            channel: SLACK_CHANNEL,
            text: `Merge request '${mergeRequest.title}' is ready to be merged! See ${mergeRequest.web_url}!`,
          });

          // prefix maintainers with '@' and join with commas
          const possibleMergers = maintainerUsernames
            .filter(username => {
              return mergeRequest!.assignee.username !== username;
            })
            .map(username => `@${username}`)
            .join(', ');

          // create note in merge request
          await api.createNote(
            mergeRequest.project_id,
            Scope.MERGE_REQUESTS,
            mergeRequest.iid,
            `${NOTE_PREFIX} Merge request is ready to be merged, ${possibleMergers}!`,
          );
        }
      }
    },
    CONCURRENCY,
  );
}
