package caller

import (
	"io"
	"net/http"

	"code.justin.tv/sse/malachai/pkg/log"
	"code.justin.tv/sse/malachai/pkg/s2s/internal"
)

// RoundTripper implements http.RoundTripper. It signs requests using the
// configured caller service's private key, retrieved from sandstorm.
type RoundTripper struct {
	inner  http.RoundTripper
	signer *RequestSigner
	logger log.S2SLogger
}

// NewRoundTripper returns an http.RoundTripper, which wraps the default http.RoundTripper
// with a request signer
//
// If logger is nil, it will be defaulted to a nil logger.
func NewRoundTripper(callerName string, cfg *Config, logger log.S2SLogger) (rt *RoundTripper, err error) {
	logger = log.NoOpIfNil(logger)

	rt, err = NewWithCustomRoundTripper(callerName, cfg, http.DefaultTransport, logger)
	return
}

// NewWithCustomRoundTripper wraps the signing RoundTripper with the provided
// http.RoundTripper
//
// If logger is nil, it will be defaulted to a nil logger.
func NewWithCustomRoundTripper(callerName string, cfg *Config, inner http.RoundTripper, logger log.S2SLogger) (rt *RoundTripper, err error) {
	logger = log.NoOpIfNil(logger)

	signer, err := NewRequestSigner(callerName, cfg, logger)
	if err != nil {
		return
	}

	signingRoundTripper := &RoundTripper{
		inner:  inner,
		signer: signer,
		logger: logger,
	}

	rt = signingRoundTripper
	return
}

// Close cleans up the sandstorm notification pipeline
func (rt *RoundTripper) Close() (err error) {
	return rt.signer.Close()
}

// SetLogger sets the logger
func (rt *RoundTripper) SetLogger(logger log.S2SLogger) {
	rt.logger = log.NoOpIfNil(logger)
}

// SetInnerRoundTripper sets the inner round tripper
func (rt *RoundTripper) SetInnerRoundTripper(inner http.RoundTripper) {
	rt.inner = inner
}

// RoundTrip implements http.RoundTripper
func (rt *RoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	if req.Body == nil || req.Body == http.NoBody {
		if err = rt.signer.SignRequest(req); err != nil {
			return
		}

		return rt.inner.RoundTrip(req)
	}

	seeker, err := internal.WrapRequestBodyWithSeeker(req, 0)
	if err != nil {
		return nil, err
	}

	rt.logger.Debugf("signing a request with body, content-length: %d", req.ContentLength)
	err = rt.signer.SignRequest(req)
	if err != nil {
		return nil, err
	}

	_, err = seeker.Seek(0, io.SeekStart)
	if err != nil {
		return nil, err
	}
	return rt.inner.RoundTrip(req)
}

// SignRequest signs a request and adds header values to the request
func (rt *RoundTripper) SignRequest(req *http.Request) (err error) {
	return rt.signer.SignRequest(req)
}

// SignRequestWithHashedBody signs a request and adds header values to the request
func (rt *RoundTripper) SignRequestWithHashedBody(req *http.Request, hashedBody []byte) (err error) {
	return rt.signer.SignRequestWithHashedBody(req, hashedBody)
}

// GenerateSignature returns the signed header and header name from the hashed body
func (rt *RoundTripper) GenerateSignature(r *http.Request, hashedBody []byte) (key string, value string, err error) {
	return rt.signer.GenerateSignature(r, hashedBody)
}
