import * as consts from './types/constants';
import * as modules from './modules';
import {RpcClient} from './nets/rpc-client';
import {EventListener} from './nets/event-listener';
import {AxiosRequestConfig} from 'axios';
import * as types from './types';
import {SdkError} from './errors';
import * as AES from 'crypto-js/aes';
import * as ENC from 'crypto-js/enc-utf8';
export {KeyDAO, CacheKeyDAO} from './keydao';
import {KeyDAO} from './keydao';
/** MCPS Client */
export class Client {
    /** MCPS Client Config */
    config: DefaultClientConfig;

    /** Axios client for tendermint rpc requests */
    rpcClient: RpcClient;

    /** WebSocket event listener */
    eventListener: EventListener;

    /** Auth module */
    auth: modules.Auth;

    /** Bank module */
    bank: modules.Bank;

    /** Key management module */
    keys: modules.Keys;

    /** Protobuf module */
    protobuf: modules.Protobuf;

    /** Tx module */
    tx: modules.Tx;

    /** Oracle module */
    oracle: modules.Oracle;

    /** Random module */
    random: modules.Random;

    /** Utils module */
    utils: modules.Utils;

    /** Tendermint module */
    tendermint: modules.Tendermint;

    /** Contract module */
    contract: modules.Contract;


    /** MCPS SDK Constructor */
    constructor(config: DefaultClientConfig) {
        this.config = config;
        if (!this.config.rpcConfig) this.config.rpcConfig = {};

        this.config.bech32Prefix = {
            AccAddr: 'cosmos',
            AccPub: 'cosmospub',
            ValAddr: 'cosmosvaloper',
            ValPub: 'cosmosvaloperpub',
            ConsAddr: 'cosmosvalcons',
            ConsPub: 'cosmosvalconspub',
        };

        // Support ibc-alpha
        // {
        //   AccAddr: 'cosmos',
        //   AccPub: 'cosmospub',
        //   ValAddr: 'cosmosvaloper',
        //   ValPub: 'cosmosvaloperpub',
        //   ConsAddr: 'cosmosvalcons',
        //   ConsPub: 'cosmosvalconspub',
        // };
        this.config.rpcConfig.baseURL = this.config.node;
        this.rpcClient = new RpcClient(this.config.rpcConfig);
        this.eventListener = new EventListener(this);

        // Modules
        this.utils = new modules.Utils(this);
        this.bank = new modules.Bank(this);
        this.keys = new modules.Keys(this);
        this.tx = new modules.Tx(this);
        this.protobuf = new modules.Protobuf(this);
        this.oracle = new modules.Oracle(this);
        this.random = new modules.Random(this);
        this.auth = new modules.Auth(this);
        this.tendermint = new modules.Tendermint(this);
        this.contract = new modules.Contract(this);

        // Set default encrypt/decrypt methods
        if (!this.config.keyDAO.encrypt || !this.config.keyDAO.decrypt) {
            const defaultKeyDAO = new DefaultKeyDAOImpl();
            this.config.keyDAO.encrypt = defaultKeyDAO.encrypt;
            this.config.keyDAO.decrypt = defaultKeyDAO.decrypt;
        }
    }

    /**
     * Set Key DAO Implemention
     *
     * @param keyDAO Key DAO Implemention
     * @returns The SDK itself
     */
    withKeyDAO(keyDAO: KeyDAO) {
        // Set default encrypt/decrypt methods
        if (!keyDAO.encrypt || !keyDAO.decrypt) {
            const defaultKeyDAO = new DefaultKeyDAOImpl();
            keyDAO.encrypt = defaultKeyDAO.encrypt;
            keyDAO.decrypt = defaultKeyDAO.decrypt;
        }
        this.config.keyDAO = keyDAO;
        return this;
    }

    /**
     * Set MCPS network type
     *
     * @param network MCPS network type, mainnet / testnet
     * @returns The SDK itself
     */
    withNetwork(network: consts.Network) {
        this.config.network = network;
        return this;
    }

    /**
     * Set MCPS chain-id
     *
     * @param chainId MCPS chain-id
     * @returns The SDK itself
     */
    withChainId(chainId: string) {
        this.config.chainId = chainId;
        return this;
    }

    /**
     * Set default gas limit
     *
     * @param gas Default gas limit
     * @returns The SDK itself
     */
    withGas(gas: string) {
        this.config.gas = gas;
        return this;
    }

    /**
     * Set default fees
     *
     * @param fee Default fee amount
     * @returns The SDK itself
     */
    withFee(fee: types.Coin) {
        this.config.fee = fee;
        return this;
    }

    /**
     * Set Axios config for tendermint rpc requests, refer to: https://github.com/axios/axios#request-config.
     *
     * Note the `baseURL` is set by `SdkConfig.node` and cannot be overwritten by this config
     *
     * @param rpcConfig Axios config for tendermint rpc requests
     * @returns The SDK itself
     */
    withRpcConfig(rpcConfig: AxiosRequestConfig) {
        rpcConfig.baseURL = this.config.node;
        this.config.rpcConfig = rpcConfig;
        this.rpcClient = new RpcClient(this.config.rpcConfig);
        return this;
    }
}

/** MCPS SDK Config */
export interface ClientConfig {
    /** MCPS node rpc address */
    node: string;

    /** MCPS network type, mainnet / testnet */
    network?: consts.Network;

    /** MCPS chain-id */
    chainId?: string;

    /** Default gas limit */
    gas?: string;

    /** Default fee amount */
    fee?: types.Coin;

    /** Key DAO Implemention */
    keyDAO?: KeyDAO;

    /** Bech32 prefix of the network, will be overwritten by network type */
    bech32Prefix?: Bech32Prefix;

    /** Axios request config for tendermint rpc requests */
    rpcConfig?: AxiosRequestConfig;
}

/** Default MCPS Client Config */
export class DefaultClientConfig implements ClientConfig {
    node: string;
    network: consts.Network;
    chainId: string;
    gas: string;
    fee: types.Coin;
    keyDAO: KeyDAO;
    bech32Prefix: Bech32Prefix;
    rpcConfig: AxiosRequestConfig;

    constructor() {
        this.node = '';
        this.network = types.Network.Mainnet;
        this.chainId = 'mcps';
        this.gas = '100000';
        this.fee = {amount: '0.6', denom: 'stake'};
        this.keyDAO = new DefaultKeyDAOImpl();
        this.bech32Prefix = {} as Bech32Prefix;
        this.rpcConfig = {timeout: 2000};
    }
}

/**
 * Bech32 Prefix
 */
export interface Bech32Prefix {
    AccAddr: string;
    AccPub: string;
    ValAddr: string;
    ValPub: string;
    ConsAddr: string;
    ConsPub: string;
}

export class DefaultKeyDAOImpl implements KeyDAO {
    write(name: string, key: types.Key): void {
        throw new SdkError(
            'Method not implemented. Please implement KeyDAO first.'
        );
    }

    read(name: string): types.Key {
        throw new SdkError(
            'Method not implemented. Please implement KeyDAO first.'
        );
    }

    delete(name: string): void {
        throw new SdkError(
            'Method not implemented. Please implement KeyDAO first.'
        );
    }

    encrypt(privKey: string, password: string): string {
        const encrypted = AES.encrypt(privKey, password).toString();
        if (!encrypted) {
            throw new SdkError('Private key encrypt failed');
        }
        return encrypted;
    }

    decrypt(encrptedPrivKey: string, password: string): string {
        const decrypted = AES.decrypt(encrptedPrivKey, password).toString(ENC);
        if (!decrypted) {
            throw new SdkError('Wrong password');
        }
        return decrypted;
    }
}
