import {Host} from './Host'
import {Application, Awaitable, Filesystem, SSHFilesystem} from '@extollo/lib'
import {ExecutionResult} from './ExecutionResult'
import {ShellCommand} from './types'
import * as ssh2 from 'ssh2'

export class SSHHost extends Host {
    private sshClient?: ssh2.Client
    private filesystem?: Filesystem

    constructor(
        protected readonly config: ssh2.ConnectConfig,
    ) {
        super()
    }

    async close() {
        this.sshClient?.end()
        this.sshClient?.destroy()
        this.sshClient = undefined

        this.filesystem?.close()
        this.filesystem = undefined
    }

    async execute(command: ShellCommand): Promise<ExecutionResult>{
        const client = await this.getSSH()
        const result = new ExecutionResult()
        return new Promise<ExecutionResult>((res, rej) => {
            client.exec(command, (err, stream) => {
                if ( err ) {
                    return rej(err)
                }

                stream
                    .on('close', (code: number) => {
                        result.exit(code)
                        res(result)
                    })
                    .on('data', (data: any) => {
                        result.out(data)
                    })
                    .stderr.on('data', (data: any) => {
                        result.error(data)
                    })
            })
        })
    }

    getFilesystem(): Awaitable<Filesystem> {
        if ( !this.filesystem ) {
            this.filesystem = Application.getContainer().makeNew<SSHFilesystem>(SSHFilesystem, {
                ssh: this.config,
                baseDir: '/',
            })
        }

        return this.filesystem
    }

    protected async getSSH(): Promise<ssh2.Client> {
        if ( this.sshClient ) {
            return this.sshClient
        }

        return new Promise((res, rej) => {
            const client = new ssh2.Client()

            client.on('ready', () => {
                this.sshClient = client
                res(client)
            }).connect(this.config)

            client.on('error', rej)
        })
    }
}
