import { IHttpRequest, HttpRequest } from '../models/HttpRequest.js'
import { ISentRequest, SentRequest } from '../models/SentRequest.js'
import { IHttpResponse, HttpResponse } from '../models/HttpResponse.js'
import { IErrorResponse, ErrorResponse } from '../models/ErrorResponse.js'
import { FlowRequestDataEnum, FlowResponseDataEnum, FlowSourceEnum } from '../models/http-flows/HttpFlows.js'
import { Headers } from '../lib/headers/Headers.js'
import { DataExtractor } from './DataExtractor.js'

/**
 * A class to extract data from the request or the response.
 */
export class RequestDataExtractor {
  /**
   * The request that has been sent to the server.
   */
  request: HttpRequest | SentRequest

  /**
   * The response object
   */
  response?: HttpResponse | ErrorResponse

  constructor(request: IHttpRequest | ISentRequest, response?: IHttpResponse | IErrorResponse) {
    this.request = new HttpRequest(request)
    if (response) {
      if (ErrorResponse.isErrorResponse(response)) {
        this.response = new ErrorResponse(response as IErrorResponse)
      } else {
        this.response = new HttpResponse(response)
      }
    }
  }

  /**
   * Reads the data from the selected path.
   *
   * @param source The source of the data to read. Note, variables are not supported here.
   * @param data The kind of data to read the value from
   * @param path The path to the data.
   * @return Data to be processed
   */
  async extract(
    source: FlowSourceEnum,
    data?: FlowRequestDataEnum | FlowResponseDataEnum,
    path?: string
  ): Promise<string | number | undefined> {
    const args = path ? path.split('.') : []
    switch (data) {
      case 'url':
        return DataExtractor.getDataUrl(this.getUrl(), args)
      case 'headers':
        return DataExtractor.getDataHeaders(this.getHeaders(source), args)
      case 'status':
        return this.response && this.response.status
      case 'statusText':
        return this.response && this.response.statusText
      case 'method':
        return this.request.method
      case 'body':
        return this.processBody(path, source)
      case 'cookie':
        return DataExtractor.readCookieValue(args[0], this.response)
      default:
        throw new Error(`Unknown data ${data} for ${source} data`)
    }
  }

  /**
   * @param source The source name
   * @returns The URL of the request
   */
  getUrl(): string {
    return this.request.url
  }

  /**
   * @param source The source name
   * @returns The headers from the request / response
   */
  getHeaders(source?: FlowSourceEnum): string {
    return DataExtractor.readHeaders(source === 'request' ? this.request : this.response)
  }

  async processBody(path?: string, source?: FlowSourceEnum): Promise<string | undefined> {
    const value = await this.getBody(source)
    if (!value) {
      return undefined
    }
    if (!path) {
      return value
    }
    const headersValue = this.getHeaders(source)
    if (!headersValue) {
      return undefined
    }
    const headers = new Headers(headersValue)
    const mime = headers.get('content-type')
    if (!mime) {
      return undefined
    }
    return DataExtractor.extractBody(value, mime, path)
  }

  /**
   * @param source The source name
   * @returns The headers from the request / response
   */
  async getBody(source?: FlowSourceEnum): Promise<string | undefined> {
    if (source === 'request') {
      return this.request.readPayloadAsString()
    }
    if (this.response) {
      return this.response.readPayloadAsString()
    }
    return undefined
  }

  /**
   * @deprecated Use `DataExtractor.extractBody()` instead.
   */
  async extractBody(value: string, mime: string, path: string): Promise<string | undefined> {
    return DataExtractor.extractBody(value, mime, path)
  }
}
