package approvalnotificationflowv1

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

func TestCatalogReadIsBoundedCanonicalAndRevisionBound(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	minimum := uint64(7)
	request := Request{MinCatalogRevision: &minimum}
	if err := ValidateRequest(ctx, request); err != nil {
		t.Fatalf("request = %v", err)
	}
	response := Response{
		Flows: []FlowDescriptor{
			{Name: "Account_Notify_Approver", Label: "Notify Account Approver", Description: "Sends the approval notification."},
			{Name: "Credits_Notify_Approver", Label: "Notify Credits Approver"},
		},
		Receipts: testReceipts(minimum),
	}
	if err := ValidateResponseForRequest(request, response); err != nil {
		t.Fatalf("response = %v", err)
	}

	unsorted := response
	unsorted.Flows = append([]FlowDescriptor(nil), response.Flows...)
	unsorted.Flows[0], unsorted.Flows[1] = unsorted.Flows[1], unsorted.Flows[0]
	if err := ValidateResponseForRequest(request, unsorted); err == nil {
		t.Fatal("expected non-canonical Flow order rejection")
	}

	duplicate := response
	duplicate.Flows = append(append([]FlowDescriptor(nil), response.Flows...), FlowDescriptor{
		Name: "account_notify_approver", Label: "Duplicate",
	})
	if err := ValidateResponseForRequest(request, duplicate); err == nil {
		t.Fatal("expected case-insensitive duplicate Flow identity rejection")
	}

	stale := response
	stale.Receipts.CatalogRevision = minimum - 1
	if err := ValidateResponseForRequest(request, stale); err == nil {
		t.Fatal("expected stale Catalog projection rejection")
	}
}

func TestCatalogReadRejectsInvalidContextAndRevision(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	for _, test := range []struct {
		name    string
		ctx     context.Context
		request Request
	}{
		{name: "nil context", ctx: nil},
		{name: "missing deadline", ctx: context.Background()},
		{name: "zero revision", ctx: ctx, request: Request{MinCatalogRevision: new(uint64)}},
	} {
		t.Run(test.name, func(t *testing.T) {
			var failure *Failure
			if err := ValidateRequest(test.ctx, test.request); !errors.As(err, &failure) || failure.Category != FailureInvalidRequest {
				t.Fatalf("ValidateRequest() = %#v", err)
			}
		})
	}
}

func TestCatalogReadRejectsMalformedOrOversizedDescriptors(t *testing.T) {
	request := Request{}
	valid := Response{
		Flows:    []FlowDescriptor{{Name: "Notify_Approver", Label: "Notify Approver"}},
		Receipts: testReceipts(1),
	}
	for _, test := range []struct {
		name   string
		mutate func(*Response)
	}{
		{name: "empty name", mutate: func(response *Response) { response.Flows[0].Name = "" }},
		{name: "untrimmed name", mutate: func(response *Response) { response.Flows[0].Name = " Notify_Approver" }},
		{name: "empty label", mutate: func(response *Response) { response.Flows[0].Label = "" }},
		{name: "invalid description", mutate: func(response *Response) { response.Flows[0].Description = string([]byte{0xff}) }},
	} {
		t.Run(test.name, func(t *testing.T) {
			response := valid
			response.Flows = append([]FlowDescriptor(nil), valid.Flows...)
			test.mutate(&response)
			if err := ValidateResponseForRequest(request, response); err == nil {
				t.Fatal("expected malformed descriptor rejection")
			}
		})
	}

	oversizedCount := Response{
		Flows:    make([]FlowDescriptor, MaxFlows+1),
		Receipts: testReceipts(1),
	}
	var failure *Failure
	if err := ValidateResponseForRequest(request, oversizedCount); !errors.As(err, &failure) ||
		failure.Category != FailureResourceExhausted || failure.LimitKind != LimitRecordCount {
		t.Fatalf("oversized count = %#v", err)
	}

	oversizedAggregate := Response{Receipts: testReceipts(1)}
	description := strings.Repeat("x", MaxDescriptionBytes)
	for index := 0; index < MaxSemanticResponseBytes/MaxDescriptionBytes+1; index++ {
		oversizedAggregate.Flows = append(oversizedAggregate.Flows, FlowDescriptor{
			Name:        fmt.Sprintf("Flow_%08d", index),
			Label:       "Flow",
			Description: description,
		})
	}
	if err := ValidateResponseForRequest(request, oversizedAggregate); !errors.As(err, &failure) ||
		failure.Category != FailureResourceExhausted || failure.LimitKind != LimitSemanticResponse {
		t.Fatalf("oversized aggregate = %#v", err)
	}
}

func testReceipts(revision uint64) Receipts {
	return Receipts{
		ProtocolVersion: "1.0", CatalogRevision: revision, CapabilityVersion: CapabilityName,
	}
}
