package catalogsnapshot

import (
	"errors"
	"strings"
	"testing"

	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

func TestCommandRegistrationProjectionIsScopedAndObjectBlind(t *testing.T) {
	input := commandRegistrationCompileInput()
	artifact, err := CompileCatalogSnapshot(input)
	if err != nil {
		t.Fatal(err)
	}
	snapshot, err := OpenCatalogSnapshot(artifact)
	if err != nil {
		t.Fatal(err)
	}
	if snapshot.doc.Schema != CatalogSnapshotSchemaV9 {
		t.Fatalf("Catalog schema = %q, want %q", snapshot.doc.Schema, CatalogSnapshotSchemaV9)
	}
	viewArtifact, err := ProjectRuntimeMetadataView(snapshot, ProjectionKey{
		AppInstallationID: "installation-a", CapabilityID: "runtime.commands.accept.v1",
	})
	if err != nil {
		t.Fatal(err)
	}
	view, err := OpenRuntimeMetadataView(viewArtifact)
	if err != nil {
		t.Fatal(err)
	}
	if view.Profile() != ProjectionCommandAcceptanceV1 || view.doc.Schema != RuntimeMetadataViewSchemaV11 {
		t.Fatalf("command view = %q/%q", view.Profile(), view.doc.Schema)
	}
	if len(view.doc.Objects) != 0 || len(view.doc.Relationships) != 0 || len(view.doc.SharingRules) != 0 {
		t.Fatalf("command view leaked object metadata: %#v", view.doc)
	}
	if !view.AllowsCommand("example.effect", 1) ||
		view.AllowsCommand("example.effect", 2) ||
		view.AllowsCommand("other.effect", 1) {
		t.Fatal("command view did not enforce the exact kind/version registration")
	}

	otherArtifact, err := ProjectRuntimeMetadataView(snapshot, ProjectionKey{
		AppInstallationID: "installation-b", CapabilityID: "runtime.commands.accept.v1",
	})
	if err != nil {
		t.Fatal(err)
	}
	other, err := OpenRuntimeMetadataView(otherArtifact)
	if err != nil {
		t.Fatal(err)
	}
	if other.AllowsCommand("example.effect", 1) || !other.AllowsCommand("other.effect", 3) {
		t.Fatal("command authority crossed AppInstallation projection scope")
	}
}

func TestCommandExecutionBindingIsWorkloadAndDefinitionScoped(t *testing.T) {
	input := commandRegistrationCompileInput()
	input.CommandRegistrations[0].ExecutionBindings = []CommandExecutionBinding{
		{
			AuthorizedWorkloadID: "integration-a",
			DefinitionRef:        "flow-a",
			DefinitionVersion:    "flow-version-a",
			ContextNamespace:     "connector-a",
		},
	}
	effective, err := BuildEffectiveMetadata(input)
	if err != nil {
		t.Fatal(err)
	}
	var effectiveDocument catalogDocument
	if err := decodeStrict(effective.bytes, &effectiveDocument); err != nil {
		t.Fatal(err)
	}
	if effectiveDocument.Schema != EffectiveMetadataSchemaV9 {
		t.Fatalf(
			"effective metadata schema=%q want=%q",
			effectiveDocument.Schema,
			EffectiveMetadataSchemaV9,
		)
	}
	snapshotArtifact, err := CompileEffectiveMetadata(effective)
	if err != nil {
		t.Fatal(err)
	}
	snapshot := mustOpenSnapshot(t, snapshotArtifact)
	if snapshot.doc.Schema != CatalogSnapshotSchemaV10 {
		t.Fatalf(
			"Catalog schema=%q want=%q",
			snapshot.doc.Schema,
			CatalogSnapshotSchemaV10,
		)
	}
	artifact, err := ProjectRuntimeMetadataView(
		snapshot,
		ProjectionKey{
			AppInstallationID: "installation-a",
			CapabilityID:      "runtime.commands.accept.v1",
		},
	)
	if err != nil {
		t.Fatal(err)
	}
	view := mustOpenView(t, artifact)
	if view.doc.Schema != RuntimeMetadataViewSchemaV12 {
		t.Fatalf(
			"Runtime view schema=%q want=%q",
			view.doc.Schema,
			RuntimeMetadataViewSchemaV12,
		)
	}
	if err := view.VerifySource(snapshot); err != nil {
		t.Fatalf("bound Runtime view source verification: %v", err)
	}
	namespace, ok := view.CommandExecutionContext(
		"example.effect",
		1,
		"integration-a",
		"flow-a",
		"flow-version-a",
	)
	if !ok || namespace != "connector-a" {
		t.Fatalf("exact command execution context = %q, %v", namespace, ok)
	}
	for name, values := range map[string][]string{
		"workload":   {"integration-other", "flow-a", "flow-version-a"},
		"definition": {"integration-a", "flow-other", "flow-version-a"},
		"version":    {"integration-a", "flow-a", "flow-version-other"},
	} {
		t.Run(name, func(t *testing.T) {
			if _, allowed := view.CommandExecutionContext(
				"example.effect",
				1,
				values[0],
				values[1],
				values[2],
			); allowed {
				t.Fatal("out-of-scope command execution binding was authorized")
			}
		})
	}

	unboundArtifact, err := ProjectRuntimeMetadataView(
		snapshot,
		ProjectionKey{
			AppInstallationID: "installation-b",
			CapabilityID:      "runtime.commands.accept.v1",
		},
	)
	if err != nil {
		t.Fatal(err)
	}
	unboundView := mustOpenView(t, unboundArtifact)
	if unboundView.doc.Schema != RuntimeMetadataViewSchemaV11 {
		t.Fatalf(
			"unbound-only Runtime view schema=%q want=%q",
			unboundView.doc.Schema,
			RuntimeMetadataViewSchemaV11,
		)
	}
	if err := unboundView.VerifySource(snapshot); err != nil {
		t.Fatalf("unbound Runtime view source verification: %v", err)
	}
	if !unboundView.AllowsCommand("other.effect", 3) {
		t.Fatal("unbound N-1 command registration was not preserved")
	}
	if _, allowed := unboundView.CommandExecutionContext(
		"other.effect",
		3,
		"integration-a",
		"flow-a",
		"flow-version-a",
	); allowed {
		t.Fatal("unbound N-1 command acquired execution authority")
	}
}

func TestCommandRegistrationFailsClosed(t *testing.T) {
	for _, test := range []struct {
		name   string
		mutate func(*CompileInput)
	}{
		{name: "missing registration", mutate: func(input *CompileInput) {
			input.CommandRegistrations = nil
		}},
		{name: "mixed revision", mutate: func(input *CompileInput) {
			input.CommandRegistrations[0].CatalogRevision++
		}},
		{name: "unknown kind syntax", mutate: func(input *CompileInput) {
			input.CommandRegistrations[0].CommandKind = "bad kind"
		}},
		{name: "unversioned payload", mutate: func(input *CompileInput) {
			input.CommandRegistrations[0].PayloadFormatVersion = 0
		}},
		{name: "object leakage", mutate: func(input *CompileInput) {
			input.Projections[0].ObjectAPINames = []string{"WorkItem"}
		}},
		{name: "wrong component selection", mutate: func(input *CompileInput) {
			input.Definitions = []DefinitionInput{{
				CatalogRevision: 17, ComponentID: "component.other",
				DefinitionVersion: "1", ContentDigest: commandTestDigest('c'),
			}}
			input.Projections[0].DefinitionComponentIDs = []string{"component.other"}
		}},
		{name: "duplicate pair", mutate: func(input *CompileInput) {
			duplicate := input.CommandRegistrations[0]
			duplicate.ComponentID = "component.command.duplicate"
			duplicate.ContentDigest = commandTestDigest('d')
			input.CommandRegistrations = append(input.CommandRegistrations, duplicate)
		}},
		{name: "partial execution binding", mutate: func(input *CompileInput) {
			input.CommandRegistrations[0].ExecutionBindings =
				[]CommandExecutionBinding{{
					AuthorizedWorkloadID: "integration-a",
					DefinitionRef:        "flow-a",
					DefinitionVersion:    "flow-version-a",
				}}
		}},
		{name: "control character in execution identity", mutate: func(input *CompileInput) {
			input.CommandRegistrations[0].ExecutionBindings =
				[]CommandExecutionBinding{{
					AuthorizedWorkloadID: "integration-a\x00other",
					DefinitionRef:        "flow-a",
					DefinitionVersion:    "flow-version-a",
					ContextNamespace:     "connector-a",
				}}
		}},
		{name: "oversized execution identity", mutate: func(input *CompileInput) {
			input.CommandRegistrations[0].ExecutionBindings =
				[]CommandExecutionBinding{{
					AuthorizedWorkloadID: strings.Repeat("a", 256),
					DefinitionRef:        "flow-a",
					DefinitionVersion:    "flow-version-a",
					ContextNamespace:     "connector-a",
				}}
		}},
		{name: "invalid UTF-8 execution identity", mutate: func(input *CompileInput) {
			input.CommandRegistrations[0].ExecutionBindings =
				[]CommandExecutionBinding{{
					AuthorizedWorkloadID: string([]byte{0xff}),
					DefinitionRef:        "flow-a",
					DefinitionVersion:    "flow-version-a",
					ContextNamespace:     "connector-a",
				}}
		}},
		{name: "duplicate execution binding", mutate: func(input *CompileInput) {
			binding := CommandExecutionBinding{
				AuthorizedWorkloadID: "integration-a",
				DefinitionRef:        "flow-a",
				DefinitionVersion:    "flow-version-a",
				ContextNamespace:     "connector-a",
			}
			input.CommandRegistrations[0].ExecutionBindings =
				[]CommandExecutionBinding{binding, binding}
		}},
		{name: "ambiguous execution namespace", mutate: func(input *CompileInput) {
			binding := CommandExecutionBinding{
				AuthorizedWorkloadID: "integration-a",
				DefinitionRef:        "flow-a",
				DefinitionVersion:    "flow-version-a",
				ContextNamespace:     "connector-a",
			}
			other := binding
			other.ContextNamespace = "connector-other"
			input.CommandRegistrations[0].ExecutionBindings =
				[]CommandExecutionBinding{binding, other}
		}},
	} {
		t.Run(test.name, func(t *testing.T) {
			input := commandRegistrationCompileInput()
			test.mutate(&input)
			if _, err := CompileCatalogSnapshot(input); err == nil {
				t.Fatal("CompileCatalogSnapshot accepted invalid command authority")
			}
		})
	}
}

func TestCommandExecutionBindingCannotHideUnderNMinusOneSchemas(t *testing.T) {
	input := commandRegistrationCompileInput()
	input.CommandRegistrations[0].ExecutionBindings =
		[]CommandExecutionBinding{{
			AuthorizedWorkloadID: "integration-a",
			DefinitionRef:        "flow-a",
			DefinitionVersion:    "flow-version-a",
			ContextNamespace:     "connector-a",
		}}
	snapshotArtifact := mustCompile(t, input)
	var snapshotDocument catalogDocument
	if err := decodeStrict(snapshotArtifact.bytes, &snapshotDocument); err != nil {
		t.Fatal(err)
	}
	snapshotDocument.Schema = CatalogSnapshotSchemaV9
	encodedSnapshot, err := marshalCanonical(snapshotDocument)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := OpenCatalogSnapshot(
		makeArtifact(ArtifactCatalogSnapshot, encodedSnapshot),
	); !errors.Is(err, ErrArtifactCorrupt) ||
		!strings.Contains(err.Error(), "pre-v10") {
		t.Fatalf(
			"open downgraded Catalog snapshot error=%v, want pre-v10 rejection",
			err,
		)
	}

	viewArtifact := mustProject(
		t,
		mustOpenSnapshot(t, snapshotArtifact),
		"installation-a",
		"runtime.commands.accept.v1",
	)
	var viewDocument runtimeViewDocument
	if err := decodeStrict(viewArtifact.bytes, &viewDocument); err != nil {
		t.Fatal(err)
	}
	viewDocument.Schema = RuntimeMetadataViewSchemaV11
	encodedView, err := marshalCanonical(viewDocument)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := OpenRuntimeMetadataView(
		makeArtifact(ArtifactRuntimeMetadataView, encodedView),
	); !errors.Is(err, ErrArtifactCorrupt) ||
		!strings.Contains(err.Error(), "pre-v12") {
		t.Fatalf(
			"open downgraded Runtime view error=%v, want pre-v12 rejection",
			err,
		)
	}
}

func commandRegistrationCompileInput() CompileInput {
	revision := CatalogRevision(17)
	return CompileInput{
		OrgID: "org-test",
		Lineage: Lineage{
			CatalogRevision: revision, SchemaGeneration: 8, EngineGeneration: 9,
		},
		Objects: []ObjectInput{{
			CatalogRevision: revision,
			Metadata: &models.ObjectMetadata{
				ID: "object-work-item", APIName: "WorkItem", Label: "Work Item", PluralLabel: "Work Items",
				SharingModel: constants.SharingModelPublicReadWrite,
				Fields: []models.FieldMetadata{{
					ID: "field-work-item-id", APIName: constants.FieldID, Label: "ID",
					Type: constants.FieldTypeString, PhysicalName: "id", IsSystem: true,
				}},
			},
		}},
		CommandRegistrations: []CommandRegistrationInput{
			{
				CatalogRevision: revision, ComponentID: "component.command.example",
				DefinitionVersion: "1", ContentDigest: commandTestDigest('a'),
				CommandKind: "example.effect", PayloadFormatVersion: 1,
			},
			{
				CatalogRevision: revision, ComponentID: "component.command.other",
				DefinitionVersion: "1", ContentDigest: commandTestDigest('b'),
				CommandKind: "other.effect", PayloadFormatVersion: 3,
			},
		},
		Projections: []RuntimeProjectionInput{
			{
				CatalogRevision: revision, AppInstallationID: "installation-a",
				CapabilityID: "runtime.commands.accept.v1", Profile: ProjectionCommandAcceptanceV1,
				DefinitionComponentIDs: []string{"component.command.example"},
			},
			{
				CatalogRevision: revision, AppInstallationID: "installation-b",
				CapabilityID: "runtime.commands.accept.v1", Profile: ProjectionCommandAcceptanceV1,
				DefinitionComponentIDs: []string{"component.command.other"},
			},
		},
	}
}

func commandTestDigest(value byte) Digest {
	return Digest("sha256:" + strings.Repeat(string(value), 64))
}
