package sfcrmappservice

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/pingcap-inc/nexuscrm-api-host/runtimegrpc"
	listviewsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/listviewv1"
	sessionsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/sessionv1"
)

func TestMyListViewHTTPBindsCurrentSession(t *testing.T) {
	service := newTestService(t, &fakeRuntime{})
	config := sessionTestConfig()
	config.RuntimeReadSession = func(_ context.Context, credentials runtimegrpc.SessionPresentedCredentials, request sessionsemantic.ReadRequest) (sessionsemantic.ReadResponse, runtimegrpc.SessionIssuedCredentials, error) {
		if request.AllowRenewal || request.UseControlProof {
			t.Fatal("list projection must only read the authenticated session")
		}
		session := sessionTestResponse()
		session.Principal.PrincipalID = credentials.AccessToken
		return sessionsemantic.ReadResponse{Session: session}, runtimegrpc.SessionIssuedCredentials{}, nil
	}
	var err error
	service.sessions, err = newSessionHTTP(config)
	if err != nil {
		t.Fatal(err)
	}
	service.listViews, err = NewRuntimePrivateListViewClient(
		func(_ context.Context, _ string, req listviewsemantic.ReadRequest) (listviewsemantic.ReadResponse, error) {
			return listviewsemantic.ReadResponse{Views: []listviewsemantic.View{}, Receipts: listViewTestReceipts()}, nil
		},
		func(context.Context, string, listviewsemantic.MutateRequest) (listviewsemantic.MutateResponse, error) {
			t.Fatal("unexpected mutation")
			return listviewsemantic.MutateResponse{}, nil
		},
	)
	if err != nil {
		t.Fatal(err)
	}
	for _, object := range []string{"Lead", "Account", "Opportunity"} {
		for _, user := range []string{"user-a", "user-b", "user-a"} {
			for _, describe := range []bool{false, true} {
				path := "/services/data/v63.0/sobjects/" + object + "/listviews"
				if describe {
					path += "/" + object + "-my-view/describe"
				}
				req := httptest.NewRequest(http.MethodGet, path, nil)
				req.Header.Set("Authorization", "Bearer "+user)
				req.Header.Set("X-User-Id", "untrusted-user")
				res := httptest.NewRecorder()
				service.ServeHTTP(res, req)
				if res.Code != http.StatusOK {
					t.Fatalf("%s: %d %s", path, res.Code, res.Body.String())
				}
				var body struct {
					Query string `json:"query"`
					Views []struct {
						ID     string `json:"id"`
						Filter string `json:"filterExpr"`
					} `json:"listviews"`
				}
				if err := json.Unmarshal(res.Body.Bytes(), &body); err != nil {
					t.Fatal(err)
				}
				want := "OwnerId = '" + user + "'"
				if describe {
					if body.Query != "SELECT Id FROM "+object+" WHERE "+want {
						t.Errorf("%s user=%s: query=%q, want bound owner %q", path, user, body.Query, want)
					}
				} else {
					if len(body.Views) != 3 || body.Views[1].Filter != want {
						t.Errorf("%s user=%s: views=%+v, want bound owner %q", path, user, body.Views, want)
					}
				}
			}
		}
	}
}

func TestMyListViewFailsClosedWithoutSessionIdentity(t *testing.T) {
	for _, scenario := range []string{"missing-service", "empty-principal", "expired-session", "unavailable"} {
		t.Run(scenario, func(t *testing.T) {
			service := newTestService(t, &fakeRuntime{})
			service.listViews, _ = NewRuntimePrivateListViewClient(
				func(context.Context, string, listviewsemantic.ReadRequest) (listviewsemantic.ReadResponse, error) {
					return listviewsemantic.ReadResponse{Views: []listviewsemantic.View{}, Receipts: listViewTestReceipts()}, nil
				},
				func(context.Context, string, listviewsemantic.MutateRequest) (listviewsemantic.MutateResponse, error) {
					t.Fatal("unexpected mutation")
					return listviewsemantic.MutateResponse{}, nil
				},
			)
			if scenario != "missing-service" {
				config := sessionTestConfig()
				config.RuntimeReadSession = func(context.Context, runtimegrpc.SessionPresentedCredentials, sessionsemantic.ReadRequest) (sessionsemantic.ReadResponse, runtimegrpc.SessionIssuedCredentials, error) {
					var err error
					if scenario == "expired-session" {
						err = &sessionsemantic.Failure{Category: sessionsemantic.FailureUnauthenticated}
					}
					if scenario == "unavailable" {
						err = &sessionsemantic.Failure{Category: sessionsemantic.FailureUnavailable}
					}
					return sessionsemantic.ReadResponse{}, runtimegrpc.SessionIssuedCredentials{}, err
				}
				var err error
				service.sessions, err = newSessionHTTP(config)
				if err != nil {
					t.Fatal(err)
				}
			}
			for _, suffix := range []string{"", "/Lead-my-view/describe"} {
				req := httptest.NewRequest(http.MethodGet, "/services/data/v63.0/sobjects/Lead/listviews"+suffix, nil)
				req.Header.Set("Authorization", "Bearer opaque-session")
				res := httptest.NewRecorder()
				service.ServeHTTP(res, req)
				want := http.StatusUnauthorized
				if scenario == "missing-service" || scenario == "unavailable" {
					want = http.StatusServiceUnavailable
				}
				if res.Code != want {
					t.Fatalf("%s: status=%d want=%d body=%s", suffix, res.Code, want, res.Body.String())
				}
			}
		})
	}
}
