package api_test

import (
	"bytes"
	"context"
	"io"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"

	"github.com/AlchemyTelcoSolutions/callisto-so-bff/cmd/app/config"
	"github.com/AlchemyTelcoSolutions/callisto-so-bff/cmd/app/handler"
	"github.com/AlchemyTelcoSolutions/callisto-so-bff/internal/api"
	api_mock "github.com/AlchemyTelcoSolutions/callisto-so-bff/internal/api/mocks"
	"github.com/AlchemyTelcoSolutions/callisto-so-bff/internal/proxy"
	"github.com/AlchemyTelcoSolutions/xutils-go/xlogger"
)

const (
	defaultApiHost = "http://example-api:8080/callisto/v1"
)

type mockClients struct {
	saleOrderService *api_mock.SaleOrderService
}

type baseHandlerTestCase struct {
	*mockClients
	name                  string
	HTTPProxyHost         string
	route                 string
	method                string
	body                  string
	file                  []byte
	fileParamName         string
	statusCode            int
	mockFunc              func(c *baseHandlerTestCase)
	customTestFunc        func(t *testing.T, response []byte, c *baseHandlerTestCase)
	customMultipartWriter func(t *testing.T, body *bytes.Buffer) *multipart.Writer
	t                     *testing.T
	proxySvc              func(*testing.T) proxy.ProxyService
	config                *config.Config
}

func getMockClients(t *testing.T) *mockClients {
	return &mockClients{
		saleOrderService: api_mock.NewSaleOrderService(t),
	}
}

func (c *baseHandlerTestCase) genericHandlerTestFunc() func(t *testing.T) {
	return func(t *testing.T) {
		c.t = t
		c.mockClients = getMockClients(t)

		if c.HTTPProxyHost == "" {
			c.HTTPProxyHost = defaultApiHost
		}

		s := setupTestServer(c)
		defer s.Close()

		c.route = s.URL + c.route

		if c.mockFunc != nil {
			c.mockFunc(c)
		}

		res, err := makeHTTPCall(c)

		if err != nil {
			t.Fatalf("Error when making http call: %v", err)
		}

		//nolint:gosec
		defer res.Body.Close()

		if res.StatusCode != c.statusCode {
			b, _ := io.ReadAll(res.Body)
			t.Fatalf("Expected status code %v, but got status code %v - %s", c.statusCode, res.StatusCode, string(b))
		}

		b, err := io.ReadAll(res.Body)
		if err != nil {
			t.Fatalf("Found error reading response body: %v", err)
		}
		// execute custom logic for this test, if it exists
		if c.customTestFunc != nil {
			c.customTestFunc(t, b, c)
		}
	}
}

func setupTestServer(
	testCase *baseHandlerTestCase,
) *httptest.Server {
	ctx := context.Background()
	cfg := &config.Config{}
	cfg = testCase.config
	if cfg == nil {
		cfg = &config.Config{}
		// default config
		cfg.HTTP.ApiPrefix = "/callisto"
		cfg.CallistoAPI.Host = "0.0.0.0"
		cfg.CallistoAPI.SaleOrder.Create = "v1/create"
		cfg.HTTP.Proxy.IsEnabled = true
	}

	h, _ := handler.NewHandler(
		ctx,
		handler.Config{
			Logger: xlogger.NoOpLogger{},
			Server: api.NewServer(api.ServerOptions{
				Logger:       xlogger.NoOpLogger{},
				SaleOrderSvc: testCase.saleOrderService,
				ProxySvc:     testCase.proxySvc(testCase.t),
				Config:       cfg,
			}),
			Configurations: cfg,
		},
	)

	server := httptest.NewServer(h)
	return server
}

func makeHTTPCall(c *baseHandlerTestCase) (*http.Response, error) {
	client := &http.Client{}
	if c.file == nil && c.customMultipartWriter == nil {
		req, err := http.NewRequest(c.method, c.route, strings.NewReader(c.body))
		if err != nil {
			return nil, err
		}
		return client.Do(req)
	}
	if c.customMultipartWriter == nil && c.file != nil && c.fileParamName == "" {
		c.fileParamName = "file"
	}

	body := new(bytes.Buffer)
	var writer *multipart.Writer
	if c.customMultipartWriter != nil {
		writer = c.customMultipartWriter(c.t, body)
	} else {
		writer = multipart.NewWriter(body)
		part, err := writer.CreateFormFile(c.fileParamName, "my-file")
		if err != nil {
			return nil, err
		}
		_, err = part.Write(c.file)
		if err != nil {
			return nil, err
		}

		err = writer.Close()
		if err != nil {
			return nil, err
		}
	}

	req, err := http.NewRequest(c.method, c.route, body)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Content-Type", writer.FormDataContentType())
	return client.Do(req)
}

func buildUrl(u string, params map[string]string) string {
	v := url.Values{}
	for k, q := range params {
		v.Add(k, q)
	}

	l := url.URL{Path: u, RawQuery: v.Encode()}
	return l.String()
}
