import { html, nothing, PropertyValues, TemplateResult } from 'lit'
import { property, state } from 'lit/decorators.js'
import {
  type IRequestAuthorization,
  Kind as RequestAuthorizationKind,
} from '@api-client/core/models/RequestAuthorization.js'
import { AmfNamespace } from '@api-client/core/amf/definitions/Namespace.js'
import type { IApiParameter, IApiParametrizedSecurityScheme } from '@api-client/core/amf/definitions/Api.js'
import * as InputCache from '../../../amf/lib/InputCache.js'
import { Authorization } from '../../authorization/ui/Authorization.js'
import { AmfParameters } from '../../../amf/lib/AmfParameters.js'
import type { IPassThroughAuthorization } from '@api-client/core/models/Authorization.js'
import type { IApiNodeShape, IShapeUnion } from '@api-client/core/amf/definitions/Shapes.js'

export default class AmfPassThrough extends Authorization {
  /**
   * The in-memory key where the values are stored.
   */
  @property({ type: Object }) accessor security: IApiParametrizedSecurityScheme | undefined

  protected amfParameters: AmfParameters

  @state() accessor schemeName: string | undefined

  constructor() {
    super()
    this.amfParameters = new AmfParameters(this)
    this.amfParameters.globalCache = true
    this.amfParameters.addEventListener('render', () => this.requestUpdate())
    this.amfParameters.addEventListener('change', () => this.notifyChange())
    this.amfParameters.addEventListener('paramchange', () => this.handleParamChange())
  }

  protected override willUpdate(cp: PropertyValues<this>): void {
    if (cp.has('security')) {
      this.restoreValues()
    }
    super.willUpdate(cp)
  }

  protected handleParamChange(): void {
    const auth = this.ensureConfig()
    auth.config = this.serialize()
  }

  protected ensureConfig(): IRequestAuthorization {
    if (!this.authorization) {
      this.authorization = {
        enabled: true,
        kind: RequestAuthorizationKind,
        type: 'pass through',
        valid: false,
        config: {} as IPassThroughAuthorization,
      }
    }
    return this.authorization
  }

  protected restoreValues(): void {
    const { security } = this
    const auth = this.ensureConfig()
    const source = 'settings'
    const list = this.amfParameters.parametersValue
    this.amfParameters.parametersValue = list.filter((item) => item.source !== source)
    if (!security || !security.types.includes(AmfNamespace.aml.vocabularies.security.ParametrizedSecurityScheme)) {
      return
    }
    const { scheme } = security
    if (!scheme) {
      return
    }
    const { type } = scheme
    if (!type || !type.startsWith('Pass Through')) {
      return
    }

    const { headers, queryParameters, queryString } = scheme
    this.appendToParams(headers, 'header', true)
    this.appendToParams(queryParameters, 'query', true)
    if (queryString && (!queryParameters || !queryParameters.length)) {
      this.appendQueryString(queryString)
    }
    this.schemeName = security.name || scheme.name
    this.notifyChange()
    this.requestUpdate()
    auth.config = this.serialize()
  }

  appendQueryString(queryString: IShapeUnion): void {
    const object = queryString as IApiNodeShape
    if (!object.properties || !object.properties.length) {
      return
    }
    const list = object.properties.map((item) => {
      const { id, range, name, minCount = 0 } = item
      return {
        id,
        binding: 'query',
        schema: range,
        name,
        examples: [],
        payloads: [],
        types: [AmfNamespace.aml.vocabularies.apiContract.Parameter],
        required: minCount > 0,
        customDomainProperties: [],
      } as IApiParameter
    })
    this.appendToParams(list, 'query', true)
  }

  /**
   * Appends a list of parameters to the list of rendered parameters.
   * @param list
   * @param source
   * @param clear When set it clears the previously set parameters
   */
  appendToParams(list: IApiParameter[], source: 'header' | 'query', clear = false): void {
    const { amfParameters } = this
    let params = amfParameters.parametersValue
    if (clear) {
      params = params.filter((p) => p.source !== source)
    }
    if (Array.isArray(list)) {
      list.forEach((param) => {
        params.push({
          paramId: param.id,
          parameter: param,
          binding: param.binding || '',
          source,
          schema: param.schema,
          schemaId: param.schema && param.schema.id ? param.schema.id : undefined,
        })
      })
    }
    this.amfParameters.parametersValue = params
  }

  /**
   * Updates, if applicable, query parameter value.
   *
   * This does nothing if the query parameter has not been defined for the current
   * scheme.
   *
   * @param name The name of the changed parameter
   * @param newValue A value to apply. May be empty but must be defined.
   */
  updateQueryParameter(name: string, newValue: string): void {
    const list = this.amfParameters.parametersValue
    const param = list.find((i) => i.binding === 'query' && i.parameter.name === name)
    if (param) {
      InputCache.set(this, param.paramId, newValue, true)
      this.requestUpdate()
    }
  }

  /**
   * Updates, if applicable, header value.
   * This is supported for RAML custom scheme and Pass Through
   * that operates on headers model which is only an internal model.
   *
   * This does nothing if the header has not been defined for current
   * scheme.
   *
   * @param name The name of the changed header
   * @param newValue A value to apply. May be empty but must be defined.
   */
  updateHeader(name: string, newValue: string): void {
    const list = this.amfParameters.parametersValue
    const param = list.find((i) => i.binding === 'header' && i.parameter.name === name)
    if (param) {
      InputCache.set(this, param.paramId, newValue, true)
      this.requestUpdate()
    }
  }

  serialize(): IPassThroughAuthorization {
    const params = this.amfParameters.parametersValue
    const result: IPassThroughAuthorization = {}
    ;(params || []).forEach((param) => {
      if (!result[param.binding as keyof IPassThroughAuthorization]) {
        result[param.binding as keyof IPassThroughAuthorization] = {}
      }
      let value = InputCache.get(this, param.paramId, true) as string | boolean
      if (value === '' || value === undefined) {
        if (param.parameter.required === false) {
          return
        }
        value = ''
      }
      if (value === false && param.parameter.required === false) {
        return
      }
      if (value === null) {
        value = ''
      }
      ;(result[param.binding as keyof IPassThroughAuthorization] as Record<string, string>)[
        param.parameter.name as keyof IPassThroughAuthorization
      ] = value as string
    })
    return result
  }

  override checkValidity(): boolean {
    const { nilValues, parametersValue } = this.amfParameters
    const hasInvalid = parametersValue.some((param) => {
      if (nilValues.includes(param.paramId)) {
        return false
      }
      const value = InputCache.get(this, param.paramId, true)
      if (!value && !param.parameter.required) {
        return false
      }
      return !value
    })
    return !hasInvalid
  }

  override render(): TemplateResult {
    return html` ${this.renderHeader()} ${this.renderEditor()} `
  }

  protected renderEditor(): TemplateResult | typeof nothing {
    return html`
      <form autocomplete="on" class="auth-form passthrough-auth">
        ${this.headersTemplate()} ${this.queryTemplate()}
      </form>
      ${this.renderAuthInfo()}
    `
  }

  headersTemplate(): TemplateResult | typeof nothing {
    const headers = this.amfParameters.parametersValue.filter((item) => item.binding === 'header')
    if (!headers.length) {
      return nothing
    }
    return html`
      <div class="section-title"><span class="label">Headers</span></div>
      ${headers.map((param) => this.amfParameters.parameterTemplate(param))}
    `
  }

  queryTemplate(): TemplateResult | typeof nothing {
    const params = this.amfParameters.parametersValue.filter((item) => item.binding === 'query')
    if (!params.length) {
      return nothing
    }
    return html`
      <div class="section-title"><span class="label">Query parameters</span></div>
      ${params.map((param) => this.amfParameters.parameterTemplate(param))}
    `
  }

  protected override renderHeader(): TemplateResult | typeof nothing {
    const { schemeName = 'Pass Through' } = this
    return html`
      <div class="auth-header">
        <span class="auth-title">Scheme: ${schemeName}</span>
      </div>
    `
  }
}
