package catalogreconcile

import (
	"context"
	"encoding/json"
	"errors"
	"reflect"
	"testing"

	"github.com/nexuscrm/metadata"
	"github.com/nexuscrm/shared/pkg/models"
	consumerv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/queryv1"
)

type searchScopeReader struct {
	items   []metadata.SearchScopeOrderItem
	err     error
	context context.Context
}

func (r *searchScopeReader) GetRuntimeSearchScopeOrder(ctx context.Context) ([]metadata.SearchScopeOrderItem, error) {
	if ctx != r.context {
		return nil, errors.New("source snapshot transaction was lost")
	}
	return r.items, r.err
}

func TestEffectiveSearchScopePreservesInstalledFactsWithinObjectClosure(t *testing.T) {
	ctx := t.Context()
	reader := &searchScopeReader{context: ctx, items: []metadata.SearchScopeOrderItem{
		{Name: "Workspace", Label: "Tenant workspace"},
		{Name: "WorkItem", Label: "Work"},
		{Name: "OtherAppObject", Label: "Other"},
	}}
	component, err := buildEffectiveSearchScopeComponent(ctx, reader, []string{"WorkItem", "Workspace"})
	if err != nil {
		t.Fatal(err)
	}
	var got []metadata.SearchScopeOrderItem
	if err := json.Unmarshal(component.CanonicalBehavior, &got); err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(got, reader.items[:2]) || component.ComponentKind != consumerv1.EffectiveSearchScopeComponentKind ||
		component.ContentDigest != consumerv1.ComputeEffectiveMetadataComponentDigest(component.CanonicalBehavior) {
		t.Fatalf("effective scope = %#v, component = %#v", got, component)
	}
	reader.err = errors.New("generation unavailable")
	if _, err := buildEffectiveSearchScopeComponent(ctx, reader, nil); !errors.Is(err, reader.err) {
		t.Fatalf("source failure was hidden: %v", err)
	}
	reader.err = nil
	reader.items = append(reader.items, metadata.SearchScopeOrderItem{Name: "workspace"})
	if _, err := buildEffectiveSearchScopeComponent(ctx, reader, []string{"Workspace"}); err == nil {
		t.Fatal("conflicting declarations were silently combined")
	}
}

func TestCompleteAppMetadataPublishesInstalledSearchScope(t *testing.T) {
	reader := &completeV3FlowReader{markedReader: &markedReader{
		t: t, objects: []*models.ObjectMetadata{validObject("WorkItem"), validObject("Workspace")},
		searchScope: []metadata.SearchScopeOrderItem{{Name: "Workspace", Label: "Workspace"}, {Name: "WorkItem", Label: "Work"}},
	}}
	release := verifiedPlatformRelease(t, nil)
	selection := MetadataSelection{OrgID: "org-test", AppInstallationID: "installation-test", RuntimeCapabilityIDs: []string{queryv1.CapabilityName}, CatalogRevision: 7, SchemaGeneration: 8, EngineGeneration: 9}
	read := func() []byte {
		result, err := readEffectiveMetadata(t.Context(), func(ctx context.Context, fn func(context.Context) error) error {
			return fn(context.WithValue(ctx, transactionMarker{}, "one-snapshot"))
		}, reader, selection, &release, consumerv1.AppMetadataV3CapabilityName)
		if err != nil {
			t.Fatal(err)
		}
		for _, component := range result.components {
			if component.ComponentKind == consumerv1.EffectiveSearchScopeComponentKind {
				if err := consumerv1.ValidateEffectiveMetadataComponent(component); err != nil {
					t.Fatal(err)
				}
				return component.CanonicalBehavior
			}
		}
		t.Fatal("complete V3 projection omitted installed search scope")
		return nil
	}
	first := read()
	var items []metadata.SearchScopeOrderItem
	if err := json.Unmarshal(first, &items); err != nil || !reflect.DeepEqual(items, reader.searchScope) {
		t.Fatalf("scope=%s err=%v", first, err)
	}
	reader.searchScope = reader.searchScope[:1]
	selection.CatalogRevision++
	second := read()
	if string(first) == string(second) {
		t.Fatal("changed installed scope retained the old projection")
	}
}
