const { HttpRequestManager , request , get , http , https} = require('../../lib/index');
const requestModule = require('request');

const {Http2Debug} = require('http2-debug');

const chai = require('chai');
const expect = chai.expect;
chai.use(require('chai-spies'));

const httpModule = require('http');
const SERVER_HOST = '0.0.0.0';

const HTTP_PORT = 8080;
const HTTP2_PORT = 8443;

const HTTP_URL = `http://${SERVER_HOST}:${HTTP_PORT}`;
const HTTP2_URL = `https://${SERVER_HOST}:${HTTP2_PORT}`;

const serverCloseActions = [];

const onHttpServerReady = new Promise((resolve , reject)=>{
    try{
        const server = httpModule.createServer((req, res) => {
            getBody(req)
            .then((bodyRaw)=>{
                const body = JSON.parse(bodyRaw ? bodyRaw : "{}");
                const headers = req.headers;

                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({
                    path : req.url,
                    method : req.method,
                    body,
                    headers
                }));
            })
            .catch((err)=>{
                res.status(500).end('')
            })
        });
        server.listen(HTTP_PORT,SERVER_HOST, (err) => {
            if (err)
                return reject(err);

            serverCloseActions.push(server.close.bind(server));
            resolve()
        });
    }
    catch(err){
        reject(err);
    }
});
const onHTTP2ServerReady = new Promise((resolve , reject)=>{
    http2Debug = new Http2Debug;
    http2Debug.createServer((err)=>{
        if (err)
            return reject(err);
        resolve();
        serverCloseActions.push(http2Debug.stopServer.bind(http2Debug));
    });
})

describe('e2e' , ()=>{
    before(()=>{
        return Promise.all([
            onHTTP2ServerReady,
            onHttpServerReady
        ])
    })
    describe('third party validation' ,()=>{
        let requestWithHttp2Client;
        before(()=>{

            requestWithHttp2Client = requestModule.defaults({
                httpModules : {
                    'http:' : http,
                    'https:' : https,
                }
            });
        })
        it('http1 request module should be able to make post request with json body' , async ()=>{
            return new Promise((resolve,reject)=>{
                requestWithHttp2Client({
                    uri : `${HTTP_URL}/test1`,
                    method : 'post',
                    json:{
                        name : 'test1'
                    },
                    headers : {
                        'tesT-me' :'90'
                    }
                }, function (error, response, body) {
                    if (error)
                        return reject(err);
                    try{
                        expect(body.body.name).eq('test1');
                        expect(body.headers['test-me']).eq('90');
                        expect(response.statusCode).eq(200);
                        expect(response.body.method).eq('POST');
                        resolve();
                    }
                    catch(err){
                        reject(err);
                    }
                   
                });
            })
        })
        it('http2 request module should be able to make post request with json body' , async ()=>{
            return new Promise((resolve,reject)=>{
                requestWithHttp2Client({
                    uri : `${HTTP2_URL}/test1`,
                    method : 'post',
                    json:{
                        name : 'test1'
                    },
                    headers : {
                        'tesT-me' :'90'
                    }
                }, function (error, response, respBody) {
                    if (error)
                        return reject(error);
                    try{
                        const json = JSON.parse(respBody.body);
                        expect(json.name).eq('test1');
                        expect(respBody.headers['test-me']).eq('90');
                        expect(response.statusCode).eq(200);
                        expect(respBody.headers[':method']).eq('POST');
                        resolve();
                    }
                    catch(err){
                        reject(err);
                    }
                   
                });
            })
        })
    })
    
    describe('request' , ()=>{
        describe('http1' , ()=>{
            it('Should be able to make request with request options as a string' , ()=>{
                return new Promise((resolve , reject)=>{
                    const req = request(`${HTTP_URL}/test1` , (res)=>{
                        getBody(res)
                        .then((bodyRaw)=>{
                            const json = JSON.parse(bodyRaw);
                            expect(res.statusCode).eq(200);
                            expect(json.path).eq('/test1');
                            expect(json.method).eq('GET');
                            resolve();
                        })
                        .catch((err)=>{
                            reject(err)
                        })
                    });
                    req.end();
                })
            });
            it('Should be able to make request with request options and url as a string' , ()=>{
                return new Promise((resolve , reject)=>{
                    const req = request(`${HTTP_URL}/test1` , { method : 'POST'}, (res)=>{
                        getBody(res)
                        .then((bodyRaw)=>{
                            const json = JSON.parse(bodyRaw);
                            expect(res.statusCode).eq(200);
                            expect(json.path).eq('/test1');
                            expect(json.method).eq('POST');
                            expect(json.body.test).eq(1);
                            resolve();
                        })
                        .catch((err)=>{
                            reject(err)
                        })
                    });
                    req.write('{"test":1}')
                    req.end();
                })
            });
            it('Should be able to make request with request options and method lowercase' , ()=>{
                return new Promise((resolve , reject)=>{
                    const req = request({
                        path : '/test1',
                        host : SERVER_HOST,
                        port : HTTP_PORT,
                        method : 'post'
                    } , (res)=>{
                        getBody(res)
                        .then((bodyRaw)=>{
                            const json = JSON.parse(bodyRaw);
                            expect(res.statusCode).eq(200);
                            expect(json.path).eq('/test1');
                            expect(json.method).eq('POST');
                            resolve();
                        })
                        .catch((err)=>{
                            reject(err)
                        })
                    });
                    req.end();
                })
            });
            it('Should be able to make request with request options and headers' , ()=>{
                return new Promise((resolve , reject)=>{
                    const req = request({
                        path : '/test1',
                        host : SERVER_HOST,
                        port : HTTP_PORT,
                        method : 'delete',
                        headers : {
                            'tesT-me' :'90'
                        }
                    } , (res)=>{
                        getBody(res)
                        .then((bodyRaw)=>{
                            const json = JSON.parse(bodyRaw);
                            expect(json.headers['test-me']).eq('90');
                            expect(res.statusCode).eq(200);
                            expect(json.path).eq('/test1');
                            expect(json.method).eq('DELETE');
                            resolve();
                        })
                        .catch((err)=>{
                            reject(err)
                        })
                    });
                    req.end();
                })
            });
        })
        describe('http2' , ()=>{
            it('Should be able to make request with request options with body' , ()=>{
                return new Promise((resolve , reject)=>{
                    const req = request({
                        path : '/test1',
                        protocol : 'https:',
                        host : SERVER_HOST,
                        port : HTTP2_PORT,
                        method : 'POST',
                        headers : {
                            'test-me' : 90
                        }
                    } , (res)=>{
                        getBody(res)
                        .then((bodyRaw)=>{
                            const json = JSON.parse(bodyRaw);
                            expect(json.headers['test-me']).eq('90');
                            expect(res.statusCode).eq(200);
                            expect(json.headers[':path']).eq('/test1');
                            expect(json.headers[':method']).eq('POST');
                            expect(JSON.parse(json.body).test).eq(1);
                            resolve();
                        })
                        .catch((err)=>{
                            reject(err)
                        })
                    });
                    req.write('{"test":1}');
                    req.end();
                })
            });
            
            it('Should be able to make request with request options and url as string' , ()=>{
                return new Promise((resolve , reject)=>{
                    const req = request(HTTP2_URL , {
                        path : '/test1',
                        host : SERVER_HOST,
                        method : 'POST',
                        protocol : 'https:',
                        port : HTTP2_PORT,
                        headers : {
                            'tesT-me' :'90'
                        }
                    } , (res)=>{
                        getBody(res)
                        .then((bodyRaw)=>{
                            const json = JSON.parse(bodyRaw);
                            expect(json.headers['test-me']).eq('90');
                            expect(res.statusCode).eq(200);
                            expect(json.headers[':path']).eq('/test1');
                            expect(json.headers[':method']).eq('POST');
                            resolve();
                        })
                        .catch((err)=>{
                            reject(err)
                        })
                    });
                    req.end();
                });
            });
            it('Should be able to make request with request as string' , ()=>{
                return new Promise((resolve , reject)=>{
                    const req = request(`${HTTP2_URL}/test1`, (res)=>{
                        getBody(res)
                        .then((bodyRaw)=>{
                            const json = JSON.parse(bodyRaw);
                            expect(res.statusCode).eq(200);
                            expect(json.headers[':path']).eq('/test1');
                            expect(json.headers[':method']).eq('GET');
                            resolve();
                        })
                        .catch((err)=>{
                            reject(err)
                        })
                    });
                    req.end();
                });
            });
        });
    });
    
    describe('get' , ()=>{
        before(()=>{
            return Promise.all([
                onHTTP2ServerReady,
                onHttpServerReady
            ])
        });
        describe('http1' , ()=>{
            it('Should be able to make request with request options as a string' , ()=>{
                return new Promise((resolve , reject)=>{
                    const req = get(`${HTTP_URL}/test1` , (res)=>{
                        getBody(res)
                        .then((bodyRaw)=>{
                            const json = JSON.parse(bodyRaw);
                            expect(res.statusCode).eq(200);
                            expect(json.path).eq('/test1');
                            expect(json.method).eq('GET');
                            resolve();
                        })
                        .catch((err)=>{
                            reject(err)
                        })
                    });
                    req.end();
                })
            });
            it('Should be able to make request with request options and url as a string' , ()=>{
                return new Promise((resolve , reject)=>{
                    const req = get(`${HTTP_URL}/test1` , { method : 'POST'}, (res)=>{
                        getBody(res)
                        .then((bodyRaw)=>{
                            const json = JSON.parse(bodyRaw);
                            expect(res.statusCode).eq(200);
                            expect(json.path).eq('/test1');
                            expect(json.method).eq('GET');
                            resolve();
                        })
                        .catch((err)=>{
                            reject(err)
                        })
                    });
                    req.end();
                })
            });
            it('Should be able to make request with request options method lowercase' , ()=>{
                return new Promise((resolve , reject)=>{
                    const req = get({
                        path : '/test1',
                        host : SERVER_HOST,
                        port : HTTP_PORT,
                        method : 'post'
                    } , (res)=>{
                        getBody(res)
                        .then((bodyRaw)=>{
                            const json = JSON.parse(bodyRaw);
                            expect(res.statusCode).eq(200);
                            expect(json.path).eq('/test1');
                            expect(json.method).eq('GET');
                            resolve();
                        })
                        .catch((err)=>{
                            reject(err)
                        })
                    });
                })
            });
            it('Should be able to make request with request options and headers' , ()=>{
                return new Promise((resolve , reject)=>{
                    const req = get({
                        path : '/test1',
                        host : SERVER_HOST,
                        port : HTTP_PORT,
                        method : 'delete',
                        headers : {
                            'tesT-me' :'90'
                        }
                    } , (res)=>{
                        getBody(res)
                        .then((bodyRaw)=>{
                            const json = JSON.parse(bodyRaw);
                            expect(json.headers['test-me']).eq('90');
                            expect(res.statusCode).eq(200);
                            expect(json.path).eq('/test1');
                            expect(json.method).eq('GET');
                            resolve();
                        })
                        .catch((err)=>{
                            reject(err)
                        })
                    });
                })
            });
        })
        describe('http2' , ()=>{
            it('Should be able to make request with request options and headers' , ()=>{
                return new Promise((resolve , reject)=>{
                    const req = get({
                        path : '/test1',
                        protocol : 'https:',
                        host : SERVER_HOST,
                        port : HTTP2_PORT,
                        method : 'POST',
                        headers : {
                            'test-me' : 90
                        }
                    } , (res)=>{
                        getBody(res)
                        .then((bodyRaw)=>{
                            const json = JSON.parse(bodyRaw);
                            expect(json.headers['test-me']).eq('90');
                            expect(res.statusCode).eq(200);
                            expect(json.headers[':path']).eq('/test1');
                            expect(json.headers[':method']).eq('GET');
                            resolve();
                        })
                        .catch((err)=>{
                            reject(err)
                        })
                    });
                })
            });
            
            it('Should be able to make request with request options and url as string' , ()=>{
                return new Promise((resolve , reject)=>{
                    const req = get(HTTP2_URL , {
                        path : '/test1',
                        host : SERVER_HOST,
                        method : 'POST',
                        protocol : 'https:',
                        port : HTTP2_PORT,
                        headers : {
                            'tesT-me' :'90'
                        }
                    } , (res)=>{
                        getBody(res)
                        .then((bodyRaw)=>{
                            const json = JSON.parse(bodyRaw);
                            expect(json.headers['test-me']).eq('90');
                            expect(res.statusCode).eq(200);
                            expect(json.headers[':path']).eq('/test1');
                            expect(json.headers[':method']).eq('GET');
                            resolve();
                        })
                        .catch((err)=>{
                            reject(err)
                        })
                    });
                });
            });
            it('Should be able to make request with request as string and body' , ()=>{
                return new Promise((resolve , reject)=>{
                    const req = get(`${HTTP2_URL}/test1`, (res)=>{
                        getBody(res)
                        .then((bodyRaw)=>{
                            const json = JSON.parse(bodyRaw);
                            expect(res.statusCode).eq(200);
                            expect(json.headers[':path']).eq('/test1');
                            expect(json.headers[':method']).eq('GET');
                            resolve();
                        })
                        .catch((err)=>{
                            reject(err)
                        })
                    });
                });
            });
        });
    });
    

    after(async ()=>{
       return new Promise((resolve)=>{
            serverCloseActions.forEach((action)=>{
                action();
            });
            setTimeout(resolve , 100)
       })
    })
})

function getBody(stream){
    return new Promise((resolve , reject)=>{
        let bodyRaw = '';
        stream.on('data' , (chunk)=>{
            bodyRaw+=chunk;
        });
        stream.on('end',(chunk)=>{
            if (chunk)
                bodyRaw+=chunk;
            resolve(bodyRaw);
        });
        stream.on('error' , (err)=>{
            reject(err)
        })
    })
}
