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,
} from '../../models/DataCatalog.js'
import { RouteBuilder } from './RouteBuilder.js'
import { SdkError } from './Errors.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'

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)
      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<DataCatalogSchemaWithVersion>
    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 listVersions(
    key: string,
    request: SdkOptions = {}
  ): Promise<ContextListResult<{ key: string; version: string; published: number }>> {
    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)
      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<{ key: string; version: string; published: number }>
    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
  }

  /**
   * 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)
      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: ContextChangeRecord<DataCatalogSchema>
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Error(`${E_PREFIX}${E_INVALID_JSON}.`)
    }
    if (!data.key) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}.`)
    }
    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)
      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: DataCatalogSchema
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Error(`${E_PREFIX}${E_INVALID_JSON}.`)
    }
    if (data.kind !== DataCatalogKind) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}.`)
    }
    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)
      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: ContextChangeRecord<DataCatalogSchema>
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Error(`${E_PREFIX}${E_INVALID_JSON}.`)
    }
    if (data.kind !== DataCatalogKind) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}.`)
    }
    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)
      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: ContextChangeRecord<DataCatalogSchema>
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Error(`${E_PREFIX}${E_INVALID_JSON}.`)
    }
    if (data.kind !== DataCatalogKind) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}.`)
    }
    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)
      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: ContextChangeRecord<DataCatalogVersionSchema>
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Error(`${E_PREFIX}${E_INVALID_JSON}.`)
    }
    if (data.kind !== DataCatalogVersionKind) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}.`)
    }
    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)
      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: DataCatalogVersionSchema
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Error(`${E_PREFIX}${E_INVALID_JSON}.`)
    }
    if (data.kind !== DataCatalogVersionKind) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}.`)
    }
    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)
      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<DataDomainSchema[]>
    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 deprecateVersion(
    key: string,
    version: string,
    reason: string,
    request: SdkOptions = {}
  ): Promise<ContextChangeRecord<DataCatalogVersionSchema>> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.dataCatalogVersion(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)
      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: ContextChangeRecord<DataCatalogVersionSchema>
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Error(`${E_PREFIX}${E_INVALID_JSON}.`)
    }
    if (data.kind !== DataCatalogVersionKind) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}.`)
    }
    return data
  }

  async unpublishVersion(
    key: string,
    version: string,
    request: SdkOptions = {}
  ): Promise<ContextChangeRecord<DataCatalogVersionSchema>> {
    const { token } = request
    const url = this.sdk.getUrl(RouteBuilder.dataCatalogVersion(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)
      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: ContextChangeRecord<DataCatalogVersionSchema>
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Error(`${E_PREFIX}${E_INVALID_JSON}.`)
    }
    if (data.kind !== DataCatalogVersionKind) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}.`)
    }
    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)
      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: DataCatalogStatus
    try {
      data = JSON.parse(result.body)
    } catch {
      throw new Error(`${E_PREFIX}${E_INVALID_JSON}.`)
    }
    if (data.kind !== DataCatalogKind) {
      throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}.`)
    }
    return data
  }
}
