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 { IHttpHistory, IHttpHistoryBulkAdd, Kind as HttpHistoryKind } from '../models/HttpHistory.js'
import {
  ContextChangeRecord,
  ContextDeleteRecord,
  ContextListResult,
  HistoryListOptions,
  IBulkOperationResult,
  IHistoryRequestListOptions,
} from '../events/BaseEvents.js'
import { Exception } from '../exceptions/exception.js'

export class HistorySdk extends SdkBase {
  /**
   * Creates a history object.
   * A history object can be created per app (type = app) or a store's organization/[project/[request]].
   *
   * The user can always read their own history. If the history is created for a organization/project then
   * history records are shared as any other object in the organization.
   *
   * Note, history objects cannot be updated. They can only be created or deleted.
   *
   * @param history The history to create
   * @param request Optional request options.
   * @returns The key of the created history.
   */
  async add(history: IHttpHistory, request: SdkOptions = {}): Promise<ContextChangeRecord<IHttpHistory>> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.history())
    const body = JSON.stringify(history)
    const result = await this.sdk.http.post(url.toString(), { token, body })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to create a history. '
    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: ContextChangeRecord<IHttpHistory>
    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
  }

  /**
   * Creates a multiple history objects in a batch operation.
   *
   * @param info The bulk create info object.
   * @param request Optional request options.
   */
  async createBulk(
    info: IHttpHistoryBulkAdd,
    request: SdkOptions = {}
  ): Promise<IBulkOperationResult<ContextChangeRecord<IHttpHistory>>> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.historyBatchCreate())
    const body = JSON.stringify(info)
    const result = await this.sdk.http.post(url.toString(), { token, body })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to create a bulk history. '
    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: IBulkOperationResult<ContextChangeRecord<IHttpHistory>>
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Exception(`${E_PREFIX}${E_INVALID_JSON}`, { code: 'E_INVALID_JSON', status: result.status })
    }
    if (!data.items) {
      throw new Exception(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`, { code: 'E_RESPONSE_UNKNOWN', status: result.status })
    }
    return data
  }

  /**
   * Lists the history.
   *
   * @param options Optional query options.
   * @param request Optional request options.
   */
  async list(options: HistoryListOptions, request: SdkOptions = {}): Promise<ContextListResult<IHttpHistory>> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.history())
    this.appendHistoryListParameters(url, options)
    const result = await this.sdk.http.get(url.toString(), { token })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to list history. '
    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<IHttpHistory>
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Exception(`${E_PREFIX}${E_INVALID_JSON}`, { code: 'E_INVALID_JSON', status: result.status })
    }
    if (!Array.isArray(data.items)) {
      throw new Exception(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`, { code: 'E_RESPONSE_UNKNOWN', status: result.status })
    }
    return data
  }

  appendHistoryListParameters(url: URL, options: HistoryListOptions): void {
    this.sdk.appendListOptions(url, options)
    const { searchParams } = url
    searchParams.set('organization', options.organization)
    if (options.type) {
      searchParams.set('type', options.type)
    }
    const projectOptions = options as IHistoryRequestListOptions
    if (projectOptions.id) {
      searchParams.set('id', projectOptions.id)
    }
    if (projectOptions.user) {
      searchParams.set('user', 'true')
    }
    if (projectOptions.project) {
      searchParams.set('project', projectOptions.project)
    }
  }

  /**
   * Deletes a history object form the store.
   *
   * @param key The key returned by the store when created the history. Also available via the
   *   `key` property on the history object.
   * @param request Optional request options.
   */
  async delete(key: string, request: SdkOptions = {}): Promise<void> {
    const { token } = request
    const path = RouteBuilder.historyItem(key)
    const url = this.sdk.getUrl(path)

    const result = await this.sdk.http.delete(url.toString(), { token })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to delete history. '
    if (result.status !== 204) {
      this.logInvalidResponse(result)
      throw this.createApiError(`${E_PREFIX}${E_RESPONSE_STATUS}${result.status}`, result.body)
    }
  }

  /**
   * Deletes history in a bulk operation.
   * @param keys The list of keys of history objects to delete.
   * @param request Optional request options.
   * @returns The list of delete records.
   */
  async deleteBulk(keys: string[], request: SdkOptions = {}): Promise<IBulkOperationResult<ContextDeleteRecord>> {
    const { token } = request
    const path = RouteBuilder.historyBatchDelete()
    const url = this.sdk.getUrl(path)
    const result = await this.sdk.http.post(url.toString(), { token, body: JSON.stringify(keys) })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to delete history. '
    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: IBulkOperationResult<ContextDeleteRecord>
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Exception(`${E_PREFIX}${E_INVALID_JSON}`, { code: 'E_INVALID_JSON', status: result.status })
    }
    if (!Array.isArray(data.items)) {
      throw new Exception(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`, { code: 'E_RESPONSE_UNKNOWN', status: result.status })
    }
    return data
  }

  /**
   * Reads a history definition from the store.
   * @param key The history key
   * @param request Optional request options.
   * @returns The history object
   */
  async read(key: string, request: SdkOptions = {}): Promise<IHttpHistory> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.historyItem(key))
    const result = await this.sdk.http.get(url.toString(), { token })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to read a history. '
    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: IHttpHistory
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Exception(`${E_PREFIX}${E_INVALID_JSON}`, { code: 'E_INVALID_JSON', status: result.status })
    }
    if (data.kind !== HttpHistoryKind) {
      throw new Exception(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`, { code: 'E_RESPONSE_UNKNOWN', status: result.status })
    }
    return data
  }

  async clearOrganization(organizationKey: string, request: SdkOptions = {}): Promise<void> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.historyClear('organization', organizationKey))
    const result = await this.sdk.http.delete(url.toString(), { token })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to clear history for the organization. '
    if (result.status !== 204) {
      this.logInvalidResponse(result)
      throw this.createApiError(`${E_PREFIX}${E_RESPONSE_STATUS}${result.status}`, result.body)
    }
  }

  async clearProject(organizationKey: string, projectKey: string, request: SdkOptions = {}): Promise<void> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.historyClear('project', organizationKey, projectKey))
    const result = await this.sdk.http.delete(url.toString(), { token })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to clear history for the project. '
    if (result.status !== 204) {
      this.logInvalidResponse(result)
      throw this.createApiError(`${E_PREFIX}${E_RESPONSE_STATUS}${result.status}`, result.body)
    }
  }

  async clearRequest(
    organizationKey: string,
    projectKey: string,
    requestKey: string,
    request: SdkOptions = {}
  ): Promise<void> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.historyClear('request', organizationKey, projectKey, requestKey))
    const result = await this.sdk.http.delete(url.toString(), { token })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to clear history for the request. '
    if (result.status !== 204) {
      this.logInvalidResponse(result)
      throw this.createApiError(`${E_PREFIX}${E_RESPONSE_STATUS}${result.status}`, result.body)
    }
  }
}
