
import * as child_process from 'child_process';
import Imap from './imap';
import * as mailparser from 'mailparser';
import * as Mailcomposer from 'mailcomposer';
import * as express from 'express'
import * as util from './util'
import * as randomStr from './randomString';
import * as Async from 'async'
import * as fs from 'fs'
import * as shortID from 'shortid'
import * as Compress from './compress'
import ImapV1 from './ImapV1'

let lastMessage = ''
let lastErr = ''
const echoTimeOut = 1000 * 10;

const child_processExec = ( command: string, SubEnv: any, power: string, debug: boolean, CallBack: ICallBack ) => {
    
    const comm = ' node ' + command;
    const envSt = JSON.stringify ( global.process.env );
    const SubEnvSt = JSON.stringify ( SubEnv );
    const env = JSON.parse ( '{' + envSt.substr ( 1, envSt.length - 2 ) + ',' + SubEnvSt.substr ( 1, SubEnvSt.length - 2 ) + '}' );

    
    //console.log (util.inspect ( env ))
    
    const grep = child_process.exec ( comm, { env: env });

    grep.stdout.on ( 'data', ( data: Buffer ) => {
		lastMessage = data.toString ( 'utf8' )
        if ( debug ) console.log ( 'grep.stdout == ', lastMessage );
    })
    
    grep.stderr.on ( 'data', ( data: Buffer ) => {
		lastErr = data.toString ( 'utf8' );
        if ( debug ) console.log ('grep.stderr ==> ', lastErr )
    })

    grep.on ( 'close', ( code ) => {
        if ( debug ) console.log ( 'exit with code:', code );
        CallBack ( null, code )
    })
}

const callback = () => {
    if ( /Invalid credentials \(Failure\)|Lookup failed|[Incorrect|Invalid] username or password|Authentication failed|authentication failed|LOGIN|Login|service\.mail\.qq\.com/.test ( lastMessage ))
        return ( new Error ( 'passwordErrMsg' ));
        
    if ( /getaddrinfo ENOTFOUND/.test ( lastMessage ))
        return ( new Error ( 'hostAddrErr' ))
        
    if ( /Your account is not enabled for IMAP use|LOGIN auth error/.test ( lastMessage ))
        return ( new Error ( 'imapEnable' ));
    
    if ( /Timed out while authenticating with server/.test ( lastMessage ))
        return ( new Error ( 'hostAuthTimeOut' ))
        
    if ( /Timed out while connecting to server/.test ( lastMessage ))
        return ( new Error ( 'hostPortTimeOut' ));
        
    if ( /Hostname\/IP doesn't match certificate's altnames/.test ( lastMessage ))
        return ( new Error ('hostCertError' ))
	
	if ( /ECONNREFUSED/.test ( lastMessage ))
		return ( new Error ( 'ECONNREFUSED' ))
        
    return  ( new Error ( 'UnknowErr' ))
}

export const testImapCommand = ( email, password, host: string, tls: boolean, port: number , CallBack ) => {
    const env: ImapClassEnv = {
        email: email,
        app_password: password,
        host: host,
        tls: tls,
        port: port,
        uuid: ''
    }

    child_processExec ( 'views/api/_testImapAccount', env, '0', true, ( err, data ) => {

        if ( err || data !== 0 ) {
            
           return CallBack ( callback ())
            
        }
        
        CallBack ()
    })
}

export class connectImap {
    static systemEmailAddress = 'test@vpn.email'
	private timeoutFun;
    private imap: Imap;
    private mailFolder: string;
    private emailAddress: string;
    private echoTimeOut = null;
    private dockerPublicKey = null;

    private echoBack = null; 
    public saveToVpnEmail ( DockerFolder: string, data: any, CallBack ) {

        Async.waterfall ([
            next => util.Encrypt ( JSON.stringify ( data ), this.sockSession.dockerPublicKey, this.sockSession.keyPair.privateKeyUTF8, 
                this.sockSession.keyPairPassword, next ),
            ( data, next ) => {
                const emailOption: mailcomposerOption = {
                    from: this.emailAddress,
                    to: [ connectImap.systemEmailAddress ],
                    attachments:[{
                        content: data
                    }]
                }
                const email = Mailcomposer ( emailOption );
                this.imap.save ( DockerFolder, email, next )
            }
        ], err => {
            if ( err ) {
                console.log ('saveToVpnEmail Async.waterfall have ERROR', err )
                return CallBack ( 'UnknowErr' )
            }
                
            CallBack ()
        })
    }

    processMailBody ( body: string, email: mailparser.ParsedMail ) {
        try {
            const datas = JSON.parse ( body );
            const vpnServerConnectData: ISentMessage  = datas;

            //      echo come
            if ( vpnServerConnectData.echo ) {
                clearTimeout ( this.echoTimeOut )
                console.log ('echo return')
                if (this.echoBack && typeof this.echoBack === 'function' )
                    this.echoBack ()
                this.echoBack = null;
                this.echoTimeOut = null;
                return;
            }
            const command : IClientEmailCommand = datas;
            if ( command.command && command.command.length ) {
                
                if ( /^activePassword$/.test ( command.command ))
                    this.sockSession.vpnServerConnectData.active = true;
                if ( /^echo$/.test ( command.command )) {

                }
                if ( /^disconnect$/.test ( command.command )) {
                    this.sockSession.vpnEmailServerKeepConnected = false;
                }


            }
            //          have docker public key
            if ( vpnServerConnectData.haveDockerPublicKey && email.attachments[1].content && email.attachments[1].content.length ) {
                const key = email.attachments[1].content.toString('utf8')
                if ( /^-----BEGIN PGP PUBLIC KEY BLOCK-----\n/.test( key )) {
                    this.dockerPublicKey = key;
                    this.sockSession.dockerPublicKey = key;
                }
                this.sockSession.vpnServerConnectData = datas;
            }
            return datas
            
        } catch ( ex ) {
            console.log ( 'Server data format ERROR', body )
        }
    }

	constructor ( private sockSession: ISockSession, CallBack: ( data ) => void )  {
		
		//this.timeoutFun = setTimeout( ioEvent, 1000 * 120, Io);
		const Config: IinputData = sockSession.imapArray;
        const key = sockSession.keyPair
        this.mailFolder = Config.uuid;
        this.emailAddress = Config.account;
        
		this.imap = new Imap ( Config.imapUserName, Config.imapUserPassword, Config.imapServer, parseInt ( Config.imapPortNumber ), Config.imapSsl,
			true, Config.uuid, true, true, false, false )
	    
		this.imap._events.on ( 'email', ( email: mailparser.ParsedMail ) => {
            
			console.log ( 'listening folder have data from vpn.email server!' )
            sockSession.imapConnected = true;

			if ( /test@vpn.email/i.test ( email.from [0].address )) {

                if ( email.attachments && email.attachments.length ) {

                    const text = email.attachments[0].content.toString ( 'utf8' )

                    if ( /^-----BEGIN PGP MESSAGE-----/.test ( text )) {
                        
                        util.Decryption ( text, sockSession.keyPairPassword, key.privateKeyUTF8, sockSession.dockerPublicKey || sockSession.vpnEmailPublicKey, ( err, data ) => {

                            if ( err )
                                return console.log ( 'Decryption mail err', text )
                            const datas = this.processMailBody ( data.data, email )
                            CallBack ( data.data )
                        })
                        return
                    }
                }
                
			}
            console.log ( 'unknow data format!', email.subject )
		})
        
        this.imap._events.on ( 'imapReady', () => {
            console.log ( 'IMAP connected' )
            sockSession.imapConnected = true;
        })

	}
    public destroy () {
        this.imap.distroy ();
    }
    
    public echoRespon ( CallBack: ICallBack ) {
        if ( ! this.sockSession.vpnServerConnectData || ! this.sockSession.vpnServerConnectData.serverMailFolder )
            return ( new Error ( '' ))
        this.echoBack = CallBack
        const emailOption: mailcomposerOption = {
            subject: 'echo',
            from: this.emailAddress,
            to: [ connectImap.systemEmailAddress ]
        }
        const email = Mailcomposer ( emailOption );
        console.log ( 'save echo to server folder:', this.sockSession.vpnServerConnectData.serverMailFolder )
        this.imap.save ( this.sockSession.vpnServerConnectData.serverMailFolder, email, () => {
            console.log ( 'sent echo success!' )
        })
        this.echoTimeOut = setTimeout(() => {
            CallBack ( new Error ( 'no' ))
            this.echoBack = null;
        }, echoTimeOut )
    }
}
interface connectOption {
    email: string;
    password: string;
    host: string;
    tls: boolean;
    port: number;
}

const _ImapPairTest = ( user: IinputData, CallBack ) => {
    const listeningFolder = shortID.generate()
    const uuid = shortID.generate()
    const pass = shortID.generate()
    const testText = new Buffer (1200).toString('base64')
    let startTime: Date = null
    let timeOut = null
    let callBackData = 0
    let imapErr = null

    const content = Compress.packetBuffer ( 0, 0, uuid, Compress.encrypt ( testText, pass ))
    const newMail = ( mail: Buffer ) => {

        const endTime = new Date ()

        try {
            const pp = Compress.openPacket ( mail )
            if ( pp.uuid != uuid )
                return
            
            Compress.decrypt ( pp.buffer, pass, ( err, data ) => {

                imap.destroyImap ()
                clearTimeout ( timeOut )
                if ( err )
                    return imapErr = err
                return callBackData = new Date().getTime() - startTime.getTime ()
            })
            
        } catch ( ex ) {
            console.log ('ImapPairTest new email got ex', ex )
        }
        
    }

    const imap = new ImapV1 ( user, listeningFolder, false, true, newMail, ( err, connectDelay ) => {
        if ( err )
            return CallBack ( err )
        CallBack( imapErr, callBackData )

    })

    imap.save ( content, listeningFolder,  err => {
        if ( err ) {
            console.log ('save got err!', err )
            imap.destroyImap ()
            return imapErr = err
        }
        startTime = new Date ()

        timeOut = setTimeout (() => {
            console.log ('timeout fire, destroy imap')
            imap.destroyImap ()
            return imapErr = new Error ('timeout')
        }, 15000 )
    })

}

const _doImapPairTest = ( user: IinputData, maxConnect: number, CallBack ) => {
    
    const loop = []
    let errCount = 0
    console.log ( 'start test imap account:', user.imapUserName )
    for ( let i = 0; i < maxConnect; i ++ ) {
        loop.push ( next => _ImapPairTest ( user, next ))
    }

    Async.parallel ( loop, ( err, time: number[] ) => {
        console.log ('_doImapPairTest callack!', err, time)
        if ( err )
            return CallBack ( err )
        if ( time.every ( n => n === -1 ))
            return CallBack ( new Error ('data error'))
        let u = 0
        time.map ( n => u += n )
        return CallBack ( null, u /maxConnect )
    })
}

export const doImapPairTest = ( user: IinputData, connectNumber: number, loopCount: number, CallBack ) => {
    const loop = []
    for ( let i = 0; i < loopCount; i ++ ) {
        loop.push ( next => _doImapPairTest ( user, connectNumber, next ))
    }
    Async.series ( loop, ( err, n: number[] ) => {
        console.log ('doImapPairTest callback', err, n)
        if ( err )
            return CallBack ( err )
        let u = 0
        n.map ( n => u += n )
        return CallBack ( null, u /loopCount )
    })
}

