import {
  httpClient,
  HttpMessageBody,
  HttpMethod,
  HttpRequest,
  HttpResponse,
  QueryParams,
} from '@activepieces/pieces-common';
import { Property } from '@activepieces/pieces-framework';
import { isNil } from '@activepieces/pieces-framework';
import { githubAuth } from '../auth';
import { githubAuthHelpers, GithubAuthValue, isAppAuth } from './auth-helpers';

export const githubCommon = {
  baseUrl: 'https://api.github.com',
  repositoryDropdown: Property.Dropdown<
    { repo: string; owner: string },
    true,
    typeof githubAuth
  >({
    displayName: 'Repository',
    refreshers: [],
    auth: githubAuth,
    required: true,
    options: async ({ auth }) => {
      if (!auth) {
        return {
          disabled: true,
          options: [],
          placeholder: 'please authenticate first',
        };
      }
      const repositories = await getUserRepo(auth);
      return {
        disabled: false,
        options: repositories.map((repo) => {
          return {
            label: repo.owner.login + '/' + repo.name,
            value: {
              owner: repo.owner.login,
              repo: repo.name,
            },
          };
        }),
      };
    },
  }),
  milestoneDropdown: (required = false) =>
    Property.Dropdown({
      auth: githubAuth,
      displayName: 'Milestone',
      description: 'The milestone to associate this issue with.',
      required,
      refreshers: ['repository'],
      options: async ({ auth, repository }) => {
        if (!auth || !repository) {
          return {
            disabled: true,
            options: [],
            placeholder: 'Please select a repository first',
          };
        }
        const { owner, repo } = repository as RepositoryProp;
        const milestones = await githubPaginatedApiCall<{
          number: number;
          title: string;
        }>({
          auth: auth as GithubAuthValue,
          method: HttpMethod.GET,
          resourceUri: `/repos/${owner}/${repo}/milestones`,
        });
        return {
          disabled: false,
          options: milestones.map((milestone) => {
            return {
              label: milestone.title,
              value: milestone.number,
            };
          }),
        };
      },
    }),
  branchDropdown: (displayName: string, desc: string, required = true) =>
    Property.Dropdown({
      auth: githubAuth,
      displayName,
      description: desc,
      required,
      refreshers: ['repository'],
      options: async ({ auth, repository }) => {
        if (!auth || !repository) {
          return {
            disabled: true,
            options: [],
            placeholder: 'Please select a repository first',
          };
        }
        const { owner, repo } = repository as RepositoryProp;
        const branches = await githubPaginatedApiCall<{ name: string }>({
          auth: auth as GithubAuthValue,
          method: HttpMethod.GET,
          resourceUri: `/repos/${owner}/${repo}/branches`,
        });
        return {
          disabled: false,
          options: branches.map((branch) => {
            return {
              label: branch.name,
              value: branch.name,
            };
          }),
        };
      },
    }),

  issueDropdown: (required = true) =>
    Property.Dropdown({
      auth: githubAuth,
      displayName: 'Issue',
      description: 'The issue to select.',
      required,
      refreshers: ['repository'],
      options: async ({ auth, repository }) => {
        if (!auth || !repository) {
          return {
            disabled: true,
            options: [],
            placeholder: 'Please select a repository first',
          };
        }
        const { owner, repo } = repository as RepositoryProp;
        const issues = await githubPaginatedApiCall<{
          number: number;
          title: string;
          pull_request?: Record<string, any>;
        }>({
          auth: auth as GithubAuthValue,
          method: HttpMethod.GET,
          resourceUri: `/repos/${owner}/${repo}/issues`,
          query: {
            state: 'open',
          },
        });
        return {
          disabled: false,
          options: issues
            .filter((issue) => !issue.pull_request)
            .map((issue) => {
              return {
                label: `#${issue.number} - ${issue.title}`,
                value: issue.number,
              };
            }),
        };
      },
    }),

  assigneeSingleDropdown: (required = false) =>
    Property.Dropdown({
      auth: githubAuth,
      displayName: 'Assignee',
      description: 'Filter issues by a specific assignee.',
      required,
      refreshers: ['repository'],
      options: async ({ auth, repository }) => {
        if (!auth || !repository) {
          return {
            disabled: true,
            options: [],
            placeholder: 'please authenticate first and select repo',
          };
        }
        const { owner, repo } = repository as RepositoryProp;
        const assignees = await getAssignee(auth, owner, repo);
        return {
          disabled: false,
          options: assignees.map((assignee) => {
            return {
              label: assignee.login,
              value: assignee.login,
            };
          }),
        };
      },
    }),

  assigneeDropDown: (required = false) =>
    Property.MultiSelectDropdown({
      auth: githubAuth,
      displayName: 'Assignees',
      description: 'Assignees for the Issue',
      refreshers: ['repository'],

      required,
      options: async ({ auth, repository }) => {
        if (!auth || !repository) {
          return {
            disabled: true,
            options: [],
            placeholder: 'please authenticate first and select repo',
          };
        }
        const { owner, repo } = repository as RepositoryProp;
        const assignees = await getAssignee(auth, owner, repo);
        return {
          disabled: false,
          options: assignees.map((assignee) => {
            return {
              label: assignee.login,
              value: assignee.login,
            };
          }),
        };
      },
    }),
  labelDropDown: (required = false) =>
    Property.MultiSelectDropdown({
      auth: githubAuth,
      displayName: 'Labels',
      description: 'Labels for the Issue',
      refreshers: ['repository'],
      required,
      options: async ({ auth, repository }) => {
        if (!auth || !repository) {
          return {
            disabled: true,
            options: [],
            placeholder: 'please authenticate first and select repo',
          };
        }
        const { owner, repo } = repository as RepositoryProp;
        const labels = await listIssueLabels(auth, owner, repo);
        return {
          disabled: false,
          options: labels.map((label) => {
            return {
              label: label.name,
              value: label.name,
            };
          }),
        };
      },
    }),
};

async function getUserRepo(auth: GithubAuthValue): Promise<RepoSummary[]> {
  if (isAppAuth(auth)) {
    return getInstallationRepos(auth);
  }
  return githubPaginatedApiCall<RepoSummary>({
    auth,
    method: HttpMethod.GET,
    resourceUri: '/user/repos',
  });
}

async function getInstallationRepos(
  auth: GithubAuthValue
): Promise<RepoSummary[]> {
  const repos: RepoSummary[] = [];
  const qs: RequestParams = { page: 1, per_page: 100 };
  while (true) {
    const response = await githubApiCall<{
      total_count: number;
      repositories: RepoSummary[];
    }>({
      auth,
      method: HttpMethod.GET,
      resourceUri: '/installation/repositories',
      query: qs,
    });
    repos.push(...response.body.repositories);
    const linkHeader = response.headers?.link;
    if (isNil(linkHeader) || !linkHeader.includes(`rel="next"`)) {
      break;
    }
    qs.page = (qs.page as number) + 1;
  }
  return repos;
}

async function getAssignee(auth: GithubAuthValue, owner: string, repo: string) {
  return githubPaginatedApiCall<{ id: number; login: string }>({
    auth,
    method: HttpMethod.GET,
    resourceUri: `/repos/${owner}/${repo}/assignees`,
  });
}

async function listIssueLabels(
  auth: GithubAuthValue,
  owner: string,
  repo: string
) {
  return githubPaginatedApiCall<{ id: number; name: string }>({
    auth,
    method: HttpMethod.GET,
    resourceUri: `/repos/${owner}/${repo}/labels`,
  });
}

export async function githubApiCall<T extends HttpMessageBody>({
  auth,
  method,
  resourceUri,
  query,
  body,
}: GithubApiCallParams): Promise<HttpResponse<T>> {
  const baseUrl = 'https://api.github.com';
  const qs: QueryParams = {};

  if (query) {
    for (const [key, value] of Object.entries(query)) {
      if (value !== null && value !== undefined) {
        qs[key] = String(value);
      }
    }
  }

  const token = await githubAuthHelpers.getBearerToken(auth);

  const request: HttpRequest = {
    method,
    url: baseUrl + resourceUri,
    headers: {
      Authorization: `Bearer ${token}`,
    },
    queryParams: qs,
    body,
  };

  return httpClient.sendRequest<T>(request);
}

export async function githubPaginatedApiCall<T extends HttpMessageBody>({
  auth,
  method,
  resourceUri,
  query,
  body,
}: GithubApiCallParams): Promise<T[]> {
  const qs = query ? query : {};

  qs.page = 1;
  qs.per_page = 100;

  const resultData: T[] = [];
  let hasMoreItems = true;

  do {
    const response = await githubApiCall<T[]>({
      auth,
      method,
      resourceUri,
      query: qs,
      body,
    });
    qs.page = qs.page + 1;
    resultData.push(...response.body);
    const linkHeader = response.headers?.link;
    hasMoreItems = !isNil(linkHeader) && linkHeader.includes(`rel="next"`);
  } while (hasMoreItems);

  return resultData;
}

export interface RepositoryProp {
  repo: string;
  owner: string;
}

export type RequestParams = Record<
  string,
  string | number | string[] | undefined
>;

export type GithubApiCallParams = {
  auth: GithubAuthValue;
  method: HttpMethod;
  resourceUri: string;
  query?: RequestParams;
  body?: any;
};

type RepoSummary = {
  id: number;
  name: string;
  owner: { login: string };
};
