package util

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strings"

	"github.com/juju/errors"
)

// DoPost issues a POST to the specified URL.
func DoPost(cli *http.Client, url string, data []byte) ([]byte, error) {
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
	if err != nil {
		return nil, errors.Annotatef(err, "new get request %s", url)
	}
	req.Header.Set("Content-Type", "application/json")
	return request(cli, req)
}

// DoPut issues a PUT to the specified URL.
func DoPut(cli *http.Client, url string, data []byte) ([]byte, error) {
	req, err := http.NewRequest("PUT", url, bytes.NewBuffer(data))
	if err != nil {
		return nil, errors.Annotatef(err, "new get request %s", url)
	}
	req.Header.Set("Content-Type", "application/json")
	return request(cli, req)
}

// DoGet issues a GET to the specified URL.
func DoGet(cli *http.Client, url string) ([]byte, error) {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, errors.Annotatef(err, "new get request %s", url)
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Connection", "close")
	return request(cli, req)
}

// DoDelete issues a DELETE to the specified URL
func DoDelete(cli *http.Client, url string) ([]byte, error) {
	req, err := http.NewRequest("DELETE", url, nil)
	if err != nil {
		return nil, errors.Annotatef(err, "new delete request %s", url)
	}
	req.Header.Set("Content-Type", "application/json")
	return request(cli, req)
}

func request(cli *http.Client, req *http.Request) ([]byte, error) {
	resp, err := cli.Do(req)
	if err != nil {
		return nil, errors.Annotate(err, "request error")
	}
	defer resp.Body.Close()

	bodyByte, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, errors.Annotate(err, "fail to read body")
	}

	if resp.StatusCode != http.StatusOK {
		return nil, errors.Errorf("request status code %d data %s", resp.StatusCode, string(bodyByte))
	}

	return bodyByte, nil
}

// GenURL adds 'http' prefix for URL
func GenURL(url string) string {
	if strings.Contains(url, "http") {
		return url
	}

	return fmt.Sprintf("http://%s", url)
}

// ReadJSON reads json from http request.
func ReadJSON(r io.ReadCloser, data interface{}) error {
	defer r.Close()
	b, err := ioutil.ReadAll(r)
	if err != nil {
		return errors.Trace(err)
	}
	return errors.Trace(json.Unmarshal(b, data))
}

// GetAndExtract gets and ertract uri data
func GetAndExtract(cli *http.Client, url string) ([]byte, error) {
	var data []byte
	d, err := DoGet(cli, url)
	if err != nil {
		return data, errors.Annotatef(err, "url - %s", url)
	}
	data, err = ExtractResponse(d)
	return data, errors.Trace(err)
}

// PostAndExtract posts and extract uri data
func PostAndExtract(cli *http.Client, url string, postData []byte) ([]byte, error) {
	var data []byte
	d, err := DoPost(cli, url, postData)
	if err != nil {
		return data, errors.Annotatef(err, "url - %s", url)
	}
	data, err = ExtractResponse(d)
	return data, errors.Trace(err)
}

// PutAndExtract puts and extract uri data
func PutAndExtract(cli *http.Client, url string, putData []byte) ([]byte, error) {
	var data []byte
	d, err := DoPut(cli, url, putData)
	if err != nil {
		return data, errors.Annotatef(err, "url - %s", url)
	}
	data, err = ExtractResponse(d)
	return data, errors.Trace(err)
}

// DeleteAndExtract deletes and extract uri data
func DeleteAndExtract(cli *http.Client, url string) ([]byte, error) {
	var data []byte
	d, err := DoDelete(cli, url)
	if err != nil {
		return data, errors.Annotatef(err, "url - %s", url)
	}
	data, err = ExtractResponse(d)
	return data, errors.Trace(err)
}
