package main

import (
	"context"
	"errors"
	"net/http"
	"sync"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/ooni/probe-cli/v3/internal/model"
	"github.com/ooni/probe-cli/v3/internal/model/mocks"
	"github.com/ooni/probe-cli/v3/internal/netxlite"
)

func TestHTTPDoWithInvalidURL(t *testing.T) {
	ctx := context.Background()
	wg := new(sync.WaitGroup)
	httpch := make(chan ctrlHTTPResponse, 1)
	wg.Add(1)
	go httpDo(ctx, &httpConfig{
		Headers:           nil,
		Logger:            model.DiscardLogger,
		MaxAcceptableBody: 1 << 24,
		NewClient: func(model.Logger) model.HTTPClient {
			return http.DefaultClient
		},
		Out: httpch,
		URL: "http://[::1]aaaa",
		Wg:  wg,
	})
	// wait for measurement steps to complete
	wg.Wait()
	resp := <-httpch
	if resp.Failure == nil || *resp.Failure != "unknown_error" {
		t.Fatal("not the failure we expected")
	}
}

func TestHTTPDoWithHTTPTransportFailure(t *testing.T) {
	expected := errors.New("mocked error")
	ctx := context.Background()
	wg := new(sync.WaitGroup)
	httpch := make(chan ctrlHTTPResponse, 1)
	wg.Add(1)
	go httpDo(ctx, &httpConfig{
		Headers:           nil,
		Logger:            model.DiscardLogger,
		MaxAcceptableBody: 1 << 24,
		NewClient: func(model.Logger) model.HTTPClient {
			return &http.Client{
				Transport: &mocks.HTTPTransport{
					MockRoundTrip: func(req *http.Request) (*http.Response, error) {
						return nil, expected
					},
					MockCloseIdleConnections: func() {
						// nothing
					},
				},
			}
		},
		Out: httpch,
		URL: "http://www.x.org",
		Wg:  wg,
	})
	// wait for measurement steps to complete
	wg.Wait()
	resp := <-httpch
	if resp.Failure == nil || *resp.Failure != "unknown_error" {
		t.Fatal("not the error we expected")
	}
}

func newErrWrapper(failure, operation string) error {
	return &netxlite.ErrWrapper{
		Failure:    failure,
		Operation:  operation,
		WrappedErr: nil, // should not matter
	}
}

func newErrWrapperTopLevel(failure string) error {
	return newErrWrapper(failure, netxlite.TopLevelOperation)
}

func Test_httpMapFailure(t *testing.T) {
	tests := []struct {
		name    string
		failure error
		want    *string
	}{{
		name:    "nil",
		failure: nil,
		want:    nil,
	}, {
		name:    "nxdomain",
		failure: newErrWrapperTopLevel(netxlite.FailureDNSNXDOMAINError),
		want:    stringPointerForString("dns_lookup_error"),
	}, {
		name:    "no answer",
		failure: newErrWrapperTopLevel(netxlite.FailureDNSNoAnswer),
		want:    stringPointerForString("dns_lookup_error"),
	}, {
		name:    "non recoverable failure",
		failure: newErrWrapperTopLevel(netxlite.FailureDNSNonRecoverableFailure),
		want:    stringPointerForString("dns_lookup_error"),
	}, {
		name:    "refused",
		failure: newErrWrapperTopLevel(netxlite.FailureDNSRefusedError),
		want:    stringPointerForString("dns_lookup_error"),
	}, {
		name:    "server misbehaving",
		failure: newErrWrapperTopLevel(netxlite.FailureDNSServerMisbehaving),
		want:    stringPointerForString("dns_lookup_error"),
	}, {
		name:    "temporary failure",
		failure: newErrWrapperTopLevel(netxlite.FailureDNSTemporaryFailure),
		want:    stringPointerForString("dns_lookup_error"),
	}, {
		name:    "timeout outside of dns lookup",
		failure: newErrWrapperTopLevel(netxlite.FailureGenericTimeoutError),
		want:    stringPointerForString(netxlite.FailureGenericTimeoutError),
	}, {
		name:    "timeout inside of dns lookup",
		failure: newErrWrapper(netxlite.FailureGenericTimeoutError, netxlite.ResolveOperation),
		want:    stringPointerForString("dns_lookup_error"),
	}, {
		name:    "connection refused",
		failure: newErrWrapperTopLevel(netxlite.FailureConnectionRefused),
		want:    stringPointerForString("connection_refused_error"),
	}, {
		name:    "anything else",
		failure: newErrWrapperTopLevel(netxlite.FailureEOFError),
		want:    stringPointerForString("unknown_error"),
	}}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := httpMapFailure(tt.failure)
			if diff := cmp.Diff(tt.want, got); diff != "" {
				t.Fatal(diff)
			}
		})
	}
}
