// 1st-party
import { ReqHandler } from "@ethicdevs/react-monolith";
// app
import { AppRoute, AppRoutesParams } from "../../routes";
// app islands
import {
  PullRequestFormState,
  RepositoryPullRequestCreateFormVariant,
} from "../../islands/RepositoryPullRequestCreateForm";
// 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 RepositoryPullRequestCreateView, {
  RepositoryPullRequestCreateViewProps,
} from "../../views/repository/RepositoryPullRequestCreateView";
import { RepositoryFileDiff } from "app/types";

const postRepositoryPullRequestCreateAction: ReqHandler = async (
  request,
  reply
) => {
  if (
    request.session.data.authenticated === false ||
    request.session.data.curr_user_uid == null
  ) {
    reply.redirect(302, request.namedViewsPathMap[AppRoute.AUTH_LOGIN]);
    return reply;
  }

  const { orgSlug, repoSlug } =
    request.params as AppRoutesParams[AppRoute.REPOSITORY_PULL_REQUEST_CREATE_ACTION]["params"];
  const {
    description,
    summary,
    state_from: fromState,
    state_dest: desiredState,
    source_parent_org_slug: sourceParentOrgSlug,
    source_repository_from_branch: sourceRepoFromBranch,
    source_repository_slug: sourceRepoSlug,
    target_parent_org_slug: targetParentOrgSlug,
    target_repository_dest_branch: targetRepoDestBranch,
    target_repository_slug: targetRepoSlug,
  } = request.body as AppRoutesParams[AppRoute.REPOSITORY_PULL_REQUEST_CREATE_ACTION]["body"];

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

  const currentUser = await usersService.getUserById(
    request.session.data.curr_user_uid
  );

  if (currentUser == null) {
    reply.redirect(302, request.namedViewsPathMap[AppRoute.AUTH_LOGIN]);
    return reply;
  }

  const parentOrg = await orgService.getOrganizationBySlug(orgSlug);
  const repo = await repoService.getRepository(orgSlug, repoSlug);

  if (parentOrg == null || repo == null) {
    return reply.status(404).callNotFound();
  }

  if (
    fromState === PullRequestFormState.ERROR ||
    desiredState === PullRequestFormState.CONFIGURE
  ) {
    let redirectUri =
      request.namedViewsPathMap[AppRoute.REPOSITORY_PULL_REQUEST_CREATE];
    redirectUri = redirectUri
      .replace(/:orgSlug/g, parentOrg.slug)
      .replace(/:repoSlug/g, repo.slug);
    reply.redirect(302, redirectUri);
    return reply;
  }

  let variant: RepositoryPullRequestCreateFormVariant | null = null;

  if (desiredState === PullRequestFormState.COMPARE) {
    // source
    const sourceParentOrg = await orgService.getOrganizationBySlug(
      sourceParentOrgSlug
    );
    const sourceRepo = await repoService.getRepository(
      sourceParentOrgSlug,
      sourceRepoSlug
    );
    // target
    const targetParentOrg = await orgService.getOrganizationBySlug(
      targetParentOrgSlug
    );
    const targetRepo = await repoService.getRepository(
      targetParentOrgSlug,
      targetRepoSlug
    );

    if (
      sourceParentOrg == null ||
      sourceRepo == null ||
      targetParentOrg == null ||
      targetRepo == null
    ) {
      return reply.status(404).callNotFound();
    }

    let fileDiffs = [] as RepositoryFileDiff[];

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

    variant = {
      state: desiredState,
      data: {
        fileDiffs,
        source: {
          parentOrg: sourceParentOrg,
          repo: sourceRepo,
          branch: sourceRepoFromBranch,
        },
        target: {
          parentOrg: targetParentOrg,
          repo: targetRepo,
          branch: targetRepoDestBranch,
        },
      },
    };
  } else if (desiredState === PullRequestFormState.DETAILS) {
    // source
    const sourceRepo = await repoService.getRepository(
      sourceParentOrgSlug,
      sourceRepoSlug
    );
    // target
    const targetRepo = await repoService.getRepository(
      targetParentOrgSlug,
      targetRepoSlug
    );

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

    const pullRequest = await pullsService.createPullRequest({
      summary,
      textMd: description,
      author: currentUser,
      source: {
        repository: sourceRepo,
        fromBranch: sourceRepoFromBranch,
      },
      target: {
        repository: targetRepo,
        destBranch: targetRepoDestBranch,
      },
    });

    variant = {
      state: desiredState,
      data: {
        canCurrentUserSubmitPullRequest: false,
        pullRequest,
      },
    };
  }

  if (variant == null) {
    variant = {
      state: PullRequestFormState.ERROR,
      data: {
        errorMessage: "Something went wrong",
      },
    };
  }

  const reqHandler = reply.makeRequestHandler(request, reply);
  return reqHandler<RepositoryPullRequestCreateViewProps>(
    RepositoryPullRequestCreateView.name,
    {
      errorMessage: null,
      initialValues: {},
      parentOrg,
      repo,
      variant,
    }
  );
};

export default postRepositoryPullRequestCreateAction;