// 1st-party
import { ReqHandler } from "@ethicdevs/react-monolith";
// app
import type { RepositoryWithParentAndForkedFromRepos } from "../../types";
import { Const } from "../../const";
import { AppRoute, AppRoutesParams } from "../../routes";
// app services
import { makeOrganizationService } from "../../services/organization";
import { makeRepositoryService } from "../../services/repository";
import { makeUsersService } from "../../services/user";
// app islands
import {
  PullRequestFormState,
  RepositoryPullRequestCreateFormVariant,
  RepositoryPullRequestTarget,
} from "../../islands/RepositoryPullRequestCreateForm";
// app views
import RepositoryPullRequestCreateView, {
  RepositoryPullRequestCreateViewProps,
} from "../../views/repositoryPullRequests/RepositoryPullRequestCreateView";

const getRepositoryPullRequestCreateView: 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 { from_branch } =
    request.query as AppRoutesParams[AppRoute.REPOSITORY_PULL_REQUEST_CREATE]["querystring"];
  const { orgSlug, repoSlug } =
    request.params as AppRoutesParams[AppRoute.REPOSITORY_PULL_REQUEST_CREATE]["params"];

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

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

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

  const branches = await repoService.getRepositoryBranches(repo);

  const currentUserForks = await repoService.getCurrentUserRepositoryForks(
    repo
  );

  const currentUserForksRepos = await currentUserForks.reduce(
    async (accP, r) => {
      let acc = await accP;
      acc = {
        ...acc,
        [`${r.organization.slug}/${r.slug}`]: {
          repo: r,
          branches: await repoService.getRepositoryBranches(r),
        },
      };
      return acc;
    },
    Promise.resolve({}) as Promise<{
      [repoSlug: string]: {
        repo: RepositoryWithParentAndForkedFromRepos;
        branches: string[];
      };
    }>
  );

  // Inject PR target's repository first.
  const [personalOrg] = await usersService.getUserOrganizations(
    request.session.data.curr_user_uid,
    true // personalOnly
  );

  let variant: RepositoryPullRequestCreateFormVariant;
  if (from_branch == null) {
    variant = {
      state: PullRequestFormState.CONFIGURE,
      data: {
        sources: {
          // target repo's source
          [parentOrg.slug]: {
            org: parentOrg,
            repos: {
              [`${parentOrg.slug}/${repo.slug}`]: {
                repo: { ...repo, organization: parentOrg },
                branches,
              },
            },
          },
          // In case user has forks on its own, add its personal org (where forks **should** be)
          ...(currentUserForks.length >= 1 && {
            [personalOrg.slug]: {
              org: personalOrg,
              repos: currentUserForksRepos,
            },
          }),
        },
        target: {
          branch: from_branch || Const.PRIMARY_BRANCH_REF,
          parentOrg,
          repo,
        },
      },
    };
  } else {
    let source: RepositoryPullRequestTarget = {
      branch: from_branch,
      parentOrg,
      repo,
    };
    let target: RepositoryPullRequestTarget = {
      branch: Const.PRIMARY_BRANCH_REF,
      parentOrg,
      repo,
    };

    const orgRepoBranchRegExp = /^([\w\-_\.]+)\/([\w\-_\.]+)@(.*)/i;
    const matches = from_branch.match(orgRepoBranchRegExp);

    if (matches != null && Array.isArray(matches)) {
      const [_, targetOrgSlug, targetRepoSlug, targetFromBranch] = matches;
      const targetParentOrg = await orgService.getOrganizationBySlug(
        targetOrgSlug
      );
      const targetRepo = await repoService.getRepository(
        targetOrgSlug,
        targetRepoSlug
      );

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

      const fileDiffs = await repoService.getRepositoryRefDiff(
        repo,
        Const.PRIMARY_BRANCH_REF,
        targetFromBranch
      );

      target = {
        branch: from_branch,
        parentOrg: targetParentOrg,
        repo: targetRepo,
      };

      variant = {
        state: PullRequestFormState.COMPARE,
        data: {
          source,
          target,
          fileDiffs,
        },
      };
    } else {
      const fileDiffs = await repoService.getRepositoryRefDiff(
        repo,
        Const.PRIMARY_BRANCH_REF,
        from_branch
      );

      variant = {
        state: PullRequestFormState.COMPARE,
        data: {
          source: {
            branch: Const.PRIMARY_BRANCH_REF,
            parentOrg,
            repo,
          },
          target,
          fileDiffs,
        },
      };
    }
  }

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

export default getRepositoryPullRequestCreateView;