// 1st-party
import type { ReqHandler } from "@ethicdevs/react-monolith";
import { ResourceVisibility } from "@prisma/client";
// app
import type { RepositoryFileDiff } from "../../types";
import { AppRoute, AppRouteParams } from "../../routes.defs";
// app services
import { makeOrganizationService } from "../../services/organization";
import { makePullRequestService } from "../../services/pullRequest";
import { makeRepositoryService } from "../../services/repository";
import { makeUsersService } from "../../services/user";
// app views
import RepositoryPullRequestsView, {
  RepositoryPullRequestsViewProps,
} from "../../views/repositoryPullRequests/RepositoryPullRequestsView";

const postRepositoryPullRequestMergeAction: ReqHandler<
  AppRouteParams,
  AppRoute.REPOSITORY_PULL_REQUEST_MERGE_ACTION
> = async (request, reply) => {
  const { orgSlug, repoSlug, pullUid } = request.params;
  const { merge_message, merge_summary } = request.body;

  console.log("params:", {
    merge_message,
    merge_summary,
  });

  const orgService = makeOrganizationService({ request });
  const prService = makePullRequestService({ request });
  const repoService = makeRepositoryService({ request });
  const usersService = makeUsersService({ request });

  const currentUser =
    request.session.data.authenticated &&
    request.session.data.curr_user_uid != null
      ? await usersService.getUserById(request.session.data.curr_user_uid)
      : null;

  const pullRequest = await prService.getPullRequestByUid(
    orgSlug,
    repoSlug,
    pullUid
  );

  if (pullRequest == null) {
    return reply.status(404).callNotFound();
  }

  const sourceRepo = await repoService.getRepositoryById(
    pullRequest.sourceRepositoryId
  );

  if (sourceRepo == null) {
    return reply.status(404).callNotFound();
  }

  const targetRepo = await repoService.getRepositoryById(
    pullRequest.targetRepositoryId
  );

  if (targetRepo == null) {
    return reply.status(404).callNotFound();
  }

  const sourceParentOrg = await orgService.getOrganizationById(
    sourceRepo.organizationId
  );

  if (sourceParentOrg == null) {
    return reply.status(404).callNotFound();
  }

  if (sourceRepo.visibility === ResourceVisibility.PRIVATE) {
    if (currentUser == null) {
      return reply.status(404).callNotFound();
    } else if (
      (await repoService.canUserAccessRepository(currentUser, sourceRepo)) ===
      false
    ) {
      return reply.status(404).callNotFound();
    }
  }

  const targetParentOrg = await orgService.getOrganizationById(
    targetRepo.organizationId
  );

  if (targetParentOrg == null) {
    return reply.status(404).callNotFound();
  }

  if (targetRepo.visibility === ResourceVisibility.PRIVATE) {
    if (currentUser == null) {
      return reply.status(404).callNotFound();
    } else if (
      (await repoService.canUserAccessRepository(currentUser, targetRepo)) ===
      false
    ) {
      return reply.status(404).callNotFound();
    }
  }

  // 1. Try add remote & compare pull request branches
  let fileDiffs = [] as RepositoryFileDiff[];

  if (sourceParentOrg.slug === targetParentOrg.slug) {
    fileDiffs = await repoService.getRepositoryRefDiff(
      sourceRepo,
      pullRequest.targetBranch,
      pullRequest.sourceBranch
    );
  } else {
    fileDiffs = await repoService.getRepositoryRemoteRefDiff(
      sourceRepo,
      pullRequest.sourceBranch,
      targetRepo,
      pullRequest.targetBranch
    );
  }

  // 2. Check if branches can be merged
  if (fileDiffs.length <= 0) {
    throw new Error("Cannot merge two branches without difference.");
  }

  // 3. Do the merge !

  const reqHandler = reply.makeRequestHandler(request, reply);
  return reqHandler<RepositoryPullRequestsViewProps>(
    RepositoryPullRequestsView.name,
    {
      // parentOrg,
      pullRequest,
      // repo,
    }
  );
};

export default postRepositoryPullRequestMergeAction;