package controlplane

import (
	"context"
	"strings"
	"testing"
)

func TestManagedComponentRowDescriptorsValidateBounds(t *testing.T) {
	tests := []struct {
		name    string
		err     error
		wantErr string
	}{
		{
			name:    "query requires columns",
			err:     ComponentRowQuery{Table: "ProcessNode", Equal: ComponentRow{"OwnerID": "pack"}}.Validate(),
			wantErr: "columns are required",
		},
		{
			name:    "query requires predicates",
			err:     ComponentRowQuery{Table: "ProcessNode", Columns: []string{"ID"}}.Validate(),
			wantErr: "equal predicates are required",
		},
		{
			name:    "insert requires values",
			err:     ComponentInsert{Table: "ProcessNode"}.Validate(),
			wantErr: "values are required",
		},
		{
			name: "insert rejects ambiguous timestamp column",
			err: ComponentInsert{
				Table:                   "ProcessNode",
				Values:                  ComponentRow{"CreatedDate": "source"},
				CurrentTimestampColumns: []string{"CreatedDate"},
			}.Validate(),
			wantErr: "current timestamp column",
		},
		{
			name: "update requires match",
			err: ComponentUpdate{
				Table:  "ProcessNode",
				Values: ComponentRow{"Label": "manager"},
			}.Validate(),
			wantErr: "match predicates are required",
		},
		{
			name: "update requires mutation",
			err: ComponentUpdate{
				Table: "ProcessNode",
				Match: ComponentRow{"ID": "node-1"},
			}.Validate(),
			wantErr: "update requires values",
		},
		{
			name:    "delete requires match",
			err:     ComponentDelete{Table: "ProcessNode"}.Validate(),
			wantErr: "match predicates are required",
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			if test.err == nil || !strings.Contains(test.err.Error(), test.wantErr) {
				t.Fatalf("Validate() error = %v, want containing %q", test.err, test.wantErr)
			}
		})
	}

	valid := []error{
		ComponentRowQuery{Table: "ProcessNode", Columns: []string{"ID"}, Equal: ComponentRow{"OwnerID": "pack"}}.Validate(),
		ComponentInsert{Table: "ProcessNode", Values: ComponentRow{"ID": "node-1"}}.Validate(),
		ComponentUpdate{Table: "ProcessNode", Match: ComponentRow{"ID": "node-1"}, CurrentTimestampColumns: []string{"UpdatedDate"}}.Validate(),
		ComponentDelete{Table: "ProcessNode", Match: ComponentRow{"ID": "node-1"}}.Validate(),
	}
	for index, err := range valid {
		if err != nil {
			t.Fatalf("valid descriptor %d: %v", index, err)
		}
	}
}

func TestManagedComponentBuildDescriptorValidation(t *testing.T) {
	tests := []struct {
		name    string
		value   ComponentBuildManagedComponent
		wantErr string
	}{
		{
			name:    "requires projection",
			value:   ComponentBuildManagedComponent{},
			wantErr: "expected projection is required",
		},
		{
			name: "requires complete collision claim",
			value: ComponentBuildManagedComponent{
				ExpectedProjection: []byte(`{}`),
				CollisionClaims:    []ComponentCollisionClaim{{Namespace: "approval_process"}},
			},
			wantErr: "collision claim key is required",
		},
		{
			name: "rejects duplicate collision claim",
			value: ComponentBuildManagedComponent{
				ExpectedProjection: []byte(`{}`),
				CollisionClaims: []ComponentCollisionClaim{
					{Namespace: "approval_process", Key: "Expense"},
					{Namespace: "approval_process", Key: "Expense"},
				},
			},
			wantErr: "duplicates",
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			err := test.value.Validate()
			if err == nil || !strings.Contains(err.Error(), test.wantErr) {
				t.Fatalf("Validate() error = %v, want containing %q", err, test.wantErr)
			}
		})
	}

	value := ComponentBuildManagedComponent{
		ExpectedProjection:              []byte(`{"name":"Expense"}`),
		PublishRuntimeStartupProjection: true,
		CollisionClaims:                 []ComponentCollisionClaim{{Namespace: "approval_process", Key: "Expense"}},
	}
	if err := value.Validate(); err != nil {
		t.Fatalf("Validate() error = %v", err)
	}
}

func TestManagedProviderValidationRequiresCompleteLifecycle(t *testing.T) {
	if err := (*ComponentManagedProvider)(nil).Validate(); err == nil || !strings.Contains(err.Error(), "provider is required") {
		t.Fatalf("nil provider Validate() error = %v", err)
	}

	incomplete := &ComponentManagedProvider{
		Preflight: func(context.Context, ComponentRowReader, ComponentManagedRequest) error { return nil },
	}
	if err := incomplete.Validate(); err == nil || !strings.Contains(err.Error(), "Apply") || !strings.Contains(err.Error(), "VerifyProjection") || !strings.Contains(err.Error(), "ListLiveBusinessKeys") || !strings.Contains(err.Error(), "ReconcileSet") {
		t.Fatalf("incomplete provider Validate() error = %v", err)
	}

	complete := &ComponentManagedProvider{
		Preflight: func(context.Context, ComponentRowReader, ComponentManagedRequest) error { return nil },
		Apply:     func(context.Context, ComponentMutationStore, ComponentManagedRequest) error { return nil },
		VerifyProjection: func(context.Context, ComponentRowReader, ComponentManagedRequest) ([]byte, []byte, error) {
			return []byte(`{}`), []byte(`{}`), nil
		},
		ListLiveBusinessKeys: func(context.Context, ComponentRowReader, ComponentManagedSetRequest) ([]ComponentLiveBusinessKey, error) {
			return nil, nil
		},
		ReconcileSet: func(context.Context, ComponentMutationStore, ComponentManagedSetRequest, []string) error {
			return nil
		},
	}
	if err := complete.Validate(); err != nil {
		t.Fatalf("complete provider Validate() error = %v", err)
	}
}

func TestManagedProviderRequestsCarrySourceOwnerScope(t *testing.T) {
	request := ComponentManagedRequest{
		PackName:           "crm",
		SourceOwner:        "source-a",
		Component:          ManifestComponent{Kind: ComponentKindApprovalProcess, BusinessKey: "Opportunity.Approval"},
		Payload:            []byte(`<ApprovalProcess/>`),
		ExpectedProjection: []byte(`{}`),
	}
	setRequest := ComponentManagedSetRequest{
		PackName:    request.PackName,
		SourceOwner: request.SourceOwner,
		Kind:        request.Component.Kind,
	}

	provider := &ComponentManagedProvider{
		Preflight: func(_ context.Context, _ ComponentRowReader, got ComponentManagedRequest) error {
			if got.SourceOwner != "source-a" {
				t.Fatalf("Preflight SourceOwner = %q", got.SourceOwner)
			}
			return nil
		},
		Apply: func(_ context.Context, _ ComponentMutationStore, got ComponentManagedRequest) error {
			if got.SourceOwner != "source-a" {
				t.Fatalf("Apply SourceOwner = %q", got.SourceOwner)
			}
			return nil
		},
		VerifyProjection: func(_ context.Context, _ ComponentRowReader, got ComponentManagedRequest) ([]byte, []byte, error) {
			if got.SourceOwner != "source-a" {
				t.Fatalf("VerifyProjection SourceOwner = %q", got.SourceOwner)
			}
			return got.ExpectedProjection, got.ExpectedProjection, nil
		},
		ListLiveBusinessKeys: func(_ context.Context, _ ComponentRowReader, got ComponentManagedSetRequest) ([]ComponentLiveBusinessKey, error) {
			if got.SourceOwner != "source-a" {
				t.Fatalf("ListLiveBusinessKeys SourceOwner = %q", got.SourceOwner)
			}
			return []ComponentLiveBusinessKey{{BusinessKey: request.Component.BusinessKey}}, nil
		},
		ReconcileSet: func(_ context.Context, _ ComponentMutationStore, got ComponentManagedSetRequest, declaredKeys []string) error {
			if got.SourceOwner != "source-a" {
				t.Fatalf("ReconcileSet SourceOwner = %q", got.SourceOwner)
			}
			if len(declaredKeys) != 0 {
				t.Fatalf("ReconcileSet declared keys = %v, want dropped-to-zero snapshot", declaredKeys)
			}
			return nil
		},
	}

	if err := provider.Preflight(context.Background(), nil, request); err != nil {
		t.Fatalf("Preflight error = %v", err)
	}
	if err := provider.Apply(context.Background(), nil, request); err != nil {
		t.Fatalf("Apply error = %v", err)
	}
	if _, _, err := provider.VerifyProjection(context.Background(), nil, request); err != nil {
		t.Fatalf("VerifyProjection error = %v", err)
	}
	if _, err := provider.ListLiveBusinessKeys(context.Background(), nil, setRequest); err != nil {
		t.Fatalf("ListLiveBusinessKeys error = %v", err)
	}
	if err := provider.ReconcileSet(context.Background(), nil, setRequest, nil); err != nil {
		t.Fatalf("ReconcileSet error = %v", err)
	}
}
