// DSL SKIP
import { CallContextEvent, CCE, dedent, getRuntime, PostfixReturn, dslIfaceGuard } from "ts-dsl";
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from "axios";
import Express from 'express';
import { Request, Response } from 'express';
import { Server } from "http";
import { promise, _, TaggedTemplateSelfChain } from 'ts-basis';
import { HttpError, HttpFinalizerExpress } from "./http.model";

// ====================================================
//  Facade
// ====================================================

type ExpressServer = Express.Express & { server?: Server, start?: () => ExpressServer, stop?: () => ExpressServer };

class httpModel {

  static get<T = any, D = any>(config: AxiosRequestConfig<D>): PostfixReturn<AxiosResponse<T>>;
  static get<T = any, D = any>(url: string, config?: AxiosRequestConfig<D>): PostfixReturn<AxiosResponse<T>>;
  static get<T = any, D = any>(strArr: TemplateStringsArray, ...args: any[]): TaggedTemplateSelfChain<PostfixReturn<AxiosResponse<T>>>;
  static get(...args) { dslIfaceGuard('lugger:http:get', __filename); return null; }

  static post<T = any, D = any>(config: AxiosRequestConfig<D>): PostfixReturn<AxiosResponse<T>>;
  static post<T = any, D = any>(url: string, config?: AxiosRequestConfig<D>): PostfixReturn<AxiosResponse<T>>;
  static post(...args) { dslIfaceGuard('lugger:http:post', __filename); return null; }

  static put<T = any, D = any>(config: AxiosRequestConfig<D>): PostfixReturn<AxiosResponse<T>>;
  static put<T = any, D = any>(url: string, config?: AxiosRequestConfig<D>): PostfixReturn<AxiosResponse<T>>;
  static put(...args) { dslIfaceGuard('lugger:http:put', __filename); return null; }

  static delete<T = any, D = any>(config: AxiosRequestConfig<D>): PostfixReturn<AxiosResponse<T>>;
  static delete<T = any, D = any>(url: string, config?: AxiosRequestConfig<D>): PostfixReturn<AxiosResponse<T>>;
  static delete(...args) { dslIfaceGuard('lugger:http:delete', __filename); return null; }

  static options<T = any, D = any>(config: AxiosRequestConfig<D>): PostfixReturn<AxiosResponse<T>>;
  static options<T = any, D = any>(url: string, config?: AxiosRequestConfig<D>): PostfixReturn<AxiosResponse<T>>;
  static options(...args) { dslIfaceGuard('lugger:http:options', __filename); return null; }

  static getContent<T = any, D = any>(url: string, config?: AxiosRequestConfig<D>): PostfixReturn<AxiosResponse<T>>;
  static getContent<T = any, D = any>(strArr: TemplateStringsArray, ...args: any[]): TaggedTemplateSelfChain<PostfixReturn<AxiosResponse<T>>>;
  static getContent(...args) { dslIfaceGuard('lugger:http:getContent', __filename); return null; }

  static server<T = any, D = any>(port: number, type?: 'express'): PostfixReturn<ExpressServer>;
  static server(...args) { dslIfaceGuard('lugger:http:server', __filename); return null; }

  static finalizer = class {
    static express = HttpFinalizerExpress;
  };
}

export const http = httpModel as (typeof httpModel & (<T = any, D = any>(url: string, config?: AxiosRequestConfig<any>) => PostfixReturn<AxiosResponse<T>>));


// ====================================================
//  Implemenation
// ====================================================

function httpModel__(cce: CallContextEvent, ...args) {
  const { url, config } = getHttpRequestArgs(args);
  return getRuntime().scopedExec(
    cce, 'lugger:http:http', { data: { url, config } },
    async (resolve, reject, scopeContext) => {
      try { return resolve(await axios.get(url, config)); } catch (e) { reject(convertAxiosError(e)); }
    });
}
(httpModel__ as any).get = (cce: CallContextEvent, ...args) => {
  const { url, config } = getHttpRequestArgs(args);
  return getRuntime().scopedExec(
    cce, 'lugger:http:http.get', { data: { url, config } },
    async (resolve, reject, scopeContext) => {
      try { resolve(await axios.get(url, config)); } catch (e) { reject(convertAxiosError(e)); }
    });
}
(httpModel__ as any).head = (cce: CallContextEvent, ...args) => {
  const { url, config } = getHttpRequestArgs(args);
  return getRuntime().scopedExec(
    cce, 'lugger:http:http.head', { data: { url, config } },
    async (resolve, reject, scopeContext) => {
      try { resolve(await axios.head(url, config)); } catch (e) { reject(convertAxiosError(e)); }
    });
}
(httpModel__ as any).post = (cce: CallContextEvent, ...args) => {
  const { url, config } = getHttpRequestArgs(args);
  return getRuntime().scopedExec(
    cce, 'lugger:http:http.post', { data: { url, config } },
    async (resolve, reject, scopeContext) => {
      try { resolve(await axios.post(url, config.data, config)); } catch (e) { reject(convertAxiosError(e)); }
    });
}
(httpModel__ as any).put = (cce: CallContextEvent, ...args) => {
  const { url, config } = getHttpRequestArgs(args);
  return getRuntime().scopedExec(
    cce, 'lugger:http:http.put', { data: { url, config } },
    async (resolve, reject, scopeContext) => {
      try { resolve(await axios.put(url, config.data, config)); } catch (e) { reject(convertAxiosError(e)); }
    });
}
(httpModel__ as any).patch = (cce: CallContextEvent, ...args) => {
  const { url, config } = getHttpRequestArgs(args);
  return getRuntime().scopedExec(
    cce, 'lugger:http:http.patch', { data: { url, config } },
    async (resolve, reject, scopeContext) => {
      try { resolve(await axios.patch(url, config.data, config)); } catch (e) { reject(convertAxiosError(e)); }
    });
}
(httpModel__ as any).delete = (cce: CallContextEvent, ...args) => {
  const { url, config } = getHttpRequestArgs(args);
  return getRuntime().scopedExec(
    cce, 'lugger:http:http.delete', { data: { url, config } },
    async (resolve, reject, scopeContext) => {
      try { resolve(await axios.delete(url, config)); } catch (e) { reject(convertAxiosError(e)); }
    });
}
(httpModel__ as any).finalizer = {
  express: (cce: CallContextEvent, ...args: any[]) => {
    return HttpFinalizerExpress(args[0], args[1]);
  }
};
(httpModel__ as any).server = (cce: CallContextEvent, ...args) => {
  const port = parseInt(args[0], 10);
  let type: 'express' = args[1]; if (!type) { type = 'express'; }
  return getRuntime().scopedExec(
    cce, 'lugger:http:http.server', { data: { port } },
    async (resolve, reject, scopeContext) => {
      try {
        if (type === 'express') {
          const app = Express();
          (app as any).start = () => {
            return promise(resolve2 => {
              (app as any).server = app.listen(port, () => {
                resolve2(app);
              });
            });
          };
          (app as any).stop = () => {
            return promise(async (resolve2, reject2) => {
              if (!(app as any).server) {
                return reject2(new Error(`cannot stop a server that has never started.`));
              }
              ((app as any).server as Server).close(e => {
                if (e) { return reject2(e); }
                return resolve(app);
              });
              return resolve2();
            });
          };
          return resolve(app);
        }
      } catch (e) { reject(e); }
    });
}

export const http__ = httpModel__ as any as (typeof http);

function getHttpRequestArgs(args: any[]) {
  const arg0 = args[0];
  if (typeof arg0 === 'string') {
    return {
      url: arg0 as string,
      config: (args[1] ? args[1] : {}) as AxiosRequestConfig,
    }
  } else {
    return {
      url: arg0.url as string,
      config: arg0 as AxiosRequestConfig,
    }
  }
}

function convertAxiosError(axiosE: AxiosError): HttpError {
  const e = new Error(`[${axiosE.code}] ${axiosE.message}`) as HttpError;
  e.codeName = axiosE.code;
  e.code = axiosE.response?.status;
  e.getResponse = () => axiosE.response;
  e.getOriginalError = () => axiosE;
  return e;
}

function couldBeJSON(data: string) {
  data = data.trim();
  return (
    (data.startsWith('{') && data.startsWith('}')) ||
    (data.startsWith('[') && data.startsWith(']'))
  );
}
