package mapping

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"testing"
	"time"

	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/pagelayoutresolutionv1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/envelope"
	"google.golang.org/protobuf/proto"
)

func TestPageLayoutResolutionWireRoundTripPreservesBoundedAuthority(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	revision := uint64(19)
	recordType := "012000000000001AAA"
	request := semantic.Request{ObjectID: "Account", RecordTypeIDs: []string{recordType}, MinCatalogRevision: &revision}
	wireRequest, err := ToWireResolvePageLayoutRequest(ctx, request)
	if err != nil {
		t.Fatal(err)
	}
	roundRequest, err := FromWireResolvePageLayoutRequest(ctx, wireRequest)
	if err != nil || roundRequest.ObjectID != request.ObjectID || len(roundRequest.RecordTypeIDs) != 1 || roundRequest.RecordTypeIDs[0] != recordType {
		t.Fatalf("request = %#v, %v", roundRequest, err)
	}
	response := semantic.Response{
		ObjectID:   request.ObjectID,
		Selections: []semantic.Selection{{RecordTypeID: &recordType, LayoutID: "layout-account"}},
		Layouts: []semantic.ResolvedLayout{{
			LayoutID: "layout-account", DocumentKind: semantic.PageLayoutDocumentKind,
			DocumentFormatVersion: semantic.PageLayoutDocumentFormatVersion,
			LayoutDocument:        []byte(`{"id":"layout-account","objectApiName":"Account"}`),
			RelatedLists: []semantic.RelatedListResolution{{
				DisplayObjectID: "Attachment", DisplayLookupField: "ParentId",
				Query:  semantic.ResolvedQueryTarget{ObjectID: "ContentDocumentLink", LookupField: "LinkedEntityId"},
				Detail: semantic.ResolvedDetailTarget{ObjectID: "ContentDocument"},
				List:   semantic.ResolvedListTarget{ObjectID: "ContentDocumentLink", FilterField: "LinkedEntityId"},
				Create: semantic.ResolvedCreateTarget{Kind: "upload", PrefillMappings: []semantic.FieldMapping{{TargetFieldID: "LinkedEntityId", SourceFieldID: "Id"}}},
			}},
		}},
		Receipts: semantic.Receipts{ProtocolVersion: "1.0", CapabilityVersion: semantic.CapabilityName, CatalogRevision: revision, SecurityEpoch: 5},
	}
	wireResponse, err := ToWireResolvePageLayoutResponse(request, response)
	if err != nil {
		t.Fatal(err)
	}
	roundResponse, err := FromWireResolvePageLayoutResponse(request, wireResponse)
	if err != nil || len(roundResponse.Layouts) != 1 || roundResponse.Layouts[0].LayoutID != response.Layouts[0].LayoutID ||
		len(roundResponse.Layouts[0].RelatedLists) != 1 || len(roundResponse.Layouts[0].RelatedLists[0].Create.PrefillMappings) != 1 {
		t.Fatalf("response = %#v, %v", roundResponse, err)
	}
}

func TestPageLayoutResolutionWirePreservesEveryOwnedLimit(t *testing.T) {
	limits := []struct {
		name    string
		kind    semantic.LimitKind
		maximum uint64
	}{
		{"record type selectors", semantic.LimitRecordCount, semantic.MaxRecordTypeSelectors},
		{"object selectors", semantic.LimitRecordCount, semantic.MaxObjectSelectors},
		{"related list resolutions", semantic.LimitRecordCount, semantic.MaxRelatedListResolutions},
		{"prefill mappings", semantic.LimitRecordCount, semantic.MaxPrefillMappingsPerResolution},
		{"projection entries", semantic.LimitRecordCount, semantic.MaxResolutionProjectionEntries},
		{"list fields per object", semantic.LimitFieldCount, semantic.MaxListFieldsPerObject},
		{"list field projection entries", semantic.LimitFieldCount, semantic.MaxResolutionProjectionEntries},
		{"layout document", semantic.LimitSemanticResponse, semantic.MaxLayoutDocumentBytes},
		{"semantic response", semantic.LimitSemanticResponse, semantic.MaxSemanticResponseBytes},
	}
	for _, limit := range limits {
		t.Run(limit.name, func(t *testing.T) {
			source := &semantic.Failure{
				Category: semantic.FailureResourceExhausted, LimitKind: limit.kind, Maximum: limit.maximum,
			}
			decoded := FromPlatformStatusError(
				ToPlatformStatusError(source, semantic.CapabilityName),
				semantic.CapabilityName,
			)
			var failure *semantic.Failure
			if !errors.As(decoded, &failure) || failure.Category != source.Category ||
				failure.LimitKind != source.LimitKind || failure.Maximum != source.Maximum {
				t.Fatalf("decoded failure = %#v", decoded)
			}
		})
	}
}

func TestPageLayoutResolutionWireRejectsForeignLimit(t *testing.T) {
	decoded := FromPlatformStatusError(
		ToPlatformStatusError(&semantic.Failure{
			Category: semantic.FailureResourceExhausted, LimitKind: semantic.LimitRecordCount, Maximum: 1,
		}, semantic.CapabilityName),
		semantic.CapabilityName,
	)
	var failure *semantic.Failure
	if !errors.As(decoded, &failure) || failure.Category != semantic.FailureInternal {
		t.Fatalf("decoded foreign failure = %#v", decoded)
	}
}

func TestPageLayoutResolutionMaximumProjectionFitsWireEnvelope(t *testing.T) {
	request := semantic.Request{ObjectID: "O"}
	response := semantic.Response{
		ObjectID: "O",
		Receipts: semantic.Receipts{
			ProtocolVersion: "1.0", CapabilityVersion: semantic.CapabilityName,
			CatalogRevision: 1, SecurityEpoch: 1,
		},
	}
	for layoutIndex := 0; layoutIndex < 8; layoutIndex++ {
		recordTypeID := fmt.Sprintf("rt-%02d", layoutIndex)
		layoutID := fmt.Sprintf("layout-%02d", layoutIndex)
		request.RecordTypeIDs = append(request.RecordTypeIDs, recordTypeID)
		response.Selections = append(response.Selections, semantic.Selection{
			RecordTypeID: &request.RecordTypeIDs[len(request.RecordTypeIDs)-1], LayoutID: layoutID,
		})
		layout := semantic.ResolvedLayout{
			LayoutID: layoutID, DocumentKind: semantic.PageLayoutDocumentKind,
			DocumentFormatVersion: semantic.PageLayoutDocumentFormatVersion,
			LayoutDocument:        []byte(`{"payload":"` + strings.Repeat("x", 300*1024) + `"}`),
		}
		for relatedIndex := 0; relatedIndex < semantic.MaxRelatedListResolutions; relatedIndex++ {
			fieldID := fmt.Sprintf("F%04d", relatedIndex)
			layout.RelatedLists = append(layout.RelatedLists, semantic.RelatedListResolution{
				DisplayObjectID: "O", DisplayLookupField: fieldID,
				Query:  semantic.ResolvedQueryTarget{ObjectID: "O", LookupField: fieldID},
				Detail: semantic.ResolvedDetailTarget{ObjectID: "O"},
				List:   semantic.ResolvedListTarget{ObjectID: "O", FilterField: fieldID},
				Create: semantic.ResolvedCreateTarget{Kind: "none"},
			})
		}
		response.Layouts = append(response.Layouts, layout)
	}
	wireResponse, err := ToWireResolvePageLayoutResponse(request, response)
	if err != nil {
		t.Fatalf("maximum projection mapping = %v", err)
	}
	if size := proto.Size(wireResponse); size > envelope.MaxResponseWireBytes {
		t.Fatalf("maximum projection wire size = %d, cap = %d", size, envelope.MaxResponseWireBytes)
	}
}
