import { BaseResource } from '@gitbeaker/requester-utils';
import { RequestHelper, endpoint } from '../infrastructure';
import type {
  AllOrNone,
  AsAdmin,
  GitlabAPIResponse,
  PaginationRequestOptions,
  PaginationTypes,
  ShowExpanded,
  Sudo,
} from '../infrastructure';
import type { ProjectSchema, ProjectStatisticsSchema, SimpleProjectSchema } from './Projects';
import type { AllEventOptions, EventSchema } from './Events';
import type { PersonalAccessTokenSchema } from './PersonalAccessTokens';
import type { CustomAttributeSchema } from '../templates/ResourceCustomAttributes';
import { AccessLevel } from '../constants';

export interface SimpleUserSchema extends Record<string, unknown> {
  id: number;
  name: string;
  username: string;
  state: string;
  avatar_url: string;
  web_url: string;
  created_at: string;
}

export interface UserSchema extends SimpleUserSchema {
  locked: boolean | null;
  bio: string | null;
  bot: boolean | null;
  location: string | null;
  public_email: string | null;
  skype: string | null;
  linkedin: string | null;
  twitter: string | null;
  discord: string | null;
  website_url: string | null;
  pronouns: string | null;
  organization: string | null;
  job_title: string | null;
  work_information: string | null;
  followers: number | null;
  following: number | null;
  local_time: string | null;
  is_followed: boolean | null;
}

export interface ExpandedUserSchema extends UserSchema {
  is_admin: boolean | null;
  bot: boolean;
  last_sign_in_at: string;
  confirmed_at: string;
  last_activity_on: string;
  email: string;
  theme_id: number;
  color_scheme_id: number;
  projects_limit: number;
  current_sign_in_at: string | null;
  note: string | null;
  identities: { provider: string; extern_uid: string; saml_provider_id: number }[] | null;
  can_create_group: boolean;
  can_create_project: boolean;
  two_factor_enabled: boolean;
  external: boolean;
  private_profile: string | null;
  namespace_id: number | null;
  created_by: string | null;
}

export interface AdminUserSchema extends ExpandedUserSchema {
  current_sign_in_ip: string;
  last_sign_in_ip: string;
  using_license_seat: boolean | null;
  email_reset_offered_at: string | null;
  shared_runners_minutes_limit?: number | null;
  extra_shared_runners_minutes_limit?: number | null;
  is_auditor?: boolean | null;
  provisioned_by_group_id?: number | null;
  plan?: string;
  trial?: boolean;
}

export interface UserActivitySchema extends Record<string, unknown> {
  username: string;
  last_activity_on: string;
  last_activity_at: string;
}

export interface UserStatusSchema extends Record<string, unknown> {
  emoji: string;
  availability: string;
  message: string;
  message_html: string;
  clear_status_at: string;
}

export interface UserPreferenceSchema extends Record<string, unknown> {
  id: number;
  user_id: number;
  view_diffs_file_by_file: boolean;
  show_whitespace_in_diffs: boolean;
}

export interface UserCountSchema extends Record<string, unknown> {
  merge_requests: number;
  assigned_issues: number;
  assigned_merge_requests: number;
  review_requested_merge_requests: number;
  todos: number;
}

export interface UserAssociationCountSchema extends Record<string, unknown> {
  groups_count: number;
  projects_count: number;
  issues_count: number;
  merge_requests_count: number;
}

export interface UserMembershipSchema extends Record<string, unknown> {
  source_id: number;
  source_name: string;
  source_type: 'Project' | 'Namespace';
  access_level: Exclude<AccessLevel, AccessLevel.ADMIN>;
}

export interface UserRunnerSchema extends Record<string, unknown> {
  id: number;
  token: string;
  token_expires_at: string | null;
}

export type AllUsersOptions = {
  orderBy?: 'name' | 'username' | 'created_at' | 'updated_at';
  createdBy?: string;
  sort?: 'asc' | 'desc';
  twoFactor?: string;
  withoutProjects?: boolean;
  admins?: boolean;
  samlProviderId?: number;
  skipLdap?: boolean;
  search?: string;
  username?: string;
  active?: boolean;
  blocked?: boolean;
  external?: boolean;
  excludeInternal?: boolean;
  excludeExternal?: boolean;
  withoutProjectBots?: boolean;
  createdBefore?: string;
  createdAfter?: string;
  withCustomAttributes?: boolean;
  customAttributes?: Record<string, string>;
} & AllOrNone<{ provider: string; externUid: string }>;

export type CreateUserOptions = {
  admin?: boolean;
  auditor?: boolean;
  avatar?: { content: Blob; filename?: string };
  bio?: string;
  canCreateGroup?: boolean;
  colorSchemeId?: number;
  commitEmail?: string;
  email?: string;
  externUid?: string;
  external?: boolean;
  extraSharedRunnersMinutesLimit?: number;
  forceRandomPassword?: boolean;
  groupIdForSaml?: number;
  linkedin?: string;
  location?: string;
  name?: string;
  note?: string;
  organization?: string;
  password?: string;
  privateProfile?: string;
  projectsLimit?: number;
  pronouns?: string;
  provider?: string;
  publicEmail?: string;
  resetPassword?: boolean;
  sharedRunnersMinutesLimit?: number;
  skipConfirmation?: boolean;
  skype?: string;
  themeId?: number;
  twitter?: string;
  discord?: string;
  username?: string;
  viewDiffsFileByFile?: boolean;
  websiteUrl?: string;
};

export type EditUserOptions = CreateUserOptions;

export type CreateUserCIRunnerOptions = {
  groupId?: number;
  projectId?: number;
  description?: string;
  paused?: boolean;
  locked?: boolean;
  runUntagged?: boolean;
  tagList?: string[];
  accessLevel?: 'not_protected' | 'ref_protected';
  maximumTimeout?: number;
  maintenanceNote?: string;
};

export type AllUserProjectsOptions = {
  archived?: boolean;
  idAfter?: number;
  idBefore?: number;
  membership?: boolean;
  minAccessLevel?: Exclude<AccessLevel, AccessLevel.ADMIN>;
  orderBy?: 'id' | 'name' | 'path' | 'created_at' | 'updated_at' | 'last_activity_at';
  owned?: boolean;
  search?: string;
  simple?: boolean;
  sort?: 'asc' | 'desc';
  starred?: boolean;
  statistics?: boolean;
  visibility?: 'public' | 'internal' | 'private';
  withCustomAttributes?: boolean;
  withIssuesEnabled?: boolean;
  withMergeRequestsEnabled?: boolean;
  withProgrammingLanguage?: string;
  updatedBefore?: string;
  updatedAfter?: string;
};

export class Users<C extends boolean = false> extends BaseResource<C> {
  activate<E extends boolean = false>(
    userId: number,
    options?: Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<void, C, E, void>> {
    return RequestHelper.post<void>()(this, endpoint`users/${userId}/activate`, options);
  }

  all<A extends boolean = false, E extends boolean = false, P extends PaginationTypes = 'offset'>(
    options?: { withCustomAttributes: true } & AsAdmin<A> &
      AllUsersOptions &
      PaginationRequestOptions<P> &
      Sudo &
      ShowExpanded<E>,
  ): Promise<
    GitlabAPIResponse<
      ((A extends false ? SimpleUserSchema : AdminUserSchema) & {
        custom_attributes: CustomAttributeSchema[];
      })[],
      C,
      E,
      P
    >
  >;

  all<A extends boolean = false, E extends boolean = false, P extends PaginationTypes = 'offset'>(
    options?: AllUsersOptions & AsAdmin<A> & PaginationRequestOptions<P> & Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<(A extends false ? SimpleUserSchema : AdminUserSchema)[], C, E, P>>;

  all<A extends boolean = false, E extends boolean = false, P extends PaginationTypes = 'offset'>(
    options?: AllUsersOptions & AsAdmin<A> & PaginationRequestOptions<P> & Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<(A extends false ? SimpleUserSchema : AdminUserSchema)[], C, E, P>> {
    return RequestHelper.get<(A extends false ? SimpleUserSchema : AdminUserSchema)[]>()(
      this,
      'users',
      options,
    );
  }

  allActivities<E extends boolean = false, P extends PaginationTypes = 'offset'>(
    options?: { from?: string } & PaginationRequestOptions<P> & Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<UserActivitySchema[], C, E, P>> {
    return RequestHelper.get<UserActivitySchema[]>()(this, 'user/activities', options);
  }

  allEvents<E extends boolean = false, P extends PaginationTypes = 'offset'>(
    userId: number,
    options?: AllEventOptions & PaginationRequestOptions<P> & Sudo & ShowExpanded<E>,
  ) {
    return RequestHelper.get<EventSchema[]>()(this, endpoint`users/${userId}/events`, options);
  }

  allFollowers<E extends boolean = false, P extends PaginationTypes = 'offset'>(
    userId: number,
    options?: PaginationRequestOptions<P> & Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<SimpleUserSchema[], C, E, P>> {
    return RequestHelper.get<SimpleUserSchema[]>()(
      this,
      endpoint`users/${userId}/followers`,
      options,
    );
  }

  allFollowing<E extends boolean = false, P extends PaginationTypes = 'offset'>(
    userId: number,
    options?: PaginationRequestOptions<P> & Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<SimpleUserSchema[], C, E, P>> {
    return RequestHelper.get<SimpleUserSchema[]>()(
      this,
      endpoint`users/${userId}/following`,
      options,
    );
  }

  allMemberships<E extends boolean = false, P extends PaginationTypes = 'offset'>(
    userId: number,
    options?: { type?: 'Project' | 'Namespace' } & PaginationRequestOptions<P> &
      Sudo &
      ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<UserMembershipSchema[], C, E, P>> {
    return RequestHelper.get<UserMembershipSchema[]>()(
      this,
      endpoint`users/${userId}/memberships`,
      options,
    );
  }

  allProjects<E extends boolean = false, P extends PaginationTypes = 'keyset'>(
    userId: string | number,
    options: PaginationRequestOptions<P> &
      AllUserProjectsOptions &
      Sudo &
      ShowExpanded<E> & { simple: true },
  ): Promise<GitlabAPIResponse<SimpleProjectSchema[], C, E, P>>;

  allProjects<E extends boolean = false, P extends PaginationTypes = 'keyset'>(
    userId: string | number,
    options: PaginationRequestOptions<P> &
      AllUserProjectsOptions &
      Sudo &
      ShowExpanded<E> & { statistics: true },
  ): Promise<
    GitlabAPIResponse<(ProjectSchema & { statistics: ProjectStatisticsSchema })[], C, E, P>
  >;

  allProjects<E extends boolean = false, P extends PaginationTypes = 'keyset'>(
    userId: string | number,
    options?: PaginationRequestOptions<P> & AllUserProjectsOptions & Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<ProjectSchema[], C, E, P>>;

  allProjects<E extends boolean = false, P extends PaginationTypes = 'offset'>(
    userId: string | number,
    options?: AllUserProjectsOptions & PaginationRequestOptions<P> & Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<ProjectSchema[], C, E, P>> {
    return RequestHelper.get<ProjectSchema[]>()(this, endpoint`users/${userId}/projects`, options);
  }

  allContributedProjects<E extends boolean = false, P extends PaginationTypes = 'keyset'>(
    userId: string | number,
    options: PaginationRequestOptions<P> &
      AllUserProjectsOptions &
      Sudo &
      ShowExpanded<E> & { simple: true },
  ): Promise<GitlabAPIResponse<SimpleProjectSchema[], C, E, P>>;

  allContributedProjects<E extends boolean = false, P extends PaginationTypes = 'keyset'>(
    userId: string | number,
    options: PaginationRequestOptions<P> &
      AllUserProjectsOptions &
      Sudo &
      ShowExpanded<E> & { statistics: true },
  ): Promise<
    GitlabAPIResponse<(ProjectSchema & { statistics: ProjectStatisticsSchema })[], C, E, P>
  >;

  allContributedProjects<E extends boolean = false, P extends PaginationTypes = 'keyset'>(
    userId: string | number,
    options?: PaginationRequestOptions<P> & AllUserProjectsOptions & Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<ProjectSchema[], C, E, P>>;

  allContributedProjects<E extends boolean = false, P extends PaginationTypes = 'offset'>(
    userId: string | number,
    options?: AllUserProjectsOptions & PaginationRequestOptions<P> & Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<ProjectSchema[], C, E, P>> {
    return RequestHelper.get<ProjectSchema[]>()(
      this,
      endpoint`users/${userId}/contributed_projects`,
      options,
    );
  }

  // Convenience method - Functionality already present in the all method in the Projects wrapper
  allStarredProjects<E extends boolean = false, P extends PaginationTypes = 'keyset'>(
    userId: string | number,
    options: PaginationRequestOptions<P> &
      AllUserProjectsOptions &
      Sudo &
      ShowExpanded<E> & { simple: true },
  ): Promise<GitlabAPIResponse<SimpleProjectSchema[], C, E, P>>;

  allStarredProjects<E extends boolean = false, P extends PaginationTypes = 'keyset'>(
    userId: string | number,
    options: PaginationRequestOptions<P> &
      AllUserProjectsOptions &
      Sudo &
      ShowExpanded<E> & { statistics: true },
  ): Promise<
    GitlabAPIResponse<(ProjectSchema & { statistics: ProjectStatisticsSchema })[], C, E, P>
  >;

  allStarredProjects<E extends boolean = false, P extends PaginationTypes = 'keyset'>(
    userId: string | number,
    options?: PaginationRequestOptions<P> & AllUserProjectsOptions & Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<ProjectSchema[], C, E, P>>;

  allStarredProjects<E extends boolean = false, P extends PaginationTypes = 'offset'>(
    userId: string | number,
    options?: AllUserProjectsOptions & PaginationRequestOptions<P> & Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<ProjectSchema[], C, E, P>> {
    return RequestHelper.get<ProjectSchema[]>()(
      this,
      endpoint`users/${userId}/starred_projects`,
      options,
    );
  }

  approve<E extends boolean = false>(
    userId: number,
    options?: Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<{ message: string }, C, E, void>> {
    return RequestHelper.post<{ message: string }>()(
      this,
      endpoint`users/${userId}/approve`,
      options,
    );
  }

  ban<E extends boolean = false>(
    userId: number,
    options?: Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<void, C, E, void>> {
    return RequestHelper.post<void>()(this, endpoint`users/${userId}/ban`, options);
  }

  block<E extends boolean = false>(
    userId: number,
    options?: Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<void, C, E, void>> {
    return RequestHelper.post<void>()(this, endpoint`users/${userId}/block`, options);
  }

  create<E extends boolean = false>(
    options?: CreateUserOptions & Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<ExpandedUserSchema, C, E, void>> {
    return RequestHelper.post<ExpandedUserSchema>()(this, 'users', options);
  }

  createPersonalAccessToken<E extends boolean = false>(
    userId: number,
    name: string,
    scopes: string[],
    options?: { expiresAt?: string } & Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<PersonalAccessTokenSchema, C, E, void>> {
    return RequestHelper.post<PersonalAccessTokenSchema>()(
      this,
      endpoint`users/${userId}/personal_access_tokens`,
      {
        name,
        scopes,
        ...options,
      },
    );
  }

  createCIRunner<E extends boolean = false>(
    runnerType: 'instance_type' | 'group_type' | 'project_type',
    options?: CreateUserCIRunnerOptions & Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<UserRunnerSchema, C, E, void>> {
    return RequestHelper.post<UserRunnerSchema>()(this, 'user/runners', {
      ...options,
      runnerType,
    });
  }

  deactivate<E extends boolean = false>(
    userId: number,
    options?: Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<void, C, E, void>> {
    return RequestHelper.post<void>()(this, endpoint`users/${userId}/deactivate`, options);
  }

  disableTwoFactor<E extends boolean = false>(
    userId: number,
    options?: Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<void, C, E, void>> {
    return RequestHelper.patch<void>()(this, endpoint`users/${userId}/disable_two_factor`, options);
  }

  edit<E extends boolean = false>(
    userId: number,
    { avatar, ...options }: EditUserOptions & Sudo & ShowExpanded<E> = {},
  ) {
    const opts: Record<string, unknown> = {
      ...options,
      isForm: true,
    };

    if (avatar) opts.avatar = [avatar.content, avatar.filename];

    return RequestHelper.put<ExpandedUserSchema>()(this, endpoint`users/${userId}`, opts);
  }

  editStatus<E extends boolean = false>(
    options?: {
      emoji?: string;
      message?: string;
      clearStatusAfter?:
        | '30_minutes'
        | '3_hours'
        | '8_hours'
        | '1_day'
        | '3_days'
        | '7_days'
        | '30_days';
    } & Sudo &
      ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<UserStatusSchema, C, E, void>> {
    return RequestHelper.put<UserStatusSchema>()(this, 'user/status', options);
  }

  editCurrentUserPreferences<E extends boolean = false>(
    viewDiffsFileByFile: boolean,
    showWhitespaceInDiffs: boolean,
    options?: Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<UserPreferenceSchema, C, E, void>> {
    return RequestHelper.put<UserPreferenceSchema>()(this, 'user/preferences', {
      viewDiffsFileByFile,
      showWhitespaceInDiffs,
      ...options,
    });
  }

  follow<E extends boolean = false>(
    userId: number,
    options?: Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<SimpleUserSchema, C, E, void>> {
    return RequestHelper.post<SimpleUserSchema>()(this, endpoint`users/${userId}/follow`, options);
  }

  reject<E extends boolean = false>(
    userId: number,
    options?: Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<{ message: string }, C, E, void>> {
    return RequestHelper.post<{ message: string }>()(
      this,
      endpoint`users/${userId}/reject`,
      options,
    );
  }

  show<A extends boolean = false, E extends boolean = false>(
    userId: number,
    options?: AsAdmin<A> & Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<A extends false ? UserSchema : AdminUserSchema, C, E, void>> {
    return RequestHelper.get<A extends false ? UserSchema : AdminUserSchema>()(
      this,
      endpoint`users/${userId}`,
      options,
    );
  }

  showCount<E extends boolean = false>(
    options?: Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<UserCountSchema, C, E, void>> {
    return RequestHelper.get<UserCountSchema>()(this, 'user_counts', options);
  }

  showAssociationsCount<E extends boolean = false>(
    userId: number,
    options?: Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<UserAssociationCountSchema, C, E, void>> {
    return RequestHelper.get<UserAssociationCountSchema>()(
      this,
      `users/${userId}/associations_count`,
      options,
    );
  }

  showCurrentUser<A extends boolean = false, E extends boolean = false>(
    options?: AsAdmin<A> & Sudo & ShowExpanded<E>,
  ): Promise<
    GitlabAPIResponse<A extends false ? ExpandedUserSchema : AdminUserSchema, C, E, void>
  > {
    return RequestHelper.get<A extends false ? ExpandedUserSchema : AdminUserSchema>()(
      this,
      'user',
      options,
    );
  }

  showCurrentUserPreferences<E extends boolean = false>(
    options?: Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<UserPreferenceSchema, C, E, void>> {
    return RequestHelper.get<UserPreferenceSchema>()(this, 'user/preferences', options);
  }

  showStatus<E extends boolean = false>({
    iDOrUsername,
    ...options
  }: { iDOrUsername?: string | number } & Sudo & ShowExpanded<E> = {}): Promise<
    GitlabAPIResponse<UserStatusSchema, C, E, void>
  > {
    let url: string;

    if (iDOrUsername) url = `users/${iDOrUsername}/status`;
    else url = 'user/status';

    return RequestHelper.get<UserStatusSchema>()(this, url, options);
  }

  remove<E extends boolean = false>(
    userId: number,
    options?: { hardDelete?: boolean } & Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<void, C, E, void>> {
    return RequestHelper.del()(this, endpoint`users/${userId}`, options);
  }

  removeAuthenticationIdentity<E extends boolean = false>(
    userId: number,
    provider: string,
    options?: Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<void, C, E, void>> {
    return RequestHelper.del()(this, endpoint`users/${userId}/identities/${provider}`, options);
  }

  unban<E extends boolean = false>(
    userId: number,
    options?: Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<void, C, E, void>> {
    return RequestHelper.post<void>()(this, endpoint`users/${userId}/unban`, options);
  }

  unblock<E extends boolean = false>(
    userId: number,
    options?: Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<void, C, E, void>> {
    return RequestHelper.post<void>()(this, endpoint`users/${userId}/unblock`, options);
  }

  unfollow<E extends boolean = false>(
    userId: number,
    options?: Sudo & ShowExpanded<E>,
  ): Promise<GitlabAPIResponse<UserSchema, C, E, void>> {
    return RequestHelper.post<UserSchema>()(this, endpoint`users/${userId}/unfollow`, options);
  }
}
