package sfcrmappservice

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/applicationnavigationv1"
	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
)

func TestNavigationRuntimeContextErrors(t *testing.T) {
	for _, test := range []struct {
		cause  error
		status int
	}{
		{context.Canceled, 499}, {context.DeadlineExceeded, 504},
		{fmt.Errorf("local identity: %w", context.Canceled), 499},
		{fmt.Errorf("local identity: %w", context.DeadlineExceeded), 504},
		{errors.New("private internal detail"), 500},
		{&commonsemantic.Failure{Category: commonsemantic.FailureForbidden}, 403},
	} {
		cause := test.cause
		for _, route := range []string{"read", "save", "resolve"} {
			t.Run(route+"/"+cause.Error(), func(t *testing.T) {
				view := uiAppsTestView(t, uiAppsTestNavigation)
				s := uiAppsTestService(t, view, uiAppsTestPermissions(view))
				s.applicationNavigation = &RuntimeApplicationNavigationClient{
					read: func(context.Context, string, semantic.ReadRequest) (semantic.ReadResponse, error) {
						return semantic.ReadResponse{}, cause
					},
					save: func(context.Context, string, semantic.SaveRequest) (semantic.SaveResponse, error) {
						return semantic.SaveResponse{}, cause
					},
				}
				s.uiApps.resolveNavigation = func(context.Context, string, semantic.ResolveRequest) (semantic.ResolveResponse, error) {
					return semantic.ResolveResponse{}, cause
				}
				request := uiAppsRequest(http.MethodGet, ApplicationNavigationPrefix+"Sales")
				if route == "save" {
					request = httptest.NewRequest(http.MethodPut, ApplicationNavigationPrefix+"Sales", strings.NewReader(`{"expected_revision":1,"idempotency_key":"probe","items":[]}`))
					request.Header.Set("Authorization", "Bearer user-secret")
				}
				if route == "resolve" {
					request = uiAppsRequest(http.MethodGet, "/services/data/v63.0/ui-api/apps")
				}
				recorder := httptest.NewRecorder()
				s.ServeHTTP(recorder, request)
				if recorder.Code != test.status {
					t.Fatalf("status=%d want=%d body=%s", recorder.Code, test.status, recorder.Body.String())
				}
				if strings.Contains(recorder.Body.String(), "private internal detail") {
					t.Fatal("internal error leaked")
				}
			})
		}
	}
}

func TestNavigationSaveExpiredRequestNeverReachesRuntime(t *testing.T) {
	for _, canceled := range []bool{true, false} {
		t.Run(fmt.Sprintf("canceled=%t", canceled), func(t *testing.T) {
			ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(-time.Second))
			want := http.StatusGatewayTimeout
			if canceled {
				cancel()
				ctx, cancel = context.WithCancel(context.Background())
				cancel()
				want = 499
			}
			defer cancel()
			view := uiAppsTestView(t, uiAppsTestNavigation)
			service := uiAppsTestService(t, view, uiAppsTestPermissions(view))
			service.applicationNavigation = &RuntimeApplicationNavigationClient{
				save: func(context.Context, string, semantic.SaveRequest) (semantic.SaveResponse, error) {
					t.Fatal("expired request reached Runtime")
					return semantic.SaveResponse{}, nil
				},
			}
			request := httptest.NewRequest(http.MethodPut, ApplicationNavigationPrefix+"Sales", strings.NewReader(`{"expected_revision":1,"idempotency_key":"expired","items":[]}`)).WithContext(ctx)
			request.Header.Set("Authorization", "Bearer user-secret")
			recorder := httptest.NewRecorder()
			service.ServeHTTP(recorder, request)
			if recorder.Code != want {
				t.Fatalf("status=%d want=%d body=%s", recorder.Code, want, recorder.Body.String())
			}
		})
	}
}
