package identityv1

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

func validRequest() Request {
	return Request{AppInstallationID: "installation-1", RequestedCapabilities: []string{"runtime.records.read-selected-root.v1"}}
}

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

func TestValidateRequestAcceptsOnlyCanonicalCallerSelectors(t *testing.T) {
	if err := ValidateRequest(deadlineContext(t), validRequest()); err != nil {
		t.Fatal(err)
	}
	for _, test := range []struct {
		name      string
		ctx       context.Context
		edit      func(*Request)
		category  FailureCategory
		component InvalidComponent
		limit     LimitKind
	}{
		{name: "missing deadline", ctx: context.Background(), category: FailureInvalidRequest, component: InvalidComponentDeadline},
		{name: "missing installation", edit: func(r *Request) { r.AppInstallationID = "" }, category: FailureInvalidRequest, component: InvalidComponentAppInstallationID},
		{name: "oversized installation", edit: func(r *Request) { r.AppInstallationID = strings.Repeat("i", MaxAppInstallationIdentifierBytes+1) }, category: FailureResourceExhausted, limit: LimitAppInstallationID},
		{name: "empty capabilities", edit: func(r *Request) { r.RequestedCapabilities = nil }, category: FailureInvalidRequest, component: InvalidComponentCapability},
		{name: "unsorted capabilities", edit: func(r *Request) { r.RequestedCapabilities = []string{"runtime.z.v1", "runtime.a.v1"} }, category: FailureInvalidRequest, component: InvalidComponentCapability},
		{name: "duplicate capabilities", edit: func(r *Request) { r.RequestedCapabilities = []string{"runtime.a.v1", "runtime.a.v1"} }, category: FailureInvalidRequest, component: InvalidComponentCapability},
		{name: "too many capabilities", edit: func(r *Request) { r.RequestedCapabilities = make([]string, MaxRequestedCapabilities+1) }, category: FailureResourceExhausted, limit: LimitCapabilityCount},
		{name: "oversized capability", edit: func(r *Request) {
			r.RequestedCapabilities = []string{"runtime." + strings.Repeat("a", MaxCapabilityIdentifierBytes)}
		}, category: FailureResourceExhausted, limit: LimitCapabilityID},
	} {
		t.Run(test.name, func(t *testing.T) {
			request := validRequest()
			if test.edit != nil {
				test.edit(&request)
			}
			ctx := test.ctx
			if ctx == nil {
				ctx = deadlineContext(t)
			}
			err := ValidateRequest(ctx, request)
			var failure *Failure
			if !errors.As(err, &failure) || failure.Category != test.category || failure.InvalidComponent != test.component || failure.LimitKind != test.limit {
				t.Fatalf("failure = %#v, %v", failure, err)
			}
		})
	}
}

func TestValidateRequestPreservesNativeCancellation(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	cancel()
	var failure *Failure
	if err := ValidateRequest(ctx, validRequest()); !errors.As(err, &failure) || failure.Category != FailureCanceled {
		t.Fatalf("failure = %#v, %v", failure, err)
	}
}

func TestValidateOpaqueResponse(t *testing.T) {
	if err := ValidateResponse(Response{OpaqueDelegation: "header.payload.signature", ExpiresAtUnixSecond: 1}); err != nil {
		t.Fatal(err)
	}
	for _, response := range []Response{{}, {OpaqueDelegation: "x", ExpiresAtUnixSecond: 0}, {OpaqueDelegation: strings.Repeat("x", MaxOpaqueDelegationBytes+1), ExpiresAtUnixSecond: 1}} {
		var failure *Failure
		if err := ValidateResponse(response); !errors.As(err, &failure) || failure.Category != FailureInternal {
			t.Fatalf("response=%#v failure=%#v err=%v", response, failure, err)
		}
	}
}

func TestRetryDoesNotClaimIdempotency(t *testing.T) {
	if RetryDispositionFor(FailureUnavailable) != RetryWithBackoff || RetryDispositionFor(FailureStaleState) != RetryAfterStateRefresh || RetryDispositionFor(FailureForbidden) != RetryNever {
		t.Fatal("unexpected retry disposition")
	}
}
