package proxy

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/AlchemyTelcoSolutions/callisto-so-bff/cmd/app/clients"
	"github.com/AlchemyTelcoSolutions/callisto-so-bff/cmd/app/config"
	"github.com/AlchemyTelcoSolutions/callisto-so-bff/internal/domain/model"
	"github.com/AlchemyTelcoSolutions/xutils-go/xlogger"
	"github.com/stretchr/testify/assert"
)

func TestProxyService(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name              string
		assert            func(t *testing.T, resp *model.ProxyResponse)
		callistoAPIClient clients.HTTPProxyClientInterface
		funcToCall        func(*Service, *http.Request) *model.ProxyResponse
		configPaths       map[string]string
		request           *http.Request
	}{
		{
			name: "error calling callisto api",
			assert: func(t *testing.T, resp *model.ProxyResponse) {
				assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
			},
			callistoAPIClient: &mockHTTPProxyClient{
				DoProxyFunc: func(*http.Request, string, map[string]string) (*http.Response, error) {
					return nil, fmt.Errorf("server fail")
				},
			},
			funcToCall: func(s *Service, r *http.Request) *model.ProxyResponse {
				return s.ForwardProxy(r, r.URL.Path)
			},
			request: getTestRequest(),
		},
		{
			name: "error reading body",
			assert: func(t *testing.T, resp *model.ProxyResponse) {
				assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
			},
			callistoAPIClient: &mockHTTPProxyClient{
				DoProxyFunc: func(*http.Request, string, map[string]string) (*http.Response, error) {
					return &http.Response{
						StatusCode: 200,
						Body:       &brokenReader{},
					}, nil
				},
			},
			funcToCall: func(s *Service, r *http.Request) *model.ProxyResponse {
				return s.ForwardProxy(r, r.URL.Path)
			},
			request: getTestRequest(),
		},
		{
			name: "success proxy response",
			assert: func(t *testing.T, resp *model.ProxyResponse) {
				assert.Equal(t, http.StatusOK, resp.StatusCode)
			},
			callistoAPIClient: &mockHTTPProxyClient{
				DoProxyFunc: func(*http.Request, string, map[string]string) (*http.Response, error) {
					r := io.NopCloser(bytes.NewReader(
						[]byte(`{"success":true,"result":[{"location_id":14,"order_number":80005468,"items":[]}]}`)),
					)
					return &http.Response{
						StatusCode: 200,
						Body:       r,
					}, nil
				},
			},
			funcToCall: func(s *Service, r *http.Request) *model.ProxyResponse {
				return s.ForwardProxy(r, r.URL.Path)
			},
			request: getTestRequest(),
		},
		{
			name: "success proxy response with configs",
			assert: func(t *testing.T, resp *model.ProxyResponse) {
				assert.Equal(t, http.StatusOK, resp.StatusCode)
			},
			configPaths: map[string]string{
				"/callisto/v1/endpoint": "v2/endpoint",
			},
			callistoAPIClient: &mockHTTPProxyClient{
				DoProxyFunc: func(*http.Request, string, map[string]string) (*http.Response, error) {
					r := io.NopCloser(bytes.NewReader(
						[]byte(`{"success":true,"result":[{"location_id":14,"order_number":80005468,"items":[]}]}`)),
					)
					return &http.Response{
						StatusCode: 200,
						Body:       r,
					}, nil
				},
			},
			funcToCall: func(s *Service, r *http.Request) *model.ProxyResponse {
				return s.ForwardProxy(r, r.URL.Path)
			},
			request: getTestRequest(),
		},
		{
			name: "success proxy response with configs don't match",
			assert: func(t *testing.T, resp *model.ProxyResponse) {
				assert.Equal(t, http.StatusOK, resp.StatusCode)
			},
			configPaths: map[string]string{
				"/callisto/v1/endpoint_no_match": "v2/endpoint",
			},
			callistoAPIClient: &mockHTTPProxyClient{
				DoProxyFunc: func(*http.Request, string, map[string]string) (*http.Response, error) {
					r := io.NopCloser(bytes.NewReader(
						[]byte(`{"success":true,"result":[{"location_id":14,"order_number":80005468,"items":[]}]}`)),
					)
					return &http.Response{
						StatusCode: 200,
						Body:       r,
					}, nil
				},
			},
			funcToCall: func(s *Service, r *http.Request) *model.ProxyResponse {
				return s.ForwardProxy(r, r.URL.Path)
			},
			request: getTestRequest(),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			config := &config.Config{}
			config.CallistoAPI.Host = "0.0.0.0"
			config.HTTP.Proxy.Paths = tt.configPaths
			s := NewProxyService().
				SetHttpClient(tt.callistoAPIClient).
				SetLogger(xlogger.NoOpLogger{}).
				SetConfigs(config)
			resp := tt.funcToCall(s, tt.request)
			tt.assert(t, resp)
		})
	}
}

func getTestRequest() *http.Request {
	jsonString := `{
		"sent":1,
		"shipping_address_id": 1
	}`
	body := io.NopCloser(bytes.NewReader([]byte(jsonString)))
	httpRequest := httptest.NewRequest(http.MethodPut, "http://localhost.com/api/v1/endpoint?with_query_string=1&and_second=2", body)
	return httpRequest
}

type mockHTTPProxyClient struct {
	DoProxyFunc func(*http.Request, string, map[string]string) (*http.Response, error)
	SendFunc    func(*http.Request) (*http.Response, error)
}

func (m *mockHTTPProxyClient) DoProxy(r *http.Request, url string, h map[string]string) (*http.Response, error) {
	return m.DoProxyFunc(r, url, h)
}

func (m *mockHTTPProxyClient) Send(r *http.Request) (*http.Response, error) {
	return m.SendFunc(r)
}

type brokenReader struct{}

func (br *brokenReader) Read(p []byte) (n int, err error) {
	return 0, fmt.Errorf("failed reading")
}

func (br *brokenReader) Close() error {
	return fmt.Errorf("failed closing")
}
