import {Response} from '@loopback/rest';
import * as AdmZip from 'adm-zip';
const AdmZipConst = require('adm-zip');

export abstract class GeneratedResponse {
  public abstract getResponse(response: Response): Response;
}

export function normalizeFileName(name: string) {
  const authorizedChars = `A-Z|a-z|0-9|:$\\.\\-_`;
  const regexpName = new RegExp('^[' + authorizedChars + ']+$', 'g');
  if (!name.match(regexpName)) {
    name = name.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
    name = name
      .replace(new RegExp('[^' + authorizedChars + ']', 'g'), '_')
      .replace(/__+/g, '_');
  }
  return name;
}

export class FileGeneratedResponse extends GeneratedResponse {
  constructor(public filePath: string, public fileName: string) {
    super();
  }
  getResponse(response: Response): Response {
    response.download(this.filePath, this.fileName);
    return response;
  }
}

export class Base64GeneratedResponse extends GeneratedResponse {
  constructor(
    public name: string,
    public base64: string,
    public type?: string,
  ) {
    super();
  }

  getResponse(response: Response): Response {
    response.header(
      'Content-Disposition',
      'attachment; filename="' + this.name + '"',
    );
    if (this.type) {
      response.type(this.type);
    }
    const buffer = Buffer.from(this.base64, 'base64');
    response.header('Content-Length', JSON.stringify(buffer.length));
    response.send(buffer);
    return response;
  }
}

export class ZipGeneratedResponse extends GeneratedResponse {
  public addedBufferSize = 0;
  public addedBase64Size = 0;
  private zip: AdmZip;
  constructor(public name: string) {
    super();

    this.zip = new AdmZipConst();
  }

  public addBuffer(name: string, buffer: Buffer) {
    this.addedBufferSize += buffer.length;
    this.zip.addFile(name, buffer);
  }

  public addBase64(name: string, base64: string) {
    this.addedBase64Size += base64.length;
    this.addBuffer(name, Buffer.from(base64, 'base64'));
  }

  getResponse(response: Response): Response {
    return new BufferGeneratedResponse(
      this.name + '.zip',
      this.zip.toBuffer(),
      'application/zip',
    ).getResponse(response);
  }
}

export class BufferGeneratedResponse extends GeneratedResponse {
  constructor(
    public name: string,
    public buffer: Buffer,
    public type?: string,
  ) {
    super();
  }

  getResponse(response: Response): Response {
    response.header(
      'Content-Disposition',
      'attachment; filename="' + normalizeFileName(this.name) + '"',
    );
    if (this.type) {
      response.type(this.type);
    }

    response.header('Content-Length', JSON.stringify(this.buffer.length));
    response.send(this.buffer);
    return response;
  }
}

export class JsonGeneratedResponse<T> extends GeneratedResponse {
  constructor(public json: T) {
    super();
  }
  getResponse(response: Response): Response {
    response.json(this.json);
    return response;
  }
}

export class BodyGeneratedResponse extends GeneratedResponse {
  constructor(
    public response: string,
    public contentType?: string,
    public noCache: boolean = true,
  ) {
    super();
  }
  getResponse(response: Response): Response {
    if (this.contentType && '' !== this.contentType) {
      response.set('Content-Type', this.contentType);
    } else {
      response.set('Content-Type', 'text/html');
    }
    if (this.noCache) {
      response.set(
        'Cache-Control',
        'private,no-cache,no-store,max-age=0,must-revalidate,pre-check=0,post-check=0',
      );
    }
    response.send(this.response);
    return response;
  }
}

export class TextGeneratedResponse extends BodyGeneratedResponse {
  constructor(public response: string, public noCache: boolean = true) {
    super(response, 'text/plain', noCache);
  }
}

export class HtmlGeneratedResponse extends BodyGeneratedResponse {
  constructor(public response: string, public noCache: boolean = true) {
    super(response, 'text/html', noCache);
  }
}

export class RedirectGeneratedResponse extends GeneratedResponse {
  constructor(public uri: string) {
    super();
  }

  getResponse(response: Response): Response {
    response.redirect(this.uri);
    return response;
  }
}
