package requests

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
)

// Builder allows users to construct a request and then execute the
// request via Do().
// Do returns a Result which allows the user to get the body,
// unmarshal the body into an interface, or into a simplejson.Json.
type Builder interface {
	WithContext(context.Context) Builder
	WithBody(io.Reader) Builder
	WithMethod(string) Builder
	WithHeaders(http.Header) Builder
	SetHeader(key, value string) Builder
	Do() Result
}

type builder struct {
	context  context.Context
	method   string
	endpoint string
	body     io.Reader
	header   http.Header
	result   *result
}

// New provides a new Builder for the given endpoint.
func New(endpoint string) Builder {
	return &builder{
		endpoint: endpoint,
		method:   "GET",
	}
}

// WithContext adds a context to the request.
// If no context is provided, context.Background() is used instead.
func (r *builder) WithContext(ctx context.Context) Builder {
	r.context = ctx
	return r
}

// WithBody adds a body to the request.
func (r *builder) WithBody(body io.Reader) Builder {
	r.body = body
	return r
}

// WithMethod sets the request method. Defaults to "GET".
func (r *builder) WithMethod(method string) Builder {
	r.method = method
	return r
}

// WithHeaders replaces the request header map with the given header map.
func (r *builder) WithHeaders(header http.Header) Builder {
	r.header = header
	return r
}

// SetHeader sets a single header to the given value.
// May be used to add multiple headers.
func (r *builder) SetHeader(key, value string) Builder {
	if r.header == nil {
		r.header = make(http.Header)
	}
	r.header.Set(key, value)
	return r
}

// Do performs the request and returns the response in its raw form.
// If the request has already been performed, returns the previous result.
// This will not allow you to repeat a request.
func (r *builder) Do() Result {
	if r.result != nil {
		// Request has already been done
		return r.result
	}

	// Must provide a non-nil context to NewRequestWithContext
	if r.context == nil {
		r.context = context.Background()
	}

	return r.do()
}

// do creates the request, executes it with the default client and extracts the
// the body into the response
func (r *builder) do() Result {
	req, err := http.NewRequestWithContext(r.context, r.method, r.endpoint, r.body)
	if err != nil {
		r.result = &result{err: fmt.Errorf("error creating request: %v", err)}
		return r.result
	}
	req.Header = r.header

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		r.result = &result{err: fmt.Errorf("error performing request: %v", err)}
		return r.result
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		r.result = &result{err: fmt.Errorf("error reading response body: %v", err)}
		return r.result
	}

	r.result = &result{response: resp, body: body}
	return r.result
}