package runtimeapi

import (
	"context"
	"testing"
	"time"

	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/delegationv1"
	listviewsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/listviewv1"
	recordsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordsv1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/mapping"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

type privateListViewReaderStub struct {
	views []*models.ListView
	user  string
}

func (s *privateListViewReaderStub) ListPrivate(_ context.Context, user *models.UserSession, _ string) ([]*models.ListView, error) {
	s.user = user.ID
	return s.views, nil
}

func (s *privateListViewReaderStub) DescribePrivate(_ context.Context, user *models.UserSession, _, _ string) (*models.ListView, error) {
	s.user = user.ID
	return s.views[0], nil
}

type privateListViewAuthoringStub struct {
	created *models.ListView
	user    string
}

func (s *privateListViewAuthoringStub) CreatePrivate(_ context.Context, user *models.UserSession, input *models.ListView) (*models.ListView, error) {
	s.user = user.ID
	created := *input
	created.ID = "view-1"
	if created.DeveloperName == nil {
		name := "My_Work_Items"
		created.DeveloperName = &name
	}
	s.created = &created
	return &created, nil
}

func (s *privateListViewAuthoringStub) UpdatePrivate(_ context.Context, _ *models.UserSession, _ string, _ models.ListViewPatch) (*models.ListView, error) {
	return nil, nil
}

func (s *privateListViewAuthoringStub) DeletePrivate(context.Context, *models.UserSession, string) error {
	return nil
}

func (s *privateListViewAuthoringStub) UpsertPrivateByDeveloperName(context.Context, *models.UserSession, string, string, *models.ListView, models.ListViewPatch) (*models.ListView, bool, error) {
	return nil, false, nil
}

type privateListViewPermissionStub struct{ allow bool }

func (s privateListViewPermissionStub) CheckObjectPermissionWithUser(context.Context, string, string, *models.UserSession) bool {
	return s.allow
}

func TestPrivateListViewServerCreatesThroughDelegatedTransactionOwner(t *testing.T) {
	reader := &privateListViewReaderStub{}
	authoring := &privateListViewAuthoringStub{}
	server, err := NewPrivateListViewServer(reader, authoring, privateListViewPermissionStub{allow: true})
	if err != nil {
		t.Fatal(err)
	}
	label := "My Work Items"
	stableName := "My_Work_Items"
	request := listviewsemantic.MutateRequest{
		Operation: listviewsemantic.MutationCreate,
		ObjectID:  "WorkItem",
		Patch: &listviewsemantic.Patch{
			Label: &label, StableName: &stableName,
			Fields: &listviewsemantic.FieldSelection{FieldIDs: []string{"Id", "DueDate"}},
		},
	}
	ctx := privateListViewTestContext(t)
	wireRequest, err := mapping.ToWireMutatePrivateListViewRequest(ctx, request)
	if err != nil {
		t.Fatal(err)
	}
	wireResponse, err := server.MutatePrivateListView(ctx, wireRequest)
	if err != nil {
		t.Fatal(err)
	}
	response, err := mapping.FromWireMutatePrivateListViewResponse(request, wireResponse)
	if err != nil {
		t.Fatal(err)
	}
	if authoring.user != "user-1" || authoring.created == nil || authoring.created.ObjectAPIName != "WorkItem" {
		t.Fatalf("authoring call = user %q, view %#v", authoring.user, authoring.created)
	}
	if response.View == nil || response.View.ViewID != "view-1" || response.View.StableName != stableName ||
		response.Receipts.CapabilityVersion != listviewsemantic.CapabilityName {
		t.Fatalf("response = %#v", response)
	}
}

func TestPrivateListViewServerListsOnlyThroughDelegatedReader(t *testing.T) {
	name := "My_Work_Items"
	reader := &privateListViewReaderStub{views: []*models.ListView{{
		ID: "view-1", ObjectAPIName: "WorkItem", Label: "My Work Items", Fields: []string{"Id"}, DeveloperName: &name,
	}}}
	server, err := NewPrivateListViewServer(reader, &privateListViewAuthoringStub{}, privateListViewPermissionStub{allow: true})
	if err != nil {
		t.Fatal(err)
	}
	request := listviewsemantic.ReadRequest{Selector: listviewsemantic.ReadList, ObjectID: "WorkItem"}
	ctx := privateListViewTestContext(t)
	wireRequest, err := mapping.ToWireReadPrivateListViewsRequest(ctx, request)
	if err != nil {
		t.Fatal(err)
	}
	wireResponse, err := server.ReadPrivateListViews(ctx, wireRequest)
	if err != nil {
		t.Fatal(err)
	}
	response, err := mapping.FromWireReadPrivateListViewsResponse(request, wireResponse)
	if err != nil {
		t.Fatal(err)
	}
	if reader.user != "user-1" || len(response.Views) != 1 || response.Views[0].ViewID != "view-1" {
		t.Fatalf("reader user = %q, response = %#v", reader.user, response)
	}
}

func TestPrivateListViewServerFailsClosedWithoutObjectPermission(t *testing.T) {
	server, err := NewPrivateListViewServer(&privateListViewReaderStub{}, &privateListViewAuthoringStub{}, privateListViewPermissionStub{})
	if err != nil {
		t.Fatal(err)
	}
	request := listviewsemantic.ReadRequest{Selector: listviewsemantic.ReadList, ObjectID: "WorkItem"}
	ctx := privateListViewTestContext(t)
	wireRequest, err := mapping.ToWireReadPrivateListViewsRequest(ctx, request)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := server.ReadPrivateListViews(ctx, wireRequest); status.Code(err) != codes.PermissionDenied {
		t.Fatalf("status = %v, want PermissionDenied", err)
	}
}

func privateListViewTestContext(t *testing.T) context.Context {
	t.Helper()
	base, cancel := context.WithTimeout(context.Background(), time.Second)
	t.Cleanup(cancel)
	ctx := context.WithValue(base, runtimeExecutionScopeContextKey{}, RuntimeExecutionScope{
		view: queryResponseTestView(t), capability: listviewsemantic.CapabilityName,
		receipts: recordsemantic.Receipts{
			ProtocolVersion: "1.0", CapabilityVersion: listviewsemantic.CapabilityName,
			CatalogRevision: 1, SchemaGeneration: 1, EngineGeneration: 1, SecurityEpoch: 3,
		},
	})
	return context.WithValue(ctx, trustedCallContextKey{}, newTrustedCall(delegationv1.Claims{
		DelegationID: "delegation-1", OrgID: testOrgID, PrincipalID: "user-1", SessionID: "session-1",
		CallerWorkloadID: testWorkloadID, AppInstallationID: testInstallationID,
		RuntimeAudience: "runtime-test", Capabilities: []string{listviewsemantic.CapabilityName},
		AuthEpoch: 2, SecurityEpoch: 3,
	}, models.UserSession{ID: "user-1"}))
}
