import { IError_Response_DTO } from "@shared/contexts";
import { NextFunction, Request, Response } from "express";
import httpStatus from "http-status";
import { URL } from "url";
import { IServerError } from "../../domain/errors";
import { IController } from "../Controller.interface";
import { InfrastructureError } from "../InfrastructureError";
import { generateExpressError } from "./ExpressErrorResponse";

export abstract class ExpressController implements IController {
  protected req: Request;
  protected res: Response;
  protected next: NextFunction;

  protected serverURL: string = "";
  protected file: any;

  protected abstract executeImpl(): Promise<void | any>;

  public execute(req: Request, res: Response, next: NextFunction): void {
    this.req = req;
    this.res = res;
    this.next = next;

    this.serverURL = `${
      new URL(`${this.req.protocol}://${this.req.get("host")}${this.req.originalUrl}`).origin
    }/api/v1`;

    //this.file = this.req && this.req["file"]; // <-- ????

    this.executeImpl();
  }

  public ok<T>(dto?: T) {
    if (dto) {
      return this._jsonResponse(200, dto);
    }

    return this.res.status(200).send();
  }

  public fail(error: IServerError) {
    console.error("ExpressController FAIL RESPONSE:", error);

    return this._errorResponse(httpStatus.INTERNAL_SERVER_ERROR, error.message);
  }

  public created<T>(dto?: T) {
    if (dto) {
      return this.res.status(httpStatus.CREATED).json(dto).send();
    }

    return this.res.status(httpStatus.CREATED).send();
  }

  public noContent() {
    return this.res.status(httpStatus.NO_CONTENT).send();
  }

  public sendFile(filepath: string) {
    return this.res.sendFile(filepath);
  }

  public downloadFile(filepath: string, filename: string, done?: any) {
    return this.res.download(filepath, filename, done);
  }

  public downloadPDF(pdfBuffer: Buffer, filename: string) {
    return this._download(pdfBuffer, "application/pdf", `${filename}`);
  }

  public clientError(message?: string) {
    return this._errorResponse(httpStatus.BAD_REQUEST, message);
  }

  public unauthorizedError(message?: string) {
    return this._errorResponse(httpStatus.UNAUTHORIZED, message);
  }

  public paymentRequiredError(message?: string) {
    return this._errorResponse(httpStatus.PAYMENT_REQUIRED, message);
  }

  public forbiddenError(message?: string) {
    return this._errorResponse(httpStatus.FORBIDDEN, message);
  }

  public notFoundError(message: string, error?: IServerError) {
    return this._errorResponse(httpStatus.NOT_FOUND, message, error);
  }

  public conflictError(message: string, error?: IServerError) {
    return this._errorResponse(httpStatus.CONFLICT, message, error);
  }

  public invalidInputError(message?: string, error?: InfrastructureError) {
    return this._errorResponse(httpStatus.UNPROCESSABLE_ENTITY, message, error);
  }

  public tooManyError(message: string, error?: Error) {
    return this._errorResponse(httpStatus.TOO_MANY_REQUESTS, message, error);
  }

  public internalServerError(message?: string, error?: IServerError) {
    return this._errorResponse(httpStatus.INTERNAL_SERVER_ERROR, message, error);
  }

  public todoError(message?: string) {
    return this._errorResponse(httpStatus.NOT_IMPLEMENTED, message);
  }

  public unavailableError(message?: string) {
    return this._errorResponse(httpStatus.SERVICE_UNAVAILABLE, message);
  }

  private _jsonResponse(statusCode: number, jsonPayload: any): Response<any> {
    return this.res.status(statusCode).json(jsonPayload).send();
  }

  private _errorResponse(
    statusCode: number,
    message?: string,
    error?: Error | InfrastructureError
  ): Response<IError_Response_DTO> {
    return generateExpressError(this.req, this.res, statusCode, message, error);
  }

  private _download(buffer: Buffer, contentType: string, filename: string) {
    this.res.set({
      "Content-Type": contentType,
      "Content-Disposition": `attachment; filename=${filename}`,
      //"Content-Length": buffer.length,
    });

    return this.res.send(buffer);
  }
}
