/* eslint-disable @typescript-eslint/no-extraneous-class */
import { RequestActions as LegacyRequestActions } from '../../models/legacy/request/ArcRequest.js'
import {
  RunnableAction as LegacyRunnable,
  Condition as LegacyCondition,
  Action as LegacyAction,
  SetCookieConfig,
  SetVariableConfig,
  DeleteCookieConfig,
  DataSourceConfiguration,
} from '../../models/legacy/actions/Actions.js'
import { IHttpFlow, IHttpAction, FlowSourceEnum, FlowRequestDataEnum, FlowResponseDataEnum } from './HttpFlows.js'
import { AmfNamespace } from '../../amf/definitions/Namespace.js'
import { IHttpStep } from './HttpStep.js'
import { IReadDataStep, ReadDataStep, ReadDataStepKind } from './steps/data/ReadDataStep.js'
import { ISetDataStep, SetDataStepKind } from './steps/data/SetDataStep.js'
import { ISetCookieStep, SetCookieStepKind } from './steps/data/SetCookieStep.js'
import { DeleteCookieStepKind, IDeleteCookieStep } from './steps/data/DeleteCookieStep.js'
import { IncludesAssertion } from './steps/assertion/IncludesAssertion.js'
import { EqualAssertion } from './steps/assertion/EqualAssertion.js'
import { GreaterThanAssertion } from './steps/assertion/GreaterThanAssertion.js'
import { LessThanAssertion } from './steps/assertion/LessThanAssertion.js'
import { NotEqualAssertion } from './steps/assertion/NotEqualAssertion.js'
import { MatchesAssertion } from './steps/assertion/MatchesAssertion.js'

/**
 * Allows to translate the old ARC actions into Action flows.
 */
export class LegacyTranslator {
  static translate(action: LegacyRequestActions): IHttpFlow[] {
    const { request, response } = action
    const result: IHttpFlow[] = []

    if (Array.isArray(request) && request.length) {
      request.forEach((runnable, index) => {
        const flow = this._runnableIntoFlow(runnable, 'request')
        flow.description = `Translated flow ${index + 1}`
        result.push(flow)
      })
    }

    if (Array.isArray(response) && response.length) {
      response.forEach((runnable, index) => {
        const flow = this._runnableIntoFlow(runnable, 'response')
        flow.description = `Translated flow ${index + 1}`
        result.push(flow)
      })
    }
    return result
  }

  protected static _runnableIntoFlow(runnable: LegacyRunnable, trigger: 'request' | 'response'): IHttpFlow {
    const flow: IHttpFlow = {
      trigger,
      actions: [],
    }
    const { actions, condition } = runnable
    if (condition) {
      flow.condition = this._translateCondition(condition)
    }
    actions.forEach((action, index) => {
      const current: IHttpAction = {
        steps: this._translateAction(action),
      }
      if (action.name) {
        current.description = action.name
      } else {
        current.description = `Translated action ${index + 1}`
      }
      flow.actions.push(current)
    })
    return flow
  }

  protected static _translateCondition(runnable: LegacyCondition): IHttpAction | undefined {
    const { source, alwaysPass, operator, path, predictedValue, type } = runnable
    if (alwaysPass) {
      // in the flow, if there's no condition the steps are always executed.
      return undefined
    }
    if (!path || !type) {
      return undefined
    }
    if (!['request', 'response'].includes(type)) {
      return
    }

    const result: IHttpAction = {
      steps: [],
    }
    const readData = new ReadDataStep()
    readData.source = type === 'request' ? FlowSourceEnum.request : FlowSourceEnum.response
    readData.path = path
    if (source) {
      if (type === 'request') {
        readData.data = source as FlowRequestDataEnum
      } else if (type === 'response') {
        readData.data = source as FlowResponseDataEnum
      }
    }
    result.steps.push(readData.toJSON())
    if (!operator) {
      return result
    }
    if (operator === 'contains') {
      const assertion = new IncludesAssertion()
      if (predictedValue) {
        assertion.value = String(predictedValue)
      }
      result.steps.push(assertion.toJSON())
    } else if (operator === 'equal') {
      const assertion = new EqualAssertion()
      if (predictedValue) {
        assertion.value = String(predictedValue)
      }
      result.steps.push(assertion.toJSON())
    } else if (operator === 'not-equal') {
      const assertion = new NotEqualAssertion()
      if (predictedValue) {
        assertion.value = String(predictedValue)
      }
      result.steps.push(assertion.toJSON())
    } else if (operator === 'greater-than') {
      const assertion = new GreaterThanAssertion()
      if (predictedValue) {
        const typed = Number(predictedValue)
        if (!Number.isNaN(typed)) {
          assertion.value = typed
        }
      }
      result.steps.push(assertion.toJSON())
    } else if (operator === 'greater-than-equal') {
      const assertion = new GreaterThanAssertion()
      assertion.equal = true
      if (predictedValue) {
        const typed = Number(predictedValue)
        if (!Number.isNaN(typed)) {
          assertion.value = typed
        }
      }
      result.steps.push(assertion.toJSON())
    } else if (operator === 'less-than') {
      const assertion = new LessThanAssertion()
      if (predictedValue) {
        const typed = Number(predictedValue)
        if (!Number.isNaN(typed)) {
          assertion.value = typed
        }
      }
      result.steps.push(assertion.toJSON())
    } else if (operator === 'less-than-equal') {
      const assertion = new LessThanAssertion()
      assertion.equal = true
      if (predictedValue) {
        const typed = Number(predictedValue)
        if (!Number.isNaN(typed)) {
          assertion.value = typed
        }
      }
      result.steps.push(assertion.toJSON())
    } else if (operator === 'regex') {
      const assertion = new MatchesAssertion()
      if (predictedValue) {
        assertion.value = String(predictedValue)
      }
      result.steps.push(assertion.toJSON())
    }
    return result
  }

  protected static _translateAction(item: LegacyAction): IHttpStep[] {
    switch (item.name) {
      case 'set-cookie':
        return this._translateSetCookieAction(item)
      case 'set-variable':
        return this._translateSetVariableAction(item)
      case 'delete-cookie':
        return this._translateDeleteCookieAction(item)
      default:
        return []
    }
  }

  protected static _translateSetCookieAction(item: LegacyAction): IHttpStep[] {
    const legacy = item.config as SetCookieConfig

    const steps: IHttpStep[] = []
    const ds = this._translateDataSource(legacy.source)
    steps.push(ds)

    const action: ISetCookieStep = {
      kind: SetCookieStepKind,
      name: legacy.name,
    }
    if (item.enabled === false) {
      action.enabled = item.enabled
    }
    if (legacy.url) {
      action.url = legacy.url
    }
    if (legacy.expires) {
      action.expires = legacy.expires
    }
    if (typeof legacy.hostOnly === 'boolean') {
      action.hostOnly = legacy.hostOnly
    }
    if (typeof legacy.httpOnly === 'boolean') {
      action.httpOnly = legacy.httpOnly
    }
    if (typeof legacy.secure === 'boolean') {
      action.secure = legacy.secure
    }
    if (typeof legacy.session === 'boolean') {
      action.session = legacy.session
    }
    steps.push(action)

    return steps
  }

  protected static _translateSetVariableAction(item: LegacyAction): IHttpStep[] {
    const legacy = item.config as SetVariableConfig

    const steps: IHttpStep[] = []
    const ds = this._translateDataSource(legacy.source)
    steps.push(ds)

    const action: ISetCookieStep = {
      kind: SetCookieStepKind,
      name: legacy.name,
    }
    if (item.enabled === false) {
      action.enabled = item.enabled
    }
    steps.push(action)

    return steps
  }

  protected static _translateDeleteCookieAction(item: LegacyAction): IHttpStep[] {
    const legacy = item.config as DeleteCookieConfig

    const steps: IHttpStep[] = []

    const action: IDeleteCookieStep = {
      kind: DeleteCookieStepKind,
    }
    if (item.enabled === false) {
      action.enabled = item.enabled
    }
    if (legacy.name && !legacy.removeAll) {
      action.name = legacy.name
    }
    if (legacy.url && !legacy.useRequestUrl) {
      action.url = legacy.url
    }
    steps.push(action)

    return steps
  }

  protected static _translateDataSource(source: DataSourceConfiguration): IHttpStep {
    if (source.value) {
      const dataSet: ISetDataStep = {
        kind: SetDataStepKind,
        value: source.value,
        dataType: AmfNamespace.w3.xmlSchema.string,
      }
      return dataSet
    }

    // Note, there's no way for us to translate the old "iterator" configuration
    // to the current syntax. We just drop it.

    const { path, type, source: legacySource } = source
    const result: IReadDataStep = {
      kind: ReadDataStepKind,
    }

    if (typeof path === 'string') {
      result.path = path
    }
    if (type) {
      switch (type) {
        case 'request':
          result.source = FlowSourceEnum.request
          break
        case 'response':
          result.source = FlowSourceEnum.response
          break
      }
    }
    if (type && legacySource) {
      if (type === 'request') {
        result.data = legacySource as FlowRequestDataEnum
      }
      if (type === 'response') {
        result.data = legacySource as FlowResponseDataEnum
      }
    }
    return result
  }
}
