package searchv1

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
	"sort"
	"testing"
	"time"

	common "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
)

func TestValidateScopeOrderRequest(t *testing.T) {
	revision := uint64(17)
	valid := ScopeOrderRequest{
		ObjectIDs:          []string{"Account", "Contact"},
		MinCatalogRevision: &revision,
	}
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	if err := ValidateScopeOrderRequest(ctx, valid); err != nil {
		t.Fatalf("ValidateScopeOrderRequest(valid) = %v", err)
	}

	tests := []struct {
		name    string
		request ScopeOrderRequest
	}{
		{name: "missing objects", request: ScopeOrderRequest{}},
		{name: "invalid object", request: ScopeOrderRequest{ObjectIDs: []string{"Account.Name"}}},
		{name: "duplicate object", request: ScopeOrderRequest{ObjectIDs: []string{"Account", "account"}}},
		{name: "too many objects", request: ScopeOrderRequest{ObjectIDs: objectIDs(MaxObjects + 1)}},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			if err := ValidateScopeOrderRequest(ctx, test.request); err == nil {
				t.Fatal("ValidateScopeOrderRequest() = nil, want failure")
			}
		})
	}

	if err := ValidateScopeOrderRequest(context.Background(), valid); err == nil {
		t.Fatal("ValidateScopeOrderRequest(without deadline) = nil, want failure")
	}
}

func TestValidateScopeOrderResponse(t *testing.T) {
	request := ScopeOrderRequest{ObjectIDs: []string{"Account", "Contact", "Lead"}}
	valid := ScopeOrderResponse{
		ObjectIDs: []string{"Lead", "Account"},
		Receipts: Receipts{
			ProtocolVersion:   "1.0",
			CatalogRevision:   17,
			SecurityEpoch:     3,
			SchemaGeneration:  2,
			EngineGeneration:  5,
			CapabilityVersion: CapabilityName,
		},
	}
	if err := ValidateScopeOrderResponse(request, valid); err != nil {
		t.Fatalf("ValidateScopeOrderResponse(valid) = %v", err)
	}
	empty := valid
	empty.ObjectIDs = []string{}
	if err := ValidateScopeOrderResponse(request, empty); err != nil {
		t.Fatalf("ValidateScopeOrderResponse(empty authorized set) = %v", err)
	}

	tests := []struct {
		name     string
		response ScopeOrderResponse
	}{
		{
			name: "object outside request",
			response: ScopeOrderResponse{
				ObjectIDs: []string{"Opportunity"},
				Receipts:  valid.Receipts,
			},
		},
		{
			name: "duplicate object",
			response: ScopeOrderResponse{
				ObjectIDs: []string{"Account", "account"},
				Receipts:  valid.Receipts,
			},
		},
		{
			name: "wrong capability receipt",
			response: ScopeOrderResponse{
				ObjectIDs: []string{"Account"},
				Receipts: func() Receipts {
					receipts := valid.Receipts
					receipts.CapabilityVersion = "runtime.query.records.v1"
					return receipts
				}(),
			},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			var failure *Failure
			err := ValidateScopeOrderResponse(request, test.response)
			if !errors.As(err, &failure) || failure.Category != common.FailureInternal {
				t.Fatalf("ValidateScopeOrderResponse() = %v, want internal failure", err)
			}
		})
	}
}

func TestScopeOrderJSONFieldNames(t *testing.T) {
	revision := uint64(17)
	tests := []struct {
		name  string
		value any
		keys  []string
	}{
		{
			name: "request",
			value: ScopeOrderRequest{
				ObjectIDs:          []string{"Account"},
				MinCatalogRevision: &revision,
			},
			keys: []string{"min_catalog_revision", "object_ids"},
		},
		{
			name: "response",
			value: ScopeOrderResponse{
				ObjectIDs: []string{"Account"},
				Receipts:  Receipts{},
			},
			keys: []string{"object_ids", "receipts"},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			encoded, err := json.Marshal(test.value)
			if err != nil {
				t.Fatalf("json.Marshal() = %v", err)
			}
			var fields map[string]json.RawMessage
			if err := json.Unmarshal(encoded, &fields); err != nil {
				t.Fatalf("json.Unmarshal() = %v", err)
			}
			keys := make([]string, 0, len(fields))
			for key := range fields {
				keys = append(keys, key)
			}
			sort.Strings(keys)
			if !reflect.DeepEqual(keys, test.keys) {
				t.Fatalf("JSON fields = %v, want %v", keys, test.keys)
			}
		})
	}
}

func objectIDs(count int) []string {
	result := make([]string, count)
	for index := range result {
		result[index] = fmt.Sprintf("Object%d", index)
	}
	return result
}
