import { Lambda } from "aws-sdk";
import { Schema } from "jsonschema";
import * as jsonschema from "jsonschema";
import * as semver from "semver";

const isBase64: (value: string, options: any) => boolean = require("is-base64");

const lambda = new Lambda();

const nameRegex = new RegExp("^[a-z0-9][a-z0-9_]+[a-z0-9]$");

const FORMATS = [
  {
    expression: nameRegex,
    name: "name",
  },
  {
    handler: (input: string) => {
      const [p1, p2] = input.split("/");
      if (p1 && p2) {
        return nameRegex.test(p1) && nameRegex.test(p2);
      } else {
        return nameRegex.test(p1);
      }
    },
    name: "target",
  },
  {
    handler: (input: string) => !!semver.valid(input),
    name: "semver",
  },
  {
    handler: (input: string) => isBase64(input, { allowBlank: false }),
    name: "base64",
  },
];

export const NAME_MAX_LENGTH = 100;
export const NAME_MIN_LENGTH = 4;
export const DESCRIPTION_MAX_LENGTH = 150;
export const TOKEN_MAX_LENGTH = 2000;
export const PASSWORD_MAX_LENGTH = 2000;

export abstract class AbstractServiceInvoker {
  constructor(
    protected service: string,
    protected func: string,
    protected schema: Schema,
  ) {}

  public abstract resolveFunctionName(): string;
  public abstract resolvePayload(args: object): string;

  public async execute(args) {
    this.validate(args);

    const res = await lambda
      .invoke({
        FunctionName: this.resolveFunctionName(),
        Payload: this.resolvePayload(args),
      })
      .promise();

    const funcResponse = JSON.parse(res.Payload.toString() || "{}");

    /**
     * Handle invocation bad response. * not invocation error *
     */
    if (funcResponse.errorType || funcResponse.errorMessage) {
      throw new Error(funcResponse.errorMessage);
    } else {
      return this.resolveResponse(funcResponse, args);
    }
  }

  public resolveResponse(response: any, args?: any) {
    return response;
  }

  public validate(args: object) {
    const { errors } = this.getValidator().validate(args, this.schema);

    if (errors.length) {
      // @Todo: validation error type
      throw errors[0];
    } else {
      return true;
    }
  }

  private getValidator() {
    const validator = new jsonschema.Validator();

    FORMATS.forEach(({ name, expression, handler }) => {
      validator.customFormats[name] = (input: string) => {
        if (handler) {
          return handler(input);
        } else if (expression instanceof RegExp) {
          return expression.test(input);
        }
      };
    });
    return validator;
  }
}
