import { ContextListOptions, ContextListResult } from '../browser.js'
import { Exception } from '../exceptions/exception.js'
import type { GroupSchema } from '../models/store/Group.js'
import { RouteBuilder } from './RouteBuilder.js'
import {
  E_INVALID_JSON,
  E_RESPONSE_NO_VALUE,
  E_RESPONSE_STATUS,
  E_RESPONSE_UNKNOWN,
  SdkBase,
  type SdkOptions,
} from './SdkBase.js'

export class GroupsSdk extends SdkBase {
  /**
   * Creates a new group in the specified organization.
   * @param oid The organization ID where the group will be created.
   * @param info The information about the group to create. See the `GroupSchema` interface for required fields.
   * @param request Optional SDK options, including authentication token.
   * @returns A promise that resolves to the created group.
   */
  async create(oid: string, info: Partial<GroupSchema>, request: SdkOptions = {}): Promise<GroupSchema> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.groups(oid))
    const body = JSON.stringify(info)
    const result = await this.sdk.http.post(url.toString(), {
      token,
      body,
      headers: {
        'content-type': 'application/json',
      },
    })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to create a group. '
    if (result.status !== 201) {
      this.logInvalidResponse(result)
      throw this.createApiError(`${E_PREFIX}${E_RESPONSE_STATUS}${result.status}`, result.body)
    }

    if (!result.body) {
      throw new Exception(`${E_PREFIX}${E_RESPONSE_NO_VALUE}`, { code: 'E_RESPONSE_NO_VALUE', status: result.status })
    }
    let data: GroupSchema
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Exception(`${E_PREFIX}${E_INVALID_JSON}`, { code: 'E_INVALID_JSON', status: result.status })
    }
    if (!data.key) {
      throw new Exception(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`, { code: 'E_RESPONSE_UNKNOWN', status: result.status })
    }
    return data
  }

  /**
   * Lists user groups in the specified organization.
   * If you want to list specific user groups, you can use the `uid` option to filter by user ID.
   * @param oid The organization ID to list groups from.
   * @param options Optional parameters for filtering, sorting, and pagination.
   * @param request Optional SDK options, including authentication token.
   * @returns A promise that resolves to a list of groups in the organization.
   */
  async list(
    oid: string,
    options: ContextListOptions = {},
    request: SdkOptions = {}
  ): Promise<ContextListResult<GroupSchema>> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.groups(oid))
    this.sdk.appendListOptions(url, options)
    const result = await this.sdk.http.get(url.toString(), { token })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to create a group. '
    if (result.status !== 200) {
      this.logInvalidResponse(result)
      throw this.createApiError(E_PREFIX + E_RESPONSE_STATUS + result.status, result.body)
    }
    if (!result.body) {
      throw new Exception(E_PREFIX + E_RESPONSE_NO_VALUE, { code: 'E_RESPONSE_NO_VALUE', status: result.status })
    }
    let data: ContextListResult<GroupSchema>
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Exception(E_PREFIX + E_INVALID_JSON, { code: 'E_INVALID_JSON', status: result.status })
    }
    return data
  }

  /**
   * Updates an existing group in the specified organization.
   * Note that this method support PUT requests only. Some fields are ignored by the API and are set automatically.
   * @param oid The organization ID where the group exists.
   * @param updated The updated information for the group. Must include the `key` of the group to update.
   * @param request Optional SDK options, including authentication token.
   * @returns A promise that resolves to the updated group.
   */
  async update(oid: string, updated: GroupSchema, request: SdkOptions = {}): Promise<GroupSchema> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.group(oid, updated.key))
    const body = JSON.stringify(updated)
    const result = await this.sdk.http.put(url.toString(), {
      token,
      body,
      headers: {
        'content-type': 'application/json',
      },
    })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to update a group. '
    if (result.status !== 200) {
      this.logInvalidResponse(result)
      throw this.createApiError(`${E_PREFIX}${E_RESPONSE_STATUS}${result.status}`, result.body)
    }

    if (!result.body) {
      throw new Exception(`${E_PREFIX}${E_RESPONSE_NO_VALUE}`, { code: 'E_RESPONSE_NO_VALUE', status: result.status })
    }
    let data: GroupSchema
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Exception(`${E_PREFIX}${E_INVALID_JSON}`, { code: 'E_INVALID_JSON', status: result.status })
    }
    if (!data.key) {
      throw new Exception(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`, { code: 'E_RESPONSE_UNKNOWN', status: result.status })
    }
    return data
  }

  /**
   * Deletes a group in the specified organization.
   * The groups support soft deletion, meaning that the group is not immediately removed from the database.
   * @param oid The organization ID where the group exists.
   * @param gid The group ID to delete.
   * @param request Optional SDK options, including authentication token.
   * @returns A promise that resolves when the group is deleted.
   */
  async delete(oid: string, gid: string, request: SdkOptions = {}): Promise<void> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.group(oid, gid))
    const result = await this.sdk.http.delete(url.toString(), { token })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to delete a group. '
    if (result.status !== 204) {
      this.logInvalidResponse(result)
      throw this.createApiError(`${E_PREFIX}${E_RESPONSE_STATUS}${result.status}`, result.body)
    }
  }

  async addUsers(oid: string, gid: string, uids: string[], request: SdkOptions = {}): Promise<GroupSchema> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.groupUsers(oid, gid))
    const body = JSON.stringify({ ids: uids })
    const result = await this.sdk.http.post(url.toString(), {
      token,
      body,
      headers: {
        'content-type': 'application/json',
      },
    })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to add users to a group. '
    if (result.status !== 200) {
      this.logInvalidResponse(result)
      throw this.createApiError(`${E_PREFIX}${E_RESPONSE_STATUS}${result.status}`, result.body)
    }
    if (!result.body) {
      throw new Exception(`${E_PREFIX}${E_RESPONSE_NO_VALUE}`, { code: 'E_RESPONSE_NO_VALUE', status: result.status })
    }
    let data: GroupSchema
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Exception(`${E_PREFIX}${E_INVALID_JSON}`, { code: 'E_INVALID_JSON', status: result.status })
    }
    if (!data.key) {
      throw new Exception(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`, { code: 'E_RESPONSE_UNKNOWN', status: result.status })
    }
    return data
  }

  async removeUsers(oid: string, gid: string, uids: string[], request: SdkOptions = {}): Promise<GroupSchema> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.groupUsers(oid, gid))
    const body = JSON.stringify({ ids: uids })
    const result = await this.sdk.http.delete(url.toString(), {
      token,
      body,
      headers: {
        'content-type': 'application/json',
      },
    })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to remove users from a group. '
    if (result.status !== 200) {
      this.logInvalidResponse(result)
      throw this.createApiError(`${E_PREFIX}${E_RESPONSE_STATUS}${result.status}`, result.body)
    }
    if (!result.body) {
      throw new Exception(`${E_PREFIX}${E_RESPONSE_NO_VALUE}`, { code: 'E_RESPONSE_NO_VALUE', status: result.status })
    }
    let data: GroupSchema
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Exception(`${E_PREFIX}${E_INVALID_JSON}`, { code: 'E_INVALID_JSON', status: result.status })
    }
    if (!data.key) {
      throw new Exception(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`, { code: 'E_RESPONSE_UNKNOWN', status: result.status })
    }
    return data
  }
}
