import {
  SdkBase,
  E_RESPONSE_STATUS,
  E_RESPONSE_NO_VALUE,
  E_INVALID_JSON,
  E_RESPONSE_UNKNOWN,
  SdkOptions,
} from './SdkBase.js'
import { ContextListResult, ContextListOptions, ContextChangeRecord } from '../events/BaseEvents.js'
import type {
  DataCatalogSchema,
  DataCatalogSchemaWithVersion,
  DataCatalogScope,
  DataCatalogStatus,
  DataCatalogVersionInfo,
} from '../models/DataCatalog.js'
import { RouteBuilder } from './RouteBuilder.js'
import { DataCatalogKind, DataCatalogVersionKind } from '../models/kinds.js'
import type { DataCatalogVersionSchema } from '../models/DataCatalogVersion.js'
import type { ForeignDomainDependency } from '../modeling/types.js'
import type { DataDomainSchema } from '../modeling/DataDomain.js'
import { Exception } from '../exceptions/exception.js'

export interface DataCatalogListOptions extends ContextListOptions {
  scope?: DataCatalogScope
}

export class DataCatalogSdk extends SdkBase {
  async list(
    options: DataCatalogListOptions = {},
    request: SdkOptions = {}
  ): Promise<ContextListResult<DataCatalogSchemaWithVersion>> {
    const { token } = request
    const opts = { ...options }
    const url = this.sdk.getUrl(RouteBuilder.dataCatalog())
    this.sdk.appendListOptions(url, opts)
    if (opts.scope) {
      url.searchParams.set('scope', opts.scope)
    }
    const result = await this.sdk.http.get(url.toString(), { token })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to list data domains. '
    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<DataCatalogSchemaWithVersion>
    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
  }

  /**
   * Lists a simple schema of versions for a given data catalog entry.
   * @param key The key of the Data Catalog entry to list versions for.
   */
  async listVersions(key: string, request: SdkOptions = {}): Promise<ContextListResult<DataCatalogVersionInfo>> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.dataCatalogEntryVersions(key))
    const result = await this.sdk.http.get(url.toString(), { token })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to list data domain versions. '
    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<DataCatalogVersionInfo>
    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
  }

  /**
   * Publishes a new data catalog entry.
   * To publish a version, use the `publishVersion` method.
   * @param entry The data catalog entry to publish.
   * @returns The created data catalog entry.
   */
  async publish(entry: DataCatalogSchema, request: SdkOptions = {}): Promise<ContextChangeRecord<DataCatalogSchema>> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.dataCatalog())
    const body = JSON.stringify(entry)
    const result = await this.sdk.http.post(url.toString(), {
      token,
      body,
      headers: {
        'content-type': 'application/json',
      },
    })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to publish data domain. '
    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<DataCatalogSchema>
    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 read(key: string, request: SdkOptions = {}): Promise<DataCatalogSchema> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.dataCatalogEntry(key))
    const result = await this.sdk.http.get(url.toString(), { token })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to read data domain. '
    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: DataCatalogSchema
    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 !== DataCatalogKind) {
      throw new Exception(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`, { code: 'E_RESPONSE_UNKNOWN', status: result.status })
    }
    return data
  }

  /**
   * Deprecates the entire data catalog entry.
   * To deprecate a version, use the `deprecateVersion` method.
   * @param key The key of the data catalog entry to deprecate.
   * @param reason The reason for deprecation.
   */
  async deprecate(
    key: string,
    reason: string,
    request: SdkOptions = {}
  ): Promise<ContextChangeRecord<DataCatalogSchema>> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.dataCatalogDeprecate(key))
    const body = JSON.stringify({ reason })
    const result = await this.sdk.http.put(url.toString(), {
      token,
      body,
      headers: {
        'content-type': 'application/json',
      },
    })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to deprecate data domain. '
    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<DataCatalogSchema>
    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 !== DataCatalogKind) {
      throw new Exception(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`, { code: 'E_RESPONSE_UNKNOWN', status: result.status })
    }
    return data
  }

  /**
   * Removes the data catalog entry from the catalog. The entry is preserved in the database
   * and the user can still access it. It is used to recover from error when the entry was published
   * by mistake.
   * @param key The key of the data catalog entry to unpublish.
   */
  async unpublish(key: string, request: SdkOptions = {}): Promise<ContextChangeRecord<DataCatalogSchema>> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.dataCatalogUnpublish(key))
    const result = await this.sdk.http.put(url.toString(), { token })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to unpublish data domain. '
    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<DataCatalogSchema>
    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 !== DataCatalogKind) {
      throw new Exception(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`, { code: 'E_RESPONSE_UNKNOWN', status: result.status })
    }
    return data
  }

  async publishVersion(
    key: string,
    version: DataCatalogVersionSchema,
    request: SdkOptions = {}
  ): Promise<ContextChangeRecord<DataCatalogVersionSchema>> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.dataCatalogEntryVersions(key))
    const body = JSON.stringify(version)
    const result = await this.sdk.http.post(url.toString(), {
      token,
      body,
      headers: {
        'content-type': 'application/json',
      },
    })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to publish data domain version. '
    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<DataCatalogVersionSchema>
    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 !== DataCatalogVersionKind) {
      throw new Exception(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`, { code: 'E_RESPONSE_UNKNOWN', status: result.status })
    }
    return data
  }

  async readVersion(key: string, version: string, request: SdkOptions = {}): Promise<DataCatalogVersionSchema> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.dataCatalogVersion(key, version))
    const result = await this.sdk.http.get(url.toString(), { token })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to read data domain version. '
    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: DataCatalogVersionSchema
    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 !== DataCatalogVersionKind) {
      throw new Exception(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`, { code: 'E_RESPONSE_UNKNOWN', status: result.status })
    }
    return data
  }

  async listDependencies(
    dependencies?: ForeignDomainDependency[],
    request: SdkOptions = {}
  ): Promise<ContextListResult<DataDomainSchema>> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.dataCatalogDependencies())
    const body = JSON.stringify({ items: dependencies })
    const result = await this.sdk.http.post(url.toString(), {
      token,
      body,
      headers: {
        'content-type': 'application/json',
      },
    })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to list data domain dependencies. '
    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<DataDomainSchema>
    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
  }

  async deprecateVersion(
    key: string,
    version: string,
    reason: string,
    request: SdkOptions = {}
  ): Promise<ContextChangeRecord<DataCatalogVersionSchema>> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.dataCatalogVersionDeprecate(key, version))
    const body = JSON.stringify({ reason })
    const result = await this.sdk.http.put(url.toString(), {
      token,
      body,
      headers: {
        'content-type': 'application/json',
      },
    })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to deprecate data domain version. '
    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<DataCatalogVersionSchema>
    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 !== DataCatalogVersionKind) {
      throw new Exception(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`, { code: 'E_RESPONSE_UNKNOWN', status: result.status })
    }
    return data
  }

  async unpublishVersion(
    key: string,
    version: string,
    request: SdkOptions = {}
  ): Promise<ContextChangeRecord<DataCatalogVersionSchema>> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.dataCatalogVersionUnpublish(key, version))
    const result = await this.sdk.http.put(url.toString(), { token })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to unpublish data domain version. '
    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<DataCatalogVersionSchema>
    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 !== DataCatalogVersionKind) {
      throw new Exception(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`, { code: 'E_RESPONSE_UNKNOWN', status: result.status })
    }
    return data
  }

  async checkPublicationStatus(domainId: string, request: SdkOptions = {}): Promise<DataCatalogStatus> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.dataCatalogStatus(domainId))
    const result = await this.sdk.http.get(url.toString(), { token })
    this.inspectCommonStatusCodes(result)
    const E_PREFIX = 'Unable to check data domain publication status. '
    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: DataCatalogStatus
    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 !== DataCatalogKind) {
      throw new Exception(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`, { code: 'E_RESPONSE_UNKNOWN', status: result.status })
    }
    return data
  }
}
