import BlobFileReader from 'react-native/Libraries/Blob/FileReader';
import { Headers, NetworkRequestInfoRow, RequestMethod } from './types';
import fromEntries from './utils/fromEntries';

export default class NetworkRequestInfo {
  id = '';
  type = '';
  url = '';
  method: RequestMethod;
  status: number = -1;
  dataSent = '';
  responseContentType = '';
  responseSize = 0;
  requestHeaders: Headers = {};
  responseHeaders: Headers = {};
  response = '';
  responseURL = '';
  responseType = '';
  timeout = 0;
  closeReason = '';
  messages = '';
  serverClose = undefined;
  serverError = undefined;
  startTime: number = 0;
  endTime: number = 0;
  gqlOperation?: string;
  updatedAt: number = 0;

  constructor(id: string, type: string, method: RequestMethod, url: string) {
    this.id = id;
    this.type = type;
    this.method = method;
    this.url = url;
    this.updatedAt = Date.now();
  }

  get duration() {
    return this.endTime - this.startTime;
  }

  get curlRequest() {
    let headersPart =
      this.requestHeaders &&
      Object.entries(this.requestHeaders)
        .map(([key, value]) => `'${key}: ${this.escapeQuotes(value)}'`)
        .join(' -H ');
    headersPart = headersPart ? `-H ${headersPart}` : '';

    const body = this.dataSent && this.escapeQuotes(this.dataSent);

    const methodPart =
      this.method !== 'GET' ? `-X${this.method.toUpperCase()}` : '';
    const bodyPart = body ? `-d '${body}'` : '';

    const parts = ['curl', methodPart, headersPart, bodyPart, `'${this.url}'`];

    return parts.filter(Boolean).join(' ');
  }

  update(values: Partial<NetworkRequestInfo>) {
    Object.assign(this, values);
    if (values.dataSent) {
      const data = this.parseData(values.dataSent);
      this.gqlOperation = data?.operationName;
    }
    this.updatedAt = Date.now();
  }

  private escapeQuotes(value: string) {
    return value.replace?.(/'/g, `\\'`);
  }

  private parseData(data: any) {
    try {
      if (data?._parts?.length) {
        return fromEntries(data?._parts);
      }
      return JSON.parse(data);
    } catch {
      return { data };
    }
  }

  private stringifyFormat(data: any) {
    return JSON.stringify(this.parseData(data), null, 2);
  }

  public toRow(): NetworkRequestInfoRow {
    return {
      url: this.url,
      gqlOperation: this.gqlOperation,
      id: this.id,
      method: this.method,
      status: this.status,
      duration: this.duration,
      startTime: this.startTime,
    };
  }

  getRequestBody(replaceEscaped = false) {
    const body = this.stringifyFormat(this.dataSent);

    if (replaceEscaped) {
      return body.replace(/\\n/g, '\n').replace(/\\"/g, '"');
    }

    return body;
  }

  private async parseResponseBlob() {
    if (this.response === null || this.response === undefined) {
      return '';
    }

    const blobReader = new BlobFileReader();
    return await new Promise<string>((resolve, reject) => {
      const handleError = () => reject(blobReader.error);

      blobReader.addEventListener('load', () => {
        resolve(blobReader.result);
      });
      blobReader.addEventListener('error', handleError);
      blobReader.addEventListener('abort', handleError);

      try {
        blobReader.readAsText(this.response);
      } catch (error) {
        reject(error);
      }
    });
  }

  async getResponseBody() {
    if (this.endTime === 0 && this.status < 0) {
      return 'Pending response...';
    }

    try {
      const body = await (this.responseType !== 'blob'
        ? this.response
        : this.parseResponseBlob());

      if (body === '' || body === null || body === undefined) {
        return '';
      }

      return this.stringifyFormat(body);
    } catch {
      return '[Unable to load response body]';
    }
  }
}
