import { AxiosError } from "axios";
import _ from "lodash";
import { DTOModel, isDTO } from "./dto_model";

export interface FailureI {
  status?: number;
  message: string;
  key?: string;
  meta?: FailureMeta;
}

interface FailureMeta {
  isRequestFailure?: boolean;
  isNetworkFailure?: boolean;
  isPlainFailure?: boolean;
  data?: any;
  [prop: string]: any | undefined;
}

class Failure implements FailureI {
  message: string;
  key?: string | undefined;
  status?: number | undefined;
  meta?: FailureMeta;
  static isf = true;

  /**
   *
   * @param {Failure} o
   * @returns {boolean}
   */
  static isFailure(o: any): o is Failure {
    if (!_.isObjectLike(o)) return false;
    if (_.has(o, "message")) return true;
    return false;
  }

  constructor({ status, message, key, meta }: FailureI) {
    this.status = status;
    this.message = message;
    this.key = key;
    this.meta = meta;
    console.error([key ?? "", message].filter((i) => i.length !== 0).join(":"));
  }

  static fromReason(reason: AxiosError<any, any>, key?: string): Failure {
    
    try {
      if(!Failure.verifyContainsDTO(reason)) {
        throw reason;
      }
      if (reason.response) {
        return new Failure({
          status: reason.response!.status,
          message:
            reason.response!.data.message ?? "Unknown response error model",
          key: key,
          meta: {
            isRequestFailure: true,
            data: reason.response.data,
          },
        });
      }
      if (reason.request) {
        return new Failure({
          message: `Something went wrong while ${reason.config.url} had been called`,
          key: `unreachedResponse(${key})`,
          meta: {
            isNetworkFailure: true,
          },
        });
      }
      throw reason;
    } catch (error) {
      return new Failure({
        message: reason.message,
        key: key,
        meta: { isPlainFailure: true },
      });
    }
  }

  /**
   * Verifies that passed object matches [DTOModel] structure
   *
   * Otherwise it will rethrow passed object
   */
  static verifyContainsDTO(
    reason: AxiosError<any, any>
  ): reason is AxiosError<DTOModel<any>, any> {
    return [reason.response, reason.request].reduce((acc, obj) => {
      if (acc || isDTO(obj)) {
        return true;
      }
      return false;
    }, false);
  }

  toString(): string {
    return `${this.message} (${this.key ?? "key unknown"}) ${
      this.status ?? "status unknown"
    }`;
  }

  mapKeyWith(transpiler: (key: string) => string) {
    if (!this.key) {
      return this.message;
    }
    return transpiler(this.key);
  }
}

export default Failure;
