import { Duplex } from 'stream';

import { Client } from 'ssh2';

import { beginEventEmitter } from '@f2/event';

import { SshShell, SshShellOptions } from './ssh-shell';


export interface ClientOptions {
    /**
     * @default 22
     */
    port?: number;
    
    user?: string;
    password?: string;
}

export interface SshClient {

    on(event: 'error', cb: (err: Error) => void): this;
    on(event: 'disconnect', cb: () => void): this;

    emit(event: 'error', err: Error): boolean;
    emit(event: 'disconnect'): boolean;
}

export class SshClient {

    private _client?: Client;
    private _closed = true;

    constructor(private host: string, private options: ClientOptions = {}) {

        beginEventEmitter(this);
    }

    public connect(): Promise<void> {

        if(!this._closed) {
            throw new Error('Already opened');
        }
        const client = new Client();

        return new Promise<void>((resolve, reject) => {

            client.on('ready', () => {

                this._client = client;

                client.removeAllListeners('error');

                client.on('error', (err) => this.emit('error', err));

                resolve();
            });

            client.on('close', () => {
                this._closed = true;

                this.close();
            });

            client.on('connect', () => {
                this._client = client;
                this._closed = false;
            })

            client.on('timeout', () => {

                this.close();
            });

            client.on('error', reject);
            
            client.connect({
                host: this.host,
                port: this.options.port,
                password: this.options.password,
                username: this.options.user,
                timeout: 5000,
                readyTimeout: 5000,
                keepaliveInterval: 10000,
                keepaliveCountMax: 2
            });
        });
    }

    public async close(): Promise<void> {

        const client = this._client;

        if(!client) {
            return;
        }
            
        this._client = undefined;

        const end = () => {

            this.emit('disconnect');
        };

        return new Promise((resolve) => {

            if(this._closed) {
                
                end();
                
            } else {

                client.on('close', () => {

                    end();

                    resolve();
                });

                client.destroy();
            }

        });
    }

    public exec(cmd: string): Promise<Duplex>;
    public exec(cmd: string, direct: true): Promise<string[]>;

    public exec(cmd: string, direct=false): Promise<Duplex | string[]> {
        
        return new Promise<Duplex | string[]>((resolve, reject) => {
            
            this._client!.exec(cmd, (err, channel) => {

                if(err) {
                    return reject(err);
                }

                if(!direct) {
                    return resolve(channel);
                } 
                
                const chunks: Buffer[] = [];

                channel.stdout.on('data', (chunk: Buffer) => {

                    chunks.push(chunk);
                });

                channel.stdout.on('end', () => {

                    resolve(Buffer.concat(chunks).toString().trim().split('\n'));
                });
            });
        });
    }

    public shell(opts?: SshShellOptions): SshShell {

        return new SshShell(this._client!, opts);
    }
/*
    public sudo(cmd: string, password: string): SshShell {


        const shell = this.shell(cmd, {
            
        });

        shell.on('dataString', (str) => {

            if(str.indexOf('password for') !== -1) {
                    
                shell.write(password);
            }
        });
        
        return shell;
    }*/
}