// Type definitions for node-base-module
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped

import * as nockTypes from "nock";
import * as mochaTypes from "mocha";
import * as expressTypes from "express";
import * as redisTypes from "ioredis";
import * as mailerTypes from "nodemailer";
import * as CassandraTypes from "cassandra-driver";
import * as Elasticsearch from 'elasticsearch';
import * as Prom from "prom-client";
import * as MysqlTypes from "mysql";

import RedisMock from "./mocks/redisMock";
import ElasticMock from "./mocks/elasticMock";
import CassandraMock from "./mocks/cassandraMock";
import Settings from "./settings";
import StaticMethod from "./statics";

interface ExtendedIORedis extends redisTypes.Redis {
    del(...keys: string[]): any;
    del(key: string, callback: (err: Error, res: string) => void): any;

    hdel(key: string, ...fields: string[]): any;
    hdel(key: string, field: string, callback: (err: Error, res: string) => void): any;

    spop_keyset?(free_set:string, used_key:string, timestamp:any):any;
}

interface ExtendedCassandraClient extends CassandraTypes.Client {
    controlConnection: any;
    profileManager: any;
}


interface RedisOptions{
    isCluster?: boolean;
    sentinels?: Array<{ host: string; port: number; }>;
    sentinel_cluster_name?: string;
    password?: string;
    keys?: { [key: string] : string; };
    statusCheckInterval?: number;
    port?: number;
    host?: string;
    family?: number;
    autoConnect?: boolean;
}

interface CassandraOptions{
    hosts?: Array<string>;
    port?: number;
    keyspace?: string;
    user?: string;
    password?: string;
    timeout?: number;
    autoConnect?: boolean;
}

interface ElasticOptions {
    host?: any;
    log?: any;
    statusCheckInterval?: number;
    autoConnect?: boolean;
}

interface MysqlOptions {
    multipleStatements?: boolean;
    host?: string;
    port?: number;
    database?: string;
    user?: string;
    password?: string;
    statusCheckInterval?: number;
    autoConnect?: boolean;
}

interface ConsulOptions {
    name?: string;
    host?: string;
    port?: number;
    secure?: boolean;
}

interface MailerOptions {
    mailTransportConfig?: any;
    mailConfig?: mailerTypes.SendMailOptions;
    statusCheckInterval?: number;
}

interface CustomCassandraOptions {
    hosts?: Array<string>;
    port?: number;
    keyspace?: string;
    user?: string;
    password?: string;
    timeout?: number;
}

interface LoggerOptions {
    logLevel?: number;
    debugLogging?: boolean;
    logFolder?: string;
    rotatingSize?: string;
    errorFile?: string;
    outputFile?: string;
}

interface StreamOptions {
    interval?: string;
    maxFiles?: number;
    compress?: boolean | "gzip";
    path?: string;
    logFile?: string;
}

interface SwaggerOptions {
    swaggerDefinition?: {
        info?: {
            title?: string;
            version?: string;
            description?: string;
        },
        basePath?: string;
    },
    apis?: Array<string>;
    allowedDomains?: Array<string>;
    allowHeaders?: string;
    allowMethods?: string;
    isActive?: boolean;
}

interface CrashReporterOptions {
    exitOnCrash?: boolean;
    maxCrashFile?: number;
    mailEnabled?: boolean;
    hiddenAttributes?: Array<string>;
    outDir?: string;
    mailConfig?: {
        subject?: string;
        from?: string;
        to?: string;
    } 
}

interface MetricsOptions {
    name?: string;
}

interface RouteMetricsOptions {
    buckets: [number],
    includeMethod?: boolean;
    includePath?: boolean;
    autoregister?: boolean, 
    timeout: number
}

interface MorganOptions {
    isActive?: boolean;
    useStdout?: boolean;
    logStream?: StreamOptions;
    format?: string;
}

interface ZipkinOptions { 
    isActive?: boolean,
    serveradress?: string,
    remoteServiceName?: string,
    localServicename?: string
}

interface ModuleOptions {
    id?: string; //for unitesting
    swagger?: SwaggerOptions;
    routeMetrics?: RouteMetricsOptions;
    morgan?: MorganOptions;
    zipkin?: ZipkinOptions;
    crashReporter?: CrashReporterOptions;
    mailer?: MailerOptions;
    logger?: LoggerOptions;
    redis?: RedisOptions;
    cassandra?: CassandraOptions;
    elastic?: ElasticOptions;
    mysql?: MysqlOptions;
    metrics?: MetricsOptions;
    consul?: ConsulOptions;    
}

interface CustomModuleOptionsStrict extends ModuleOptionsStrict {
    custom: any;
}

interface CustomModuleOptions extends ModuleOptions {
    custom: any;
}

interface ModuleOptionsStrict {
    swagger: SwaggerOptions;
    morgan: {
        isActive: boolean;
        useStdout: boolean;
        logStream: StreamOptions ;
    };
    routeMetrics: RouteMetricsOptions;
    zipkin: ZipkinOptions;
    crashReporter: CrashReporterOptions,
    mailer: MailerOptions;
    logger: LoggerOptions;
    redis: RedisOptions;
    cassandra: CassandraOptions;
    elastic: ElasticOptions;
    mysql: MysqlOptions;
    metrics: MetricsOptions;
    consul: ConsulOptions;
}
interface EnvMap {
    [key: string] : {
        route: string[];
        type: "array" | "boolean" | "string" | "number";
    }
}

interface OnError {
    (error: any): void;
}
interface OnSuccess {
    (): void;
}

interface ExternStatus {
    (): Object;
}

interface ExternFunctionality {
    (): boolean;
}

interface FileGenerator {
    (time: Date, index: number): string
}

interface StreamWrite {
    (chunk: string, encoding: string, cb: OnError): void;
}

interface WebServer {
    initialize(): expressTypes.Express;
    getExpress(): any;
    setLogStream(logStream: any): void;
    setLogFileGenerator(logFileGenerator: FileGenerator): void;
    activateMorgan(active: boolean): void;
}

interface MockWebServer extends WebServer {
    handleWriteStream(write: StreamWrite): void;
    mockCustomGenerator(time: Date, index: number): string;
}

interface Logger{
    setLogLevel(level: number): void;
    log(str: string, level: number): void;
}

interface RedisCallback {
    (isSucces: boolean, result: any): void
}

interface Callback {
    (err: any, result: any): void
}

interface Redis {
    destroy(): void;
    set(key: string, value: string, callback?: RedisCallback): void;
    set_with_ttl(key: string, value: any, ttl?:number, callback?:RedisCallback):void;
    hset(key: string, field: string, value: any, callback: RedisCallback): void;
    keys(key: string, callback: RedisCallback): void;
    get(key: string, callback: RedisCallback): void;
    hget(key: string, field: string, callback: RedisCallback): void;
    hgetall(key: string, callback: RedisCallback): void;
    delete(key: string, callback?: RedisCallback): void;
    hdel(key: string, field: string, callback: RedisCallback): void;
    begin_transaction(): redisTypes.Pipeline;
    exec_transaction(redis_transactions: redisTypes.Pipeline, callback: RedisCallback): void;
    countValues(key: string, callback: RedisCallback): void;
    count_used_sessions(used_sessions_key: string, callback: RedisCallback): void;
    add_to_set(setName: string, values: string, callback: RedisCallback): void;
    add_session(sessions: string, free_sessions_set_key: string, callback: RedisCallback): void;
    remove_from_set(setName: string, member: string, callback: RedisCallback): void;
    pop_from_set(setName: string, callback: RedisCallback): void;
    spop(setName: string, count:number, callback: RedisCallback): void;
    smembers(key: string, callback: RedisCallback): void;
    setCount(key: string, callback: RedisCallback): void;
    add_to_list(listName: string, values: string, callback: RedisCallback): void;
    remove_from_list(listName: string, callback: RedisCallback): void;
    move(sourceList: string, destinationList: string, callback: RedisCallback): void;
    countList(listName: string, callback: RedisCallback): void;
    rPoplpush(sourceList: string, destinationList: string, callback: RedisCallback): void;  
    lock_session(free_sessions_set_key: string, used_sessions_key: string, lock_duration:number, callback: RedisCallback): void;
    lock_session_lua(free_sessions_set_key: string, used_sessions_key: string, lock_duration:number, callback: RedisCallback): void;
    lock_session_with_watch(free_sessions_set_key: string, used_sessions_key: string, lock_duration:number, callback: RedisCallback): void;
    unlock_session(session: string, isErr: boolean, free_sessions_set_key: string, used_sessions_key: string, error_sessions_set_key: string, callback: RedisCallback): void;
    rpoplpush_with_counter(destination_list: string, value: string, counter: number, callback: RedisCallback): void;
    getStatus(): string;
    close(): void;
    isFunctional(): boolean;  
    promisefy(method:any, ...args: any[]): any;
}

interface Cassandra {
    destroy(): void;
    connect(onSuccess: OnSuccess | null, onError: OnError | null): void;
    close(): void;
    execute(query: string, params: Object, options: Object, callback?: (err: Error, result: CassandraTypes.types.ResultSet | null) => void): void;
    getStatus(): string;
    getControlConnection(): any | null;
    getCurrentKeyspace(): string | undefined;
}

interface Mailer {
    sendMail(mailOptions: Object, callback: Callback): void;
    destroy(): void;
    getStatus(): string;
    isFunctional(): boolean;
}

interface Registy {
    counter(name: string): Prom.Counter | null
    newCounter(name: string, help: string, labels: string[]): Prom.Counter
    gauge(name: string): Prom.Gauge | null
    newGauge(name: string, help: string, labels: string[]): Prom.Gauge
    histogram(name: string): Prom.Histogram | null
    newHistogram(name: string, help: string, labels?: string[], buckets?: number[]): Prom.Histogram
    destroy(): void;
    getStatus(): string;
    isFunctional(): boolean;
}

interface Observer {
    getServices(): any;
    getError(clean?: boolean): Error;
    destroy(): void;
}

interface Consul {
    newObserver(name: string, error?: (error: Error) => void): Observer;
    deleteObserver(name: string): void;
    observer(name: string): Observer | null;

    destroy(): void;
    getStatus(): string;
    isFunctional(): boolean;
}

interface ErrorResponse {
    (error: Object, response: Object): void;
}

interface Elastic {
    destroy(): void;
    connect(): void;
    close(): void;
    getStatus(): string;
    index(object: Object, response: ErrorResponse): void;
    delete(object: Object, response: ErrorResponse): void;
    get(object: Object, response: ErrorResponse): void;
    search(object: Object, response: ErrorResponse): void;
    isFunctional(): boolean;
}

interface Mysql {
    destroy(): void;
    connect(): void;
    close(): void;
    getStatus(): string;
    query: MysqlTypes.QueryFunction;
    isFunctional(): boolean;
}

interface utils {
    deepExtend(target: Object, ...args: Object[]): Object;
    pad(num: number): string;
    getIp(req: expressTypes.Request): string | undefined;
}

interface PromiseOption {
    reject?: (reason?: any) => void;
    resolve?: (value?: any) => void;

    bind?: any;
    args: any[];
    controller?: any;
}

interface statics {
    sendResponse(res:Express.Response, status_code: number, message:any, data:any):void;
    promisefy(method:any, options:PromiseOption): Promise<any>;
}

interface Crashreporter {
}



export interface ExpressRequest extends expressTypes.Request {
    parsed_body: any;
}

export interface ExpressResponse extends expressTypes.Response {
    body: any;

    /**
    * @deprecated use status() to set status or setHeader() to set a header
    */
    writeHead(status: number): this;

   /**
    * @deprecated use status() to set status or setHeader() to set a header
    */
    writeHead(status: number, hea?: any): this;

   /**
    * @deprecated use send() instead; send() also set "content-lenght"
    */
    end(cb?: Function): void;
    /**
    * @deprecated use send() instead; send() also set "content-lenght"
    */
    end(chunk: any, cb?: Function): void;
    /**
    * @deprecated use send() instead; send() also set "content-lenght"
    */
    end(chunk: any, encoding?: string, cb?: Function): void;

}

export declare function setDefaultFileOptions(filename: string): boolean;
export declare function setDefaultOptions(options: Object): void;

export declare function setEnvMap(envMap: Object): void;
export declare function setEnvMapFile(filename: string): void;

/**
* @deprecated use reset() to reset all settings
*/
export declare function resetDefaultOptions(options: Object): void;
export declare function setProjectStatus(callback: ExternStatus): void;
export declare function setProjectFunctionality(isFunctional: ExternFunctionality): void;
export declare function isFunctional(): boolean;
export declare function setDefaultOptions(options: Object): void;
export declare function express(options?: Object): WebServer;
export declare function crashreporter(token?: string): Crashreporter;
export declare function logger(token?: string): Logger;
export declare function redis(token?: string): Redis;
export declare function cassandra(token?: string): Cassandra;
export declare function elastic(token?: string): Elastic;
export declare function mysql(token?: string): Mysql;
export declare function mailer(token?: string): Mailer;
export declare function metrics(token?: string): Registy;
export declare function consul(token?: string): Consul;
export declare function getSettings(): Settings;
export declare function getCustomSettings(token?: string): any;
export declare function setCustomSettings(settings: any, token?: string): void;
/**
 * Use carefully this will reset all your settings
 * Useful when you wan to start from fresh settings for exemple in testing
 */
export declare function resetSettings(): void;


export namespace express {
    function mock(options?: Object): MockWebServer;
}

export namespace crashreporter {
    function configurate(options: Object): void;
    function newInstance(options: Object, token: string): Crashreporter;
    function deleteInstance(token?: string): void;
    function reset(): void;    
}

export namespace logger {
    function configurate(options: Object): void;
    function newInstance(options: Object, token: string): Logger;
    function deleteInstance(token?: string): void;
    function reset(): void;    
}

export namespace redis {
    function configurate(options: Object): void;
    function newInstance(options: Object, token: string): Redis;
    function deleteInstance(token?: string): void;
    function mock(options?: Object, customLib?: Object): Redis;
    function reset(): void;    
}

export namespace cassandra {
    function configurate(options: Object): void;
    function newInstance(options: Object, token: string): Cassandra;
    function deleteInstance(token?: string): void;
    function mock(options?: Object, customLib?: Object): Cassandra; 
    function reset(): void;    
}

export namespace elastic {
    function configurate(options: Object): void;
    function newInstance(options: Object, token: string): Elastic;
    function deleteInstance(token?: string): void;
    function mock(options?: Object, customLib?: Object): Elastic; 
    function reset(): void;    
}

export namespace mysql {
    function configurate(options: Object): void;
    function newInstance(options: Object, token: string): Mysql;
    function deleteInstance(token?: string): void;
    function reset(): void;    
}

export namespace mailer {
    function configurate(options: Object): void;
    function newInstance(options: Object, token: string): Mailer;
    function deleteInstance(token?: string): void;
    function reset(): void;    
}

export namespace metrics {
    function configurate(options: Object): void;
    function newInstance(options: Object, token: string): Registy;
    function deleteInstance(token?: string): void;
    function reset(): void;
}

export namespace consul {
    function configurate(options: Object): void;
    function newInstance(options: Object, token: string): Consul;
    function deleteInstance(token?: string): void;
    function reset(): void;
}

export namespace mocks {
    var redis: new () => RedisMock;
    var elastic: new (params: Elasticsearch.ConfigOptions) => ElasticMock;
    var cassandra: new (options: CassandraOptions, errorCallback: (err: any) => void, successCallback: () => void, callbackStatus: (err: any) => void) => CassandraMock;
}

export var Static: statics;

export declare function nock(): nockTypes.Scope;
export declare function mocha(): mochaTypes;

export declare enum LOG_LEVEL {
    INFO,
    WARNING,
    ERROR,
    DEBUG,
    DEBUG_VERBOSE,
    QUERIES
  }

export var Utils: utils;