package listviewv1

import (
	"context"
	"errors"
	"testing"
	"time"

	common "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
)

func TestPrivateListViewRequestShapesAndDelegatedAuthority(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	label := "My accounts"
	fields := &FieldSelection{FieldIDs: []string{"Name", "OwnerId"}}

	tests := []struct {
		name    string
		request MutateRequest
		want    common.InvalidComponent
	}{
		{name: "create", request: MutateRequest{Operation: MutationCreate, ObjectID: "Account", Patch: &Patch{Label: &label, Fields: fields}}},
		{name: "update", request: MutateRequest{Operation: MutationUpdate, ObjectID: "Account", ViewID: "view-1", Patch: &Patch{Fields: &FieldSelection{FieldIDs: []string{}}}}},
		{name: "delete", request: MutateRequest{Operation: MutationDelete, ObjectID: "Account", ViewID: "view-1"}},
		{name: "create requires label", request: MutateRequest{Operation: MutationCreate, ObjectID: "Account", Patch: &Patch{}}, want: common.InvalidComponentCommandData},
		{name: "delete rejects patch", request: MutateRequest{Operation: MutationDelete, ObjectID: "Account", ViewID: "view-1", Patch: &Patch{}}, want: common.InvalidComponentCommandData},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			err := ValidateMutateRequest(ctx, test.request)
			if test.want == "" && err != nil {
				t.Fatal(err)
			}
			if test.want != "" {
				var failure *Failure
				if !errors.As(err, &failure) || failure.InvalidComponent != test.want {
					t.Fatalf("failure = %#v", err)
				}
			}
		})
	}
}

func TestPrivateListViewReadAndResponseAreClosedBySelector(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	list := ReadRequest{Selector: ReadList, ObjectID: "Account"}
	if err := ValidateReadRequest(ctx, list); err != nil {
		t.Fatal(err)
	}
	views := []View{validTestView("view-2"), validTestView("view-1")}
	SortViews(views)
	if err := ValidateReadResponse(list, ReadResponse{Views: views, Receipts: testReceipts()}); err != nil {
		t.Fatal(err)
	}
	describe := ReadRequest{Selector: ReadDescribe, ObjectID: "Account", ViewID: "view-1"}
	view := validTestView("view-1")
	if err := ValidateReadResponse(describe, ReadResponse{View: &view, Receipts: testReceipts()}); err != nil {
		t.Fatal(err)
	}
	if err := ValidateReadResponse(describe, ReadResponse{Views: []View{view}, Receipts: testReceipts()}); err == nil {
		t.Fatal("DESCRIBE accepted LIST response shape")
	}
}

func TestPrivateListViewValidationRejectsAuthorityLikeOrInvalidProjectionFacts(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	if err := ValidateReadRequest(ctx, ReadRequest{Selector: ReadList, ObjectID: "Account", ViewID: "owner-probe"}); err == nil {
		t.Fatal("LIST accepted a view identity probe")
	}
	request := ReadRequest{Selector: ReadList, ObjectID: "Account"}
	duplicateFields := validTestView("view-1")
	duplicateFields.FieldIDs = []string{"Name", "Name"}
	if err := ValidateReadResponse(request, ReadResponse{Views: []View{duplicateFields}, Receipts: testReceipts()}); err == nil {
		t.Fatal("duplicate field projection accepted")
	}
}

func TestPrivateListViewFreshnessFenceAndAtomicSortPatch(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	minimum := uint64(2)
	read := ReadRequest{Selector: ReadList, ObjectID: "Account", MinCatalogRevision: &minimum}
	stale := testReceipts()
	if err := ValidateReadResponse(read, ReadResponse{Views: []View{}, Receipts: stale}); err == nil {
		t.Fatal("read response below the requested Catalog revision was accepted")
	}
	stale.CatalogRevision = minimum
	if err := ValidateReadResponse(read, ReadResponse{Views: []View{}, Receipts: stale}); err != nil {
		t.Fatal(err)
	}

	label := "Updated"
	field := "Name"
	direction := SortAscending
	mutation := MutateRequest{
		Operation: MutationUpdate, ObjectID: "Account", ViewID: "view-1",
		MinCatalogRevision: &minimum, Patch: &Patch{Label: &label, SortFieldID: &field},
	}
	if err := ValidateMutateRequest(ctx, mutation); err == nil {
		t.Fatal("half sort PATCH was accepted")
	}
	mutation.Patch.SortDirection = &direction
	if err := ValidateMutateRequest(ctx, mutation); err != nil {
		t.Fatal(err)
	}
	view := validTestView("view-1")
	view.Label, view.SortFieldID, view.SortDirection = label, field, direction
	if err := ValidateMutateResponse(mutation, MutateResponse{View: &view, Receipts: testReceipts()}); err == nil {
		t.Fatal("mutation response below the requested Catalog revision was accepted")
	}
}

func validTestView(id string) View {
	return View{ViewID: id, ObjectID: "Account", Label: "My accounts", FieldIDs: []string{"Name"}, StableName: "My_Accounts"}
}

func testReceipts() Receipts {
	return Receipts{ProtocolVersion: "1.0", CatalogRevision: 1, SchemaGeneration: 2, EngineGeneration: 3, SecurityEpoch: 4, CapabilityVersion: CapabilityName}
}
