package recordviewv1

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"strings"
	"testing"
	"time"

	query "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/queryv1"
)

func testContext(t *testing.T) context.Context {
	t.Helper()
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	t.Cleanup(cancel)
	return ctx
}

func validReadRequest() ReadRequest {
	return ReadRequest{
		ObjectID:   "WorkItem",
		Selector:   Selector{Kind: SelectorRecordID, RecordID: "record-1"},
		Projection: ProjectionEnrichedView,
	}
}

func validReadResponse() ReadResponse {
	recordID := "record-1"
	ownerID := "principal-1"
	return ReadResponse{
		RecordID: recordID,
		Root: Record{ObjectID: "WorkItem", Fields: []Field{
			{Name: "Id", Value: Value{Kind: ValueString, String: &recordID}},
			{Name: "OwnerId", Value: Value{Kind: ValueReference, Reference: &ownerID}},
		}},
		ReferenceDisplays: []ReferenceDisplay{{
			SourceFieldID: "OwnerId", RelationshipID: "Owner", TargetObjectID: "Principal",
			TargetRecordID: ownerID, DisplayName: "Ada Example",
		}},
		Access:   &RecordAccess{Read: true, Update: true},
		Receipts: Receipts{ProtocolVersion: "1.0", CapabilityVersion: CapabilityName},
	}
}

func TestReadRequestClosedSelectorAndProjection(t *testing.T) {
	ctx := testContext(t)
	if err := ValidateReadRequest(ctx, validReadRequest()); err != nil {
		t.Fatal(err)
	}
	fieldRequest := ReadRequest{ObjectID: "WorkItem", Selector: Selector{Kind: SelectorFieldEqual, FieldID: "PublicKey", FieldValue: ""}, Projection: ProjectionRoot}
	if err := ValidateReadRequest(ctx, fieldRequest); err != nil {
		t.Fatalf("empty exact field value must remain representable: %v", err)
	}

	tests := []struct {
		name      string
		request   ReadRequest
		category  FailureCategory
		component InvalidComponent
		limit     LimitKind
	}{
		{name: "missing deadline", request: validReadRequest(), category: FailureInvalidRequest, component: InvalidComponentDeadline},
		{name: "unknown projection", request: func() ReadRequest { r := validReadRequest(); r.Projection = "GRAPH"; return r }(), category: FailureInvalidRequest, component: InvalidComponentOperation},
		{name: "mixed selector", request: func() ReadRequest { r := validReadRequest(); r.Selector.FieldID = "PublicKey"; return r }(), category: FailureInvalidRequest, component: InvalidComponentOperation},
		{name: "field one over", request: ReadRequest{ObjectID: "WorkItem", Selector: Selector{Kind: SelectorFieldEqual, FieldID: strings.Repeat("F", MaxFieldIdentifierBytes+1)}, Projection: ProjectionRoot}, category: FailureResourceExhausted, limit: LimitFieldID},
		{name: "invalid field value", request: ReadRequest{ObjectID: "WorkItem", Selector: Selector{Kind: SelectorFieldEqual, FieldID: "PublicKey", FieldValue: string([]byte{0xff})}, Projection: ProjectionRoot}, category: FailureInvalidRequest, component: InvalidComponentFieldValue},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			useCtx := ctx
			if tc.name == "missing deadline" {
				useCtx = context.Background()
			}
			var failure *Failure
			err := ValidateReadRequest(useCtx, tc.request)
			if !errors.As(err, &failure) || failure.Category != tc.category ||
				(tc.component != "" && failure.InvalidComponent != tc.component) ||
				(tc.limit != "" && failure.LimitKind != tc.limit) {
				t.Fatalf("failure = %#v", err)
			}
		})
	}
}

func TestReadResponseBindsFlatReferenceFactsToAuthorizedRoot(t *testing.T) {
	request := validReadRequest()
	response := validReadResponse()
	if err := ValidateReadResponse(request, response); err != nil {
		t.Fatal(err)
	}

	for name, mutate := range map[string]func(*ReadResponse){
		"wrong receipt":                          func(r *ReadResponse) { r.Receipts.CapabilityVersion = "runtime.records.other.v1" },
		"wrong selected record":                  func(r *ReadResponse) { r.RecordID = "record-2" },
		"unknown source field":                   func(r *ReadResponse) { r.ReferenceDisplays[0].SourceFieldID = "MissingId" },
		"target mismatch":                        func(r *ReadResponse) { r.ReferenceDisplays[0].TargetRecordID = "other" },
		"contradictory access":                   func(r *ReadResponse) { r.Access.Read = false },
		"product nesting forbidden in root mode": func(r *ReadResponse) {},
	} {
		t.Run(name, func(t *testing.T) {
			copy := response
			copy.Root.Fields = append([]Field(nil), response.Root.Fields...)
			copy.ReferenceDisplays = append([]ReferenceDisplay(nil), response.ReferenceDisplays...)
			access := *response.Access
			copy.Access = &access
			copyRequest := request
			if name == "product nesting forbidden in root mode" {
				copyRequest.Projection = ProjectionRoot
			}
			mutate(&copy)
			if err := ValidateReadResponse(copyRequest, copy); err == nil {
				t.Fatal("expected rejection")
			}
		})
	}
}

func TestReadWorkspaceAuthorityIsBoundedToRequestedSectionsAndObject(t *testing.T) {
	request := validReadRequest()
	request.Authority = &AuthoritySelection{Sections: []AuthoritySection{
		AuthoritySystemPermissions,
		AuthorityObjectPermissions,
		AuthorityFieldPermissions,
		AuthorityRecordTypePermissions,
	}}
	response := validReadResponse()
	response.Authority = &SelectedAuthority{
		SystemPermissions: []PermissionGrant{{PermissionID: "ApiEnabled", Granted: true}},
		ObjectPermissions: []ObjectPermission{{ResourceType: "WorkItem", CanRead: true, CanEdit: true}},
		FieldPermissions: []FieldPermission{
			{ResourceType: "WorkItem", FieldName: "Id", CanRead: true},
			{ResourceType: "WorkItem", FieldName: "OwnerId", CanRead: true, CanEdit: true},
		},
		RecordTypePermissions: []RecordTypePermission{{
			ResourceType: "WorkItem", RecordTypeID: "rt-master", Available: true, IsDefault: true,
		}},
	}
	if err := ValidateReadRequest(testContext(t), request); err != nil {
		t.Fatal(err)
	}
	if err := ValidateReadResponse(request, response); err != nil {
		t.Fatal(err)
	}

	for name, mutate := range map[string]func(*ReadResponse){
		"unrequested section": func(r *ReadResponse) {
			r.Authority.CustomPermissions = []PermissionGrant{{PermissionID: "Unexpected", Granted: true}}
		},
		"other object": func(r *ReadResponse) {
			r.Authority.FieldPermissions[0].ResourceType = "Other"
		},
		"unsorted fields": func(r *ReadResponse) {
			r.Authority.FieldPermissions[0], r.Authority.FieldPermissions[1] = r.Authority.FieldPermissions[1], r.Authority.FieldPermissions[0]
		},
		"hidden default record type": func(r *ReadResponse) {
			r.Authority.RecordTypePermissions[0].Available = false
		},
		"control in permission grant": func(r *ReadResponse) {
			r.Authority.SystemPermissions[0].PermissionID += "\n"
		},
		"case-folded duplicate field": func(r *ReadResponse) {
			r.Authority.FieldPermissions = append(r.Authority.FieldPermissions, FieldPermission{
				ResourceType: "WorkItem", FieldName: "id", CanRead: true,
			})
		},
		"duplicate record type ID": func(r *ReadResponse) {
			r.Authority.RecordTypePermissions = append(r.Authority.RecordTypePermissions, RecordTypePermission{
				ResourceType: "WorkItem", RecordTypeID: "rt-master", Available: true,
			})
		},
	} {
		t.Run(name, func(t *testing.T) {
			copy := response
			selected := *response.Authority
			selected.SystemPermissions = append([]PermissionGrant(nil), response.Authority.SystemPermissions...)
			selected.ObjectPermissions = append([]ObjectPermission(nil), response.Authority.ObjectPermissions...)
			selected.FieldPermissions = append([]FieldPermission(nil), response.Authority.FieldPermissions...)
			selected.RecordTypePermissions = append([]RecordTypePermission(nil), response.Authority.RecordTypePermissions...)
			copy.Authority = &selected
			mutate(&copy)
			if err := ValidateReadResponse(request, copy); err == nil {
				t.Fatal("expected rejection")
			}
		})
	}
}

func TestReadWorkspaceAuthoritySelectionIsClosedSortedAndUnique(t *testing.T) {
	for name, sections := range map[string][]AuthoritySection{
		"unknown":   {AuthoritySection("OPEN")},
		"duplicate": {AuthorityObjectPermissions, AuthorityObjectPermissions},
		"unsorted":  {AuthorityFieldPermissions, AuthoritySystemPermissions},
	} {
		t.Run(name, func(t *testing.T) {
			request := validReadRequest()
			request.Authority = &AuthoritySelection{Sections: sections}
			if err := ValidateReadRequest(testContext(t), request); err == nil {
				t.Fatal("expected rejection")
			}
		})
	}
}

func TestAttachmentResolutionIsBoundedOrderedAndOpaque(t *testing.T) {
	ctx := testContext(t)
	request := ResolveAttachmentSourcesRequest{
		ParentObjectID: "WorkItem", ParentRecordID: "record-1",
		ResolverID: "legacy_attachment", SourceValues: []string{"source-1", "source-2"},
	}
	response := ResolveAttachmentSourcesResponse{
		Results:  []AttachmentSourceResult{{Index: 0, Outcome: AttachmentSourceUnavailable}, {Index: 1, Outcome: AttachmentSourceResolved, FileID: "file-1", FileName: "example.txt"}},
		Receipts: Receipts{ProtocolVersion: "1.0", CapabilityVersion: CapabilityName},
	}
	if err := ValidateResolveRequest(ctx, request); err != nil {
		t.Fatal(err)
	}
	if err := ValidateResolveResponse(request, response); err != nil {
		t.Fatal(err)
	}
	encoded, err := json.Marshal(response.Results[0])
	if err != nil {
		t.Fatal(err)
	}
	for _, leak := range [][]byte{[]byte("FORBIDDEN"), []byte("UNAUTHORIZED"), []byte("file_id"), []byte("file_name")} {
		if bytes.Contains(encoded, leak) {
			t.Fatalf("opaque unavailable result leaked %q: %s", leak, encoded)
		}
	}

	oneOver := request
	oneOver.SourceValues = make([]string, MaxAttachmentSources+1)
	for i := range oneOver.SourceValues {
		oneOver.SourceValues[i] = "value"
	}
	var failure *Failure
	if err := ValidateResolveRequest(ctx, oneOver); !errors.As(err, &failure) || failure.LimitKind != LimitOperationCount {
		t.Fatalf("one-over failure = %#v", err)
	}
}

func TestReadResponseBindsAuthorizedAttachmentFieldWithoutProductGrammar(t *testing.T) {
	request := ReadRequest{ObjectID: "WorkItem", Selector: Selector{Kind: SelectorRecordID, RecordID: "record-1"}, Projection: ProjectionEnrichedView}
	value := "opaque-source-value"
	response := ReadResponse{
		RecordID:               "record-1",
		Root:                   Record{ObjectID: "WorkItem", Fields: []Field{{Name: "DocumentLink", Value: Value{Kind: ValueString, String: &value}}}},
		AttachmentSourceFields: []AttachmentSourceField{{SourceFieldID: "DocumentLink", ResolverID: "legacy_attachment"}},
		Receipts:               Receipts{ProtocolVersion: "1.0", CapabilityVersion: CapabilityName},
	}
	if err := ValidateReadResponse(request, response); err != nil {
		t.Fatal(err)
	}
	response.AttachmentSourceFields[0].SourceFieldID = "HiddenField"
	if err := ValidateReadResponse(request, response); err == nil {
		t.Fatal("attachment resolver binding for an absent/FLS-hidden field was accepted")
	}
}

func TestReadResponseRejectsWrongObjectAndDecimalExpansion(t *testing.T) {
	request := ReadRequest{ObjectID: "WorkItem", Selector: Selector{Kind: SelectorRecordID, RecordID: "record-1"}, Projection: ProjectionRoot}
	response := ReadResponse{
		RecordID: "record-1", Root: Record{ObjectID: "OtherItem"},
		Receipts: Receipts{ProtocolVersion: "1.0", CapabilityVersion: CapabilityName},
	}
	if err := ValidateReadResponse(request, response); err == nil {
		t.Fatal("response for another object was accepted")
	}
	response.Root.ObjectID = request.ObjectID
	response.Root.Fields = []Field{{Name: "Amount", Value: Value{Kind: ValueDecimal, Decimal: &Decimal{Coefficient: "1", Scale: MaxSemanticResponseBytes}}}}
	var failure *Failure
	if err := ValidateReadResponse(request, response); !errors.As(err, &failure) || failure.LimitKind != LimitSemanticResponse {
		t.Fatalf("decimal expansion failure = %#v", err)
	}
}

func TestReadResponsePreservesSharedCarrierLimitFailure(t *testing.T) {
	request := ReadRequest{ObjectID: "WorkItem", Selector: Selector{Kind: SelectorRecordID, RecordID: "record-1"}, Projection: ProjectionRoot}
	nested := Record{}
	for depth := 0; depth <= 8; depth++ {
		child := nested
		nested = Record{Fields: []Field{{Name: "", Value: Value{Kind: ValueObject, Object: &child}}}}
	}
	response := ReadResponse{
		RecordID: "record-1", Root: Record{ObjectID: "WorkItem", Fields: []Field{{Name: "Nested", Value: Value{Kind: ValueObject, Object: &nested}}}},
		Receipts: Receipts{ProtocolVersion: "1.0", CapabilityVersion: CapabilityName},
	}
	var failure *Failure
	if err := ValidateReadResponse(request, response); !errors.As(err, &failure) || failure.Category != FailureResourceExhausted || failure.LimitKind != query.LimitQueryDepth {
		t.Fatalf("nested carrier failure = %#v", err)
	}
}

type vectorSuite struct {
	SchemaVersion string `json:"schema_version"`
	ReadVectors   []struct {
		Name     string       `json:"name"`
		Covers   []string     `json:"covers"`
		Request  ReadRequest  `json:"request"`
		Response ReadResponse `json:"response"`
	} `json:"read_vectors"`
	ResolveVectors []struct {
		Name     string                           `json:"name"`
		Covers   []string                         `json:"covers"`
		Request  ResolveAttachmentSourcesRequest  `json:"request"`
		Response ResolveAttachmentSourcesResponse `json:"response"`
	} `json:"resolve_vectors"`
	Failures []struct {
		Name     string           `json:"name"`
		Category FailureCategory  `json:"category"`
		Retry    RetryDisposition `json:"retry"`
	} `json:"failures"`
}

func TestNeutralConformanceVectors(t *testing.T) {
	data := ConformanceVectorsJSON()
	for _, forbidden := range [][]byte{[]byte("salesforce"), []byte("sobject"), []byte("sfcrm"), []byte("contentdistribution"), []byte("soql")} {
		if bytes.Contains(bytes.ToLower(data), forbidden) {
			t.Fatalf("product vocabulary %q leaked into neutral vectors", forbidden)
		}
	}
	decoder := json.NewDecoder(bytes.NewReader(data))
	decoder.DisallowUnknownFields()
	var suite vectorSuite
	if err := decoder.Decode(&suite); err != nil {
		t.Fatal(err)
	}
	if suite.SchemaVersion != "nexuscrm.runtime.record-view.conformance/v1" || len(suite.ReadVectors) == 0 || len(suite.ResolveVectors) == 0 || len(suite.Failures) == 0 {
		t.Fatalf("incomplete suite: %#v", suite)
	}
	ctx := testContext(t)
	seen := map[string]struct{}{}
	for _, vector := range suite.ReadVectors {
		if vector.Name == "" || len(vector.Covers) == 0 {
			t.Fatalf("incomplete read vector: %#v", vector)
		}
		if _, duplicate := seen[vector.Name]; duplicate {
			t.Fatalf("duplicate vector %q", vector.Name)
		}
		seen[vector.Name] = struct{}{}
		if err := ValidateReadRequest(ctx, vector.Request); err != nil {
			t.Fatalf("%s request: %v", vector.Name, err)
		}
		if err := ValidateReadResponse(vector.Request, vector.Response); err != nil {
			t.Fatalf("%s response: %v", vector.Name, err)
		}
	}
	for _, vector := range suite.ResolveVectors {
		if vector.Name == "" || len(vector.Covers) == 0 {
			t.Fatalf("incomplete resolve vector: %#v", vector)
		}
		if _, duplicate := seen[vector.Name]; duplicate {
			t.Fatalf("duplicate vector %q", vector.Name)
		}
		seen[vector.Name] = struct{}{}
		if err := ValidateResolveRequest(ctx, vector.Request); err != nil {
			t.Fatalf("%s request: %v", vector.Name, err)
		}
		if err := ValidateResolveResponse(vector.Request, vector.Response); err != nil {
			t.Fatalf("%s response: %v", vector.Name, err)
		}
	}
	for _, vector := range suite.Failures {
		if vector.Name == "" || !ValidTopLevelFailure(vector.Category) || RetryDispositionFor(vector.Category) != vector.Retry {
			t.Fatalf("invalid failure vector: %#v", vector)
		}
	}
}
