package httpmock

import (
	"fmt"
	"net/http"
	"sync"
	"testing"

	"github.com/stretchr/testify/assert"
)

// Replace http.Client transport layer with registry so all requests get
// recorded.
func ReplaceTripper(client *http.Client, reg *Registry) {
	client.Transport = reg
}

type Registry struct {
	mu       sync.Mutex
	stubs    []*Stub
	Requests []*http.Request
}

func (r *Registry) Register(m Matcher, resp Responder) {
	r.stubs = append(r.stubs, &Stub{
		Matcher:   m,
		Responder: resp,
	})
}

func (r *Registry) Exclude(t *testing.T, m Matcher) {
	excludedStub := &Stub{
		Matcher: m,
		Responder: func(req *http.Request) (*http.Response, error) {
			assert.FailNowf(t, "Exclude error", "API called when excluded: %v", req.URL)
			return nil, nil
		},
		exclude: true,
	}
	r.stubs = append(r.stubs, excludedStub)
}

type Testing interface {
	Errorf(string, ...interface{})
	Helper()
}

func (r *Registry) Verify(t Testing) {
	n := 0
	for _, s := range r.stubs {
		if !s.matched && !s.exclude {
			n++
		}
	}
	if n > 0 {
		t.Helper()
		// NOTE: stubs offer no useful reflection, so we can't print details
		// about dead stubs and what they were trying to match
		t.Errorf("%d unmatched HTTP stubs", n)
	}
}

// RoundTrip satisfies http.RoundTripper
func (r *Registry) RoundTrip(req *http.Request) (*http.Response, error) {
	var stub *Stub

	r.mu.Lock()
	for _, s := range r.stubs {
		if s.matched || !s.Matcher(req) {
			continue
		}
		// TODO: reinstate this check once the legacy layer has been cleaned up
		// if stub != nil {
		// 	r.mu.Unlock()
		// 	return nil, fmt.Errorf("more than 1 stub matched %v", req)
		// }
		stub = s
		break // TODO: remove
	}

	if stub != nil {
		stub.matched = true
	}

	if stub == nil {
		r.mu.Unlock()
		return nil, fmt.Errorf("no registered stubs matched %v", req)
	}

	r.Requests = append(r.Requests, req)
	r.mu.Unlock()

	return stub.Responder(req)
}
