package pagelayoutresolutionv1

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

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

func TestObjectListFieldsContractPreservesOrderAndEmptyCollections(t *testing.T) {
	request := ObjectListFieldsRequest{ObjectIDs: []string{"Account", "Contact"}}
	if err := ValidateObjectListFieldsRequest(listFieldsTestContext(t), request); err != nil {
		t.Fatal(err)
	}
	response := ObjectListFieldsResponse{
		Objects: []ObjectListFields{
			{ObjectID: "Account", OrderedFieldIDs: []string{"Name", "OwnerId"}},
			{ObjectID: "Contact", OrderedFieldIDs: []string{}},
		},
		Receipts: Receipts{ProtocolVersion: "1.0", CapabilityVersion: CapabilityName},
	}
	if err := ValidateObjectListFieldsResponse(request, response); err != nil {
		t.Fatal(err)
	}
	response.Objects[0].OrderedFieldIDs = []string{"Name", "name"}
	if err := ValidateObjectListFieldsResponse(request, response); err == nil {
		t.Fatal("accepted duplicate field identity")
	}
}

func TestObjectListFieldsRequestUsesSharedContextValidation(t *testing.T) {
	ctx := fixedDeadlineContext{Context: context.Background(), deadline: time.Now().Add(-time.Second)}
	assertObjectListFieldsFailure(t,
		ValidateObjectListFieldsRequest(ctx, ObjectListFieldsRequest{ObjectIDs: []string{"Account"}}),
		FailureInvalidRequest, InvalidComponentDeadline, "", 0)
}

func TestObjectListFieldsRequestRejectsFoldedDuplicates(t *testing.T) {
	request := ObjectListFieldsRequest{ObjectIDs: []string{"Account", "account"}}
	assertObjectListFieldsFailure(t, ValidateObjectListFieldsRequest(listFieldsTestContext(t), request),
		FailureInvalidRequest, InvalidComponentOperation, "", 0)
}

func TestObjectListFieldsRequestClassifiesSelectorLimit(t *testing.T) {
	objectIDs := make([]string, MaxObjectSelectors+1)
	for index := range objectIDs {
		objectIDs[index] = fmt.Sprintf("Object%04d", index)
	}
	assertObjectListFieldsFailure(t,
		ValidateObjectListFieldsRequest(listFieldsTestContext(t), ObjectListFieldsRequest{ObjectIDs: objectIDs}),
		FailureResourceExhausted, "", LimitRecordCount, MaxObjectSelectors)
}

func TestObjectListFieldsResponseValidatesRequestShape(t *testing.T) {
	request := ObjectListFieldsRequest{ObjectIDs: []string{"Contact", "Account"}}
	response := ObjectListFieldsResponse{
		Objects: []ObjectListFields{
			{ObjectID: "Contact", OrderedFieldIDs: []string{}},
			{ObjectID: "Account", OrderedFieldIDs: []string{}},
		},
		Receipts: Receipts{ProtocolVersion: "1.0", CapabilityVersion: CapabilityName},
	}
	assertObjectListFieldsFailure(t, ValidateObjectListFieldsResponse(request, response),
		FailureInternal, "", "", 0)
}

func TestObjectListFieldsResponseRejectsMissingCollection(t *testing.T) {
	request := ObjectListFieldsRequest{ObjectIDs: []string{"Account"}}
	response := ObjectListFieldsResponse{
		Objects:  []ObjectListFields{{ObjectID: "Account"}},
		Receipts: Receipts{ProtocolVersion: "1.0", CapabilityVersion: CapabilityName},
	}
	assertObjectListFieldsFailure(t, ValidateObjectListFieldsResponse(request, response),
		FailureInternal, "", "", 0)
}

func TestObjectListFieldsResponseClassifiesFieldLimits(t *testing.T) {
	t.Run("per object", func(t *testing.T) {
		request := ObjectListFieldsRequest{ObjectIDs: []string{"Account"}}
		response := ObjectListFieldsResponse{
			Objects: []ObjectListFields{{
				ObjectID: "Account", OrderedFieldIDs: make([]string, MaxListFieldsPerObject+1),
			}},
			Receipts: Receipts{ProtocolVersion: "1.0", CapabilityVersion: CapabilityName},
		}
		assertObjectListFieldsFailure(t, ValidateObjectListFieldsResponse(request, response),
			FailureResourceExhausted, "", LimitFieldCount, MaxListFieldsPerObject)
	})

	t.Run("aggregate", func(t *testing.T) {
		objectCount := MaxResolutionProjectionEntries/MaxListFieldsPerObject + 1
		request := ObjectListFieldsRequest{ObjectIDs: make([]string, objectCount)}
		response := ObjectListFieldsResponse{
			Objects:  make([]ObjectListFields, objectCount),
			Receipts: Receipts{ProtocolVersion: "1.0", CapabilityVersion: CapabilityName},
		}
		for index := range request.ObjectIDs {
			objectID := fmt.Sprintf("Object%04d", index)
			fieldIDs := make([]string, MaxListFieldsPerObject)
			for fieldIndex := range fieldIDs {
				fieldIDs[fieldIndex] = fmt.Sprintf("Field%04d", fieldIndex)
			}
			request.ObjectIDs[index] = objectID
			response.Objects[index] = ObjectListFields{
				ObjectID: objectID, OrderedFieldIDs: fieldIDs,
			}
		}
		assertObjectListFieldsFailure(t, ValidateObjectListFieldsResponse(request, response),
			FailureResourceExhausted, "", LimitFieldCount, MaxResolutionProjectionEntries)
	})
}

type fixedDeadlineContext struct {
	context.Context
	deadline time.Time
}

func (ctx fixedDeadlineContext) Deadline() (time.Time, bool) { return ctx.deadline, true }

func assertObjectListFieldsFailure(
	t *testing.T,
	err error,
	wantCategory FailureCategory,
	wantInvalid InvalidComponent,
	wantLimit LimitKind,
	wantMaximum int,
) {
	t.Helper()
	var failure *Failure
	if !errors.As(err, &failure) || failure.Category != wantCategory ||
		failure.InvalidComponent != wantInvalid || failure.LimitKind != wantLimit ||
		failure.Maximum != uint64(wantMaximum) {
		t.Fatalf("error = %#v, want %s/%s/%s/%d", err, wantCategory, wantInvalid, wantLimit, wantMaximum)
	}
}
