import { IHttpRequest } from '../../models/HttpRequest.js'
import { ISentRequest } from '../../models/SentRequest.js'
import { IResponse } from '../../models/Response.js'
import { IErrorResponse } from '../../models/ErrorResponse.js'
import { IHttpAction, IHttpFlow } from '../../models/http-flows/HttpFlows.js'
import { IHttpStep } from '../../models/http-flows/HttpStep.js'
import { HttpFlows, IStepValue } from './HttpFlows.js'

export type FlowValue = string | number | boolean | null | undefined | unknown

export class HttpFlowRunner extends HttpFlows {
  /**
   * Runs flows before the HTTP request is sent to the remote machine.
   * The request has to be pre-processed, that is, variables should already be applied to it.
   */
  async request(request: IHttpRequest, flows?: IHttpFlow[]): Promise<void> {
    if (!flows || !flows.length) {
      return
    }
    await this.runFlows(request, flows)
  }

  async response(
    request: IHttpRequest | ISentRequest,
    response: IResponse | IErrorResponse,
    flows?: IHttpFlow[]
  ): Promise<void> {
    if (!flows || !flows.length) {
      return
    }
    await this.runFlows(request, flows, response)
  }

  protected async runFlows(
    request: IHttpRequest | ISentRequest,
    flows: IHttpFlow[],
    response?: IResponse | IErrorResponse
  ): Promise<void> {
    for (const flow of flows) {
      const { condition, actions } = flow
      const passedCondition = await this.checkCondition(request, condition, response)
      if (!passedCondition) {
        continue
      }
      for (const action of actions) {
        try {
          await this.runAction(action, request, response)
        } catch (e) {
          // eslint-disable-next-line no-console
          console.warn(e)
          // if the step fails then we stop execution.
          // TODO: we should generate a report with the list of execution errors and present it in the UI.
          break
        }
      }
    }
  }

  async runAction(
    action: IHttpAction,
    request: IHttpRequest | ISentRequest,
    response?: IResponse | IErrorResponse
  ): Promise<void> {
    const { steps } = action
    if (!Array.isArray(steps) || !steps.length) {
      return
    }
    await this.runSteps(steps, request, response)
  }

  async checkCondition(
    request: IHttpRequest | ISentRequest,
    condition?: IHttpAction,
    response?: IResponse | IErrorResponse
  ): Promise<boolean> {
    if (!condition) {
      return true
    }
    const { steps = [] } = condition
    if (!Array.isArray(steps) || !steps.length) {
      return true
    }
    // the condition works with assertions. If the condition throws then it is not satisfied.
    try {
      await this.runSteps(steps, request, response)
      return true
    } catch {
      return false
    }
  }

  protected async runSteps(
    steps: IHttpStep[],
    request: IHttpRequest | ISentRequest,
    response?: IResponse | IErrorResponse
  ): Promise<void> {
    // this keeps a value from the last step to be passed to the next step.
    let lastResult: IStepValue = {
      value: undefined,
    }
    for (const step of steps) {
      lastResult = await this.runStep(request, step, lastResult, response)
    }
  }
}
