import {
  SdkBase,
  E_RESPONSE_STATUS,
  E_RESPONSE_NO_VALUE,
  E_INVALID_JSON,
  E_RESPONSE_UNKNOWN,
  SdkOptions,
} from './SdkBase.js'
import { RouteBuilder } from './RouteBuilder.js'
import type { ContextListOptions, ContextListResult } from '../../events/BaseEvents.js'
import type { IOrganization, UserOrganizationGrantType } from '../../models/store/Organization.js'
import type { InvitationSchema } from '../../models/store/Invitation.js'
import { SdkError } from './Errors.js'
import type { PatchInfo } from '../../patch/types.js'
import type { IUser } from '../../models/store/User.js'

export class OrganizationsSdk extends SdkBase {
  /**
   * Lists all user organizations.
   */
  async list(request: SdkOptions = {}): Promise<ContextListResult<IOrganization>> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.organizations())
    const result = await this.sdk.http.get(url.toString(), { token })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to list your organizations. '
    if (result.status !== 200) {
      this.logInvalidResponse(result)
      let e = this.createGenericSdkError(result.body)
      if (!e) {
        e = new SdkError(`${E_PREFIX}${E_RESPONSE_STATUS}${result.status}`, result.status)
        e.response = result.body
      }
      throw e
    }
    if (!result.body) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_NO_VALUE}`)
    }
    let data: ContextListResult<IOrganization>
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Error(`${E_PREFIX}${E_INVALID_JSON}`)
    }
    if (!Array.isArray(data.items)) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`)
    }
    return data
  }

  async create(orgName: string, request: SdkOptions = {}): Promise<IOrganization> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.organizations())
    const body = {
      name: orgName,
    }
    const result = await this.sdk.http.post(url.toString(), {
      body: JSON.stringify(body),
      token,
      headers: {
        'Content-Type': 'application/json',
      },
    })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to create an organization. '
    if (result.status !== 200) {
      throw new Error(`${E_PREFIX}Invalid response status: ${result.status}`)
    }
    if (!result.body) {
      throw new Error(`${E_PREFIX}Response has no value.`)
    }
    let data: IOrganization
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Error(`${E_PREFIX}${E_INVALID_JSON}`)
    }
    if (!data.kind) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`)
    }
    return data
  }

  /**
   * Lists all invitations for a given organization.
   * @param oid The organization ID.
   * @param request The request options.
   * @returns A promise that resolves to a list of invitations.
   */
  async listInvitations(oid: string, request: SdkOptions = {}): Promise<ContextListResult<InvitationSchema>> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.invitations(oid))
    const result = await this.sdk.http.get(url.toString(), { token })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to list organization invitations. '
    if (result.status !== 200) {
      this.logInvalidResponse(result)
      let e = this.createGenericSdkError(result.body)
      if (!e) {
        e = new SdkError(`${E_PREFIX}${E_RESPONSE_STATUS}${result.status}`, result.status)
        e.response = result.body
      }
      throw e
    }
    if (!result.body) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_NO_VALUE}`)
    }
    let data: ContextListResult<InvitationSchema>
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Error(`${E_PREFIX}${E_INVALID_JSON}`)
    }
    if (!Array.isArray(data.items)) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`)
    }
    return data
  }

  /**
   * Creates an invitation for a user to join an organization.
   * @param oid The organization ID.
   * @param email The email address of the user to invite.
   * @param grant_type The type of grant for the user.
   * @param name The name of the user (optional).
   * @param request The request options.
   * @returns A promise that resolves to the created invitation.
   */
  async createInvitation(
    oid: string,
    email: string,
    grant_type: UserOrganizationGrantType,
    name?: string,
    request: SdkOptions = {}
  ): Promise<InvitationSchema> {
    const url = this.sdk.getUrl(RouteBuilder.invitations(oid))
    const body = {
      email,
      name,
      grant_type,
    }
    const result = await this.sdk.http.post(url.toString(), {
      body: JSON.stringify(body),
      token: request.token,
      headers: {
        'Content-Type': 'application/json',
      },
    })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to create an invitation. '
    if (result.status !== 200) {
      throw new Error(`${E_PREFIX}Invalid response status: ${result.status}`)
    }
    if (!result.body) {
      throw new Error(`${E_PREFIX}Response has no value.`)
    }
    let data: InvitationSchema
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Error(`${E_PREFIX}${E_INVALID_JSON}`)
    }
    if (!data.kind) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`)
    }
    return data
  }

  /**
   * Finds an invitation by its token.
   * @param oid The organization ID.
   * @param token The invitation token.
   * @param request The request options.
   * @returns A promise that resolves to the found invitation.
   */
  async findInvitationByToken(oid: string, token: string, request: SdkOptions = {}): Promise<InvitationSchema> {
    const url = this.sdk.getUrl(RouteBuilder.findInvitation(oid))
    url.searchParams.append('token', token)
    const result = await this.sdk.http.get(url.toString(), request)
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to find invitation by token. '
    if (result.status !== 200) {
      throw new Error(`${E_PREFIX}Invalid response status: ${result.status}`)
    }
    if (!result.body) {
      throw new Error(`${E_PREFIX}Response has no value.`)
    }
    let data: InvitationSchema
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Error(`${E_PREFIX}${E_INVALID_JSON}`)
    }
    if (!data.kind) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`)
    }
    return data
  }

  /**
   * Declines an invitation.
   * @param oid The organization ID.
   * @param id The invitation ID.
   * @param request The request options.
   * @returns A promise that resolves when the invitation is declined.
   */
  async declineInvitation(oid: string, id: string, request: SdkOptions = {}): Promise<InvitationSchema> {
    const url = this.sdk.getUrl(RouteBuilder.declineInvitation(oid, id))
    const result = await this.sdk.http.post(url.toString(), request)
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to decline invitation. '
    if (result.status !== 200) {
      throw new Error(`${E_PREFIX}Invalid response status: ${result.status}`)
    }
    if (!result.body) {
      throw new Error(`${E_PREFIX}Response has no value.`)
    }
    let data: InvitationSchema
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Error(`${E_PREFIX}${E_INVALID_JSON}`)
    }
    if (!data.kind) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`)
    }
    return data
  }

  /**
   * Soft-deletes an invitation.
   * @param oid The organization ID.
   * @param id The invitation ID.
   * @param request The request options.
   * @returns A promise that resolves to the deleted invitation.
   */
  async deleteInvitation(oid: string, id: string, request: SdkOptions = {}): Promise<InvitationSchema> {
    const url = this.sdk.getUrl(RouteBuilder.invitation(oid, id))
    const result = await this.sdk.http.delete(url.toString(), request)
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to delete invitation. '
    if (result.status !== 200) {
      throw new Error(`${E_PREFIX}Invalid response status: ${result.status}`)
    }
    if (!result.body) {
      throw new Error(`${E_PREFIX}Response has no value.`)
    }
    let data: InvitationSchema
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Error(`${E_PREFIX}${E_INVALID_JSON}`)
    }
    if (!data.kind) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`)
    }
    return data
  }

  /**
   * Patches an invitation. The server performs the patch validation.
   * The API only allows to patch some of the invitation properties:
   * - name
   * - expires_at
   * - grant_type
   * @param oid The organization ID.
   * @param id The invitation ID.
   * @param info The patch information.
   * @param request The request options.
   * @returns A promise that resolves to the patched invitation.
   */
  async patchInvitation(oid: string, id: string, info: PatchInfo, request: SdkOptions = {}): Promise<InvitationSchema> {
    const url = this.sdk.getUrl(RouteBuilder.invitation(oid, id))
    const result = await this.sdk.http.patch(url.toString(), {
      body: JSON.stringify(info),
      token: request.token,
      headers: {
        'Content-Type': 'application/json',
      },
    })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to update invitation. '
    if (result.status !== 200) {
      throw new Error(`${E_PREFIX}Invalid response status: ${result.status}`)
    }
    if (!result.body) {
      throw new Error(`${E_PREFIX}Response has no value.`)
    }
    let data: InvitationSchema
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Error(`${E_PREFIX}${E_INVALID_JSON}`)
    }
    if (!data.kind) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`)
    }
    return data
  }

  /**
   * Lists users in the organization.
   *
   * @param oid The key of the organization we want to read the user from.
   * @param options Optional query options.
   * @param request Optional request options.
   */
  async listUsers(
    oid: string,
    options?: ContextListOptions,
    request: SdkOptions = {}
  ): Promise<ContextListResult<IUser>> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.organizationUsers(oid))
    this.sdk.appendListOptions(url, options)
    const result = await this.sdk.http.get(url.toString(), { token })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to list projects. '
    if (result.status !== 200) {
      this.logInvalidResponse(result)
      throw new Error(`${E_PREFIX}${E_RESPONSE_STATUS}${result.status}`)
    }
    if (!result.body) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_NO_VALUE}`)
    }
    let data: ContextListResult<IUser>
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Error(`${E_PREFIX}${E_INVALID_JSON}.`)
    }
    if (!Array.isArray(data.items)) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}.`)
    }
    return data
  }

  /**
   * Gets a user by its key in the organization.
   *
   * @param oid The key of the organization parent organization.
   * @param key The user key.
   * @param request Optional request options.
   * @returns The user object
   * @deprecated Use `organizations.readUser()` instead.
   */
  async getUser(oid: string, key: string, request: SdkOptions = {}): Promise<IUser> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.organizationUser(oid, key))
    const result = await this.sdk.http.get(url.toString(), { token })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to read the user info. '
    if (result.status !== 200) {
      this.logInvalidResponse(result)
      throw new Error(`${E_PREFIX}${E_RESPONSE_STATUS}${result.status}`)
    }
    if (!result.body) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_NO_VALUE}`)
    }
    let data: IUser
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Error(`${E_PREFIX}${E_INVALID_JSON}.`)
    }
    return data
  }
}
