package middlewares

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	SOProto "github.com/AlchemyTelcoSolutions/proto/gen/go/callisto/so/v1"
	commonDomain "github.com/AlchemyTelcoSolutions/utils/service/domain"
	"github.com/go-chi/chi/v5"
	"github.com/stretchr/testify/assert"
	"google.golang.org/grpc"
)

func TestGetLegacyOrderID(t *testing.T) {
	t.Parallel()

	legacyID := 1234
	orderRef := "order-ref"

	testSuite := []struct {
		name           string
		testPath       string
		expectedValue  string
		grpcClient     func() grpc.ClientConnInterface
		statusExpected int
	}{
		{
			name:          "successful get order ref",
			testPath:      fmt.Sprintf("/sale-orders/%d", legacyID),
			expectedValue: orderRef,
			grpcClient: func() grpc.ClientConnInterface {
				return &clientConn{
					invokeFunc: func(ctx context.Context, method string, args, reply interface{}, opts ...grpc.CallOption) error {
						res, _ := reply.(*SOProto.GetNewReferenceResponse)
						*res = SOProto.GetNewReferenceResponse{
							OrderRef: orderRef,
						}
						return nil
					},
				}
			},
			statusExpected: http.StatusOK,
		},
		// we need to persist the original value in the context for forwarding requests like /callisto/v1/sale-orders/import-csv
		{
			name:          "invalid legacy id",
			testPath:      "/sale-orders/legacy-id",
			expectedValue: "legacy-id",
			grpcClient: func() grpc.ClientConnInterface {
				return &clientConn{
					invokeFunc: func(ctx context.Context, method string, args, reply interface{}, opts ...grpc.CallOption) error {
						return nil
					},
				}
			},
			statusExpected: http.StatusOK,
		},
		{
			name:     "order reference not found",
			testPath: fmt.Sprintf("/sale-orders/%d", legacyID),
			grpcClient: func() grpc.ClientConnInterface {
				return &clientConn{
					invokeFunc: func(ctx context.Context, method string, args, reply interface{}, opts ...grpc.CallOption) error {
						return commonDomain.WrapWithNotFoundError(fmt.Errorf("sale order not found by legacy id"), "order reference not found")
					},
				}
			},
			statusExpected: http.StatusNotFound,
		},
		{
			name:     "some error communicating with callisto-so",
			testPath: fmt.Sprintf("/sale-orders/%d", legacyID),
			grpcClient: func() grpc.ClientConnInterface {
				return &clientConn{
					invokeFunc: func(ctx context.Context, method string, args, reply interface{}, opts ...grpc.CallOption) error {
						return commonDomain.Wrap(fmt.Errorf("some error occured"), "random error with grpc service")
					},
				}
			},
			statusExpected: http.StatusInternalServerError,
		},
	}
	for _, ts := range testSuite {
		t.Run(ts.name, func(t *testing.T) {
			r := chi.NewRouter()

			middlewareFunc := GetOrderRefByLegacyID(ts.grpcClient())

			var referenceValue string
			// Define the test route
			r.Post("/sale-orders/{order_reference}", middlewareFunc(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
				referenceValue = chi.URLParam(r, "order_reference")
			})).ServeHTTP)

			req, err := http.NewRequest("POST", ts.testPath, nil)
			if err != nil {
				t.Fatal(err)
			}

			recorder := httptest.NewRecorder()
			r.ServeHTTP(recorder, req)

			assert.Equal(t, ts.statusExpected, recorder.Code)
			assert.Equal(t, ts.expectedValue, referenceValue)
		})
	}
}

type clientConn struct {
	invokeFunc    func(ctx context.Context, method string, args, reply interface{}, opts ...grpc.CallOption) error
	newStreamFunc func(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error)
}

func (c *clientConn) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...grpc.CallOption) error {
	return c.invokeFunc(ctx, method, args, reply, opts...)
}

func (c *clientConn) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) {
	return c.newStreamFunc(ctx, desc, method, opts...)
}
