import * as express from "express";
import { ensureAuthenticated } from "./connection";

import {
  getPullRequest,
  getUser,
  handleError,
  isOwnerOrAdmin,
  extendExpirationDate,
} from "./route-utils";
import AnonymousError from "../../core/AnonymousError";
import { IAnonymizedPullRequestDocument } from "../../core/model/anonymizedPullRequests/anonymizedPullRequests.types";
import PullRequest from "../../core/PullRequest";
import AnonymizedPullRequestModel from "../../core/model/anonymizedPullRequests/anonymizedPullRequests.model";
import { RepositoryStatus } from "../../core/types";

const router = express.Router();

// user needs to be connected for all user API
router.use(ensureAuthenticated);

// refresh pullRequest
router.post(
  "/:pullRequestId/refresh",
  async (req: express.Request, res: express.Response) => {
    try {
      const pullRequest = await getPullRequest(req, res, { nocheck: true });
      if (!pullRequest) return;

      const user = await getUser(req);
      isOwnerOrAdmin([pullRequest.owner.id], user);
      await pullRequest.updateIfNeeded({ force: true });
      res.json({ status: pullRequest.status });
    } catch (error) {
      handleError(error, res, req);
    }
  }
);

// extend the expiration of a pullRequest (default +6 months) and bring it back
// online if it had expired
router.post(
  "/:pullRequestId/extend",
  async (req: express.Request, res: express.Response) => {
    try {
      const pullRequest = await getPullRequest(req, res, { nocheck: true });
      if (!pullRequest) return;

      if (
        pullRequest.status == RepositoryStatus.PREPARING ||
        pullRequest.status == RepositoryStatus.REMOVING ||
        pullRequest.status == RepositoryStatus.EXPIRING ||
        pullRequest.status == RepositoryStatus.REMOVED
      ) {
        throw new AnonymousError("invalid_status", {
          object: pullRequest,
          httpStatus: 409,
        });
      }

      const user = await getUser(req);
      isOwnerOrAdmin([pullRequest.owner.id], user);

      const newExpiration = extendExpirationDate(
        pullRequest.model.options.expirationDate
      );
      pullRequest.model.options.expirationDate = newExpiration;
      await AnonymizedPullRequestModel.updateOne(
        { _id: pullRequest.model._id },
        { $set: { "options.expirationDate": newExpiration } }
      ).exec();

      await pullRequest.updateIfNeeded({ force: true });
      res.json({ status: pullRequest.status, expirationDate: newExpiration });
    } catch (error) {
      handleError(error, res, req);
    }
  }
);

// delete a pullRequest
router.delete(
  "/:pullRequestId/",
  async (req: express.Request, res: express.Response) => {
    const pullRequest = await getPullRequest(req, res, { nocheck: true });
    if (!pullRequest) return;
    try {
      if (pullRequest.status == "removed")
        throw new AnonymousError("is_removed", {
          object: req.params.pullRequestId,
          httpStatus: 410,
        });
      const user = await getUser(req);
      isOwnerOrAdmin([pullRequest.owner.id], user);
      await pullRequest.remove();
      return res.json({ status: pullRequest.status });
    } catch (error) {
      handleError(error, res, req);
    }
  }
);

router.get(
  "/:owner/:repository/:pullRequestId",
  async (req: express.Request, res: express.Response) => {
    const user = await getUser(req);
    try {
      const pullRequest = new PullRequest(
        new AnonymizedPullRequestModel({
          owner: user.id,
          source: {
            pullRequestId: parseInt(req.params.pullRequestId),
            repositoryFullName: `${req.params.owner}/${req.params.repository}`,
          },
        })
      );
      pullRequest.owner = user;
      await pullRequest.download();
      res.json(pullRequest.toJSON());
    } catch (error) {
      handleError(error, res, req);
    }
  }
);

// get pullRequest information
router.get(
  "/:pullRequestId/",
  async (req: express.Request, res: express.Response) => {
    try {
      const pullRequest = await getPullRequest(req, res, { nocheck: true });
      if (!pullRequest) return;

      const user = await getUser(req);
      isOwnerOrAdmin([pullRequest.owner.id], user);
      res.json(pullRequest.toJSON());
    } catch (error) {
      handleError(error, res, req);
    }
  }
);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function validateNewPullRequest(pullRequestUpdate: any): void {
  const validCharacters = /^[0-9a-zA-Z\-_]+$/;
  if (
    !pullRequestUpdate.pullRequestId.match(validCharacters) ||
    pullRequestUpdate.pullRequestId.length < 3
  ) {
    throw new AnonymousError("invalid_pullRequestId", {
      object: pullRequestUpdate,
      httpStatus: 400,
    });
  }
  if (!pullRequestUpdate.source.repositoryFullName) {
    throw new AnonymousError("repository_not_specified", {
      object: pullRequestUpdate,
      httpStatus: 400,
    });
  }
  if (!pullRequestUpdate.source.pullRequestId) {
    throw new AnonymousError("pullRequestId_not_specified", {
      object: pullRequestUpdate,
      httpStatus: 400,
    });
  }
  if (
    parseInt(pullRequestUpdate.source.pullRequestId) !=
    pullRequestUpdate.source.pullRequestId
  ) {
    throw new AnonymousError("pullRequestId_is_not_a_number", {
      object: pullRequestUpdate,
      httpStatus: 400,
    });
  }
  if (!pullRequestUpdate.options) {
    throw new AnonymousError("options_not_provided", {
      object: pullRequestUpdate,
      httpStatus: 400,
    });
  }
  if (!Array.isArray(pullRequestUpdate.terms)) {
    throw new AnonymousError("invalid_terms_format", {
      object: pullRequestUpdate,
      httpStatus: 400,
    });
  }
}

function updatePullRequestModel(
  model: IAnonymizedPullRequestDocument,
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  pullRequestUpdate: any
) {
  model.options = {
    terms: pullRequestUpdate.terms,
    expirationMode: pullRequestUpdate.options.expirationMode,
    expirationDate: pullRequestUpdate.options.expirationDate
      ? new Date(pullRequestUpdate.options.expirationDate)
      : undefined,
    update: pullRequestUpdate.options.update,
    image: pullRequestUpdate.options.image,
    link: pullRequestUpdate.options.link,
    body: pullRequestUpdate.options.body,
    title: pullRequestUpdate.options.title,
    username: pullRequestUpdate.options.username,
    origin: pullRequestUpdate.options.origin,
    diff: pullRequestUpdate.options.diff,
    comments: pullRequestUpdate.options.comments,
    date: pullRequestUpdate.options.date,
  };
}

// update a pullRequest
router.post(
  "/:pullRequestId/",
  async (req: express.Request, res: express.Response) => {
    try {
      const pullRequest = await getPullRequest(req, res, { nocheck: true });
      if (!pullRequest) return;
      const user = await getUser(req);

      isOwnerOrAdmin([pullRequest.owner.id], user);
      const pullRequestUpdate = req.body;
      validateNewPullRequest(pullRequestUpdate);
      pullRequest.model.anonymizeDate = new Date();

      updatePullRequestModel(pullRequest.model, pullRequestUpdate);
      // TODO handle conference
      pullRequest.model.conference = pullRequestUpdate.conference;
      await AnonymizedPullRequestModel.updateOne(
        { _id: pullRequest.model._id },
        {
          $set: {
            options: pullRequest.model.options,
            conference: pullRequest.model.conference,
            anonymizeDate: pullRequest.model.anonymizeDate,
          },
        }
      ).exec();
      await pullRequest.updateStatus(RepositoryStatus.PREPARING);
      await pullRequest.updateIfNeeded({ force: true });
      res.json(pullRequest.toJSON());
    } catch (error) {
      return handleError(error, res, req);
    }
  }
);

// add pullRequest
router.post("/", async (req: express.Request, res: express.Response) => {
  const user = await getUser(req);
  const pullRequestUpdate = req.body;

  try {
    validateNewPullRequest(pullRequestUpdate);

    const pullRequest = new PullRequest(
      new AnonymizedPullRequestModel({
        owner: user.id,
        options: pullRequestUpdate.options,
      })
    );

    pullRequest.model.pullRequestId = pullRequestUpdate.pullRequestId;
    pullRequest.model.anonymizeDate = new Date();
    pullRequest.model.owner = user.id;

    updatePullRequestModel(pullRequest.model, pullRequestUpdate);
    pullRequest.source.accessToken = user.accessToken;
    pullRequest.source.pullRequestId = pullRequestUpdate.source.pullRequestId;
    pullRequest.source.repositoryFullName =
      pullRequestUpdate.source.repositoryFullName;

    pullRequest.conference = pullRequestUpdate.conference;

    await pullRequest.model.save();
    await pullRequest.anonymize();
    res.send(pullRequest.toJSON());
  } catch (error) {
    if (
      error instanceof Error &&
      error.message.indexOf(" duplicate key") > -1
    ) {
      return handleError(
        new AnonymousError("pullRequestId_already_used", {
          httpStatus: 400,
          cause: error,
          object: pullRequestUpdate,
        }),
        res,
        req
      );
    }
    return handleError(error, res, req);
  }
});

export default router;
