package recyclebinmutationv1

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

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

func TestRequestRequiresBoundedRecordIdentityAndDeadline(t *testing.T) {
	ctx := mutationContext(t)
	zero := uint64(0)
	if err := ValidateRequest(ctx, Request{RecordID: "row-1"}); err != nil {
		t.Fatalf("valid request = %v", err)
	}
	for name, tc := range map[string]struct {
		ctx       context.Context
		request   Request
		category  FailureCategory
		component InvalidComponent
		limit     LimitKind
	}{
		"deadline":   {ctx: context.Background(), request: Request{RecordID: "row-1"}, category: FailureInvalidRequest, component: InvalidComponentDeadline},
		"blank":      {ctx: ctx, request: Request{}, category: FailureInvalidRequest, component: InvalidComponentRecordID},
		"whitespace": {ctx: ctx, request: Request{RecordID: " \t\n"}, category: FailureInvalidRequest, component: InvalidComponentRecordID},
		"revision":   {ctx: ctx, request: Request{RecordID: "row-1", MinCatalogRevision: &zero}, category: FailureInvalidRequest, component: InvalidComponentOperation},
		"oversize":   {ctx: ctx, request: Request{RecordID: strings.Repeat("r", MaxRecordIdentifierBytes+1)}, category: FailureResourceExhausted, limit: LimitRecordID},
	} {
		t.Run(name, func(t *testing.T) {
			var failure *Failure
			err := ValidateRequest(tc.ctx, tc.request)
			if !errors.As(err, &failure) || failure.Category != tc.category || failure.InvalidComponent != tc.component || failure.LimitKind != tc.limit {
				t.Fatalf("failure = %#v", err)
			}
		})
	}
	for _, recordID := range []string{" row-1", "row-1\n"} {
		if err := ValidateRequest(ctx, Request{RecordID: recordID}); err != nil {
			t.Fatalf("listed record identity %q must remain mutable: %v", recordID, err)
		}
	}
}

func TestResponseIsCapabilityAndFreshnessBound(t *testing.T) {
	revision := uint64(7)
	request := Request{RecordID: "row-1", MinCatalogRevision: &revision}
	response := Response{Receipts: Receipts{ProtocolVersion: "1.0", CapabilityVersion: CapabilityName, CatalogRevision: revision}}
	if err := ValidateResponse(request, response); err != nil {
		t.Fatalf("valid response = %v", err)
	}
	response.Receipts.CapabilityVersion = "runtime.records.other.v1"
	if err := ValidateResponse(request, response); err == nil {
		t.Fatal("wrong capability receipt was accepted")
	}
	response.Receipts.CapabilityVersion = CapabilityName
	response.Receipts.CatalogRevision = revision - 1
	if err := ValidateResponse(request, response); err == nil {
		t.Fatal("stale response was accepted")
	}
	zero := uint64(0)
	request.MinCatalogRevision = &zero
	response.Receipts.CatalogRevision = revision
	if err := ValidateResponse(request, response); err == nil {
		t.Fatal("response paired with an invalid request was accepted")
	}
}

func TestMutationFailureSurfaceIncludesCurrentEngineOutcomes(t *testing.T) {
	for _, category := range []FailureCategory{
		FailureInvalidRequest, FailureUnauthenticated, FailureForbidden,
		FailureMissing, FailureConflict, FailureCanceled, FailureDeadlineExceeded,
		FailureResourceExhausted, FailureIncompatible, FailureStaleState,
		FailureUnavailable, FailureInternal,
	} {
		if !ValidTopLevelFailure(category) {
			t.Fatalf("category %s is not valid", category)
		}
	}
}
