/*
Package http is an implementation of http protocol
*/
package http

import (
	"io/ioutil"
	"net/http"
	"time"
)

// Client is the interface of http client
type Client interface {
	Send(*HttpRequest) (*HttpResponse, error)
}

// HttpClient used to send a real request via http to server
type HttpClient struct {
	transport http.RoundTripper
}

// NewHttpClient will create a new HttpClient instance
func NewHttpClient() HttpClient {
	return HttpClient{}
}

// NewHttpClientWithTransport will create a new HttpClient with specified transport
func NewHttpClientWithTransport(transport http.RoundTripper) HttpClient {
	return HttpClient{transport: transport}
}

// Send will send a real http request to remote server
func (c *HttpClient) Send(req *HttpRequest) (*HttpResponse, error) {
	// build http.Client with timeout settings
	httpClient, err := c.buildHTTPClient(req.GetTimeout())
	if err != nil {
		return nil, err
	}

	// convert sdk http request to origin http.Request
	httpReq, err := req.buildHTTPRequest()
	if err != nil {
		return nil, err
	}

	// TODO: enable tracer via `httptrace` package
	resp, err := c.doHTTPRequest(httpClient, httpReq)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

func (c *HttpClient) buildHTTPClient(timeout time.Duration) (*http.Client, error) {
	httpClient := http.Client{}
	if timeout != 0 {
		httpClient = http.Client{Timeout: timeout}
	}
	if c.transport != nil {
		httpClient.Transport = c.transport
	}
	return &httpClient, nil
}

func (c *HttpClient) doHTTPRequest(client *http.Client, req *http.Request) (*HttpResponse, error) {
	// send request
	httpResp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer httpResp.Body.Close()

	// raise status error
	if httpResp.StatusCode >= 400 {
		return nil, NewStatusError(httpResp.StatusCode, httpResp.Status)
	}

	// read content
	body, err := ioutil.ReadAll(httpResp.Body)
	if err != nil {
		return nil, err
	}

	// build response wrapper
	resp := NewHttpResponse()
	resp.setHttpResponse(httpResp)
	_ = resp.SetBody(body)
	return resp, nil
}
