package catalogsnapshot

import (
	"bytes"
	"errors"
	"testing"

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

const testEffectivePermissionCapability = "runtime.permissions.read-effective.v1"

func TestEffectivePermissionProjectionIsScopedMinimalAndImmutable(t *testing.T) {
	input := effectivePermissionCompileInput()
	snapshot := mustOpenSnapshot(t, mustCompile(t, input))
	artifact, err := ProjectRuntimeMetadataView(snapshot, ProjectionKey{
		AppInstallationID: testInstallA,
		CapabilityID:      testEffectivePermissionCapability,
	})
	if err != nil {
		t.Fatal(err)
	}
	view := mustOpenView(t, artifact)
	if view.Profile() != ProjectionEffectivePermissionsV1 ||
		!view.SupportsEffectivePermissionServing() ||
		len(view.Objects()) != 2 || view.SupportsMutationClosure() {
		t.Fatalf("effective-permission authority shape = profile %q objects %d mutation %t", view.Profile(), len(view.Objects()), view.SupportsMutationClosure())
	}
	if err := view.VerifySource(snapshot); err != nil {
		t.Fatalf("verify immutable source: %v", err)
	}

	workItem, ok := view.EffectivePermissionObject("workitem")
	if !ok || workItem.ResourceType != "WorkItem" || len(workItem.FieldNames) != 0 || len(workItem.RecordTypes) != 2 {
		t.Fatalf("WorkItem permission projection = %#v, %t", workItem, ok)
	}
	if workItem.RecordTypes[0].RecordTypeID != "rt-hidden" || workItem.RecordTypes[1].RecordTypeID != "rt-visible" {
		t.Fatalf("active record types = %#v", workItem.RecordTypes)
	}
	if _, ok := view.EffectivePermissionObject("Unrelated"); ok {
		t.Fatal("projection leaked an out-of-scope object")
	}
	decisionObject, ok := view.Object("workitem")
	if !ok || decisionObject.RequiredReadPermission != "ReadWork" ||
		decisionObject.SharingModel != constants.SharingModelControlledByParent ||
		models.FindField(decisionObject, "WorkspaceId") == nil ||
		models.FindField(decisionObject, "InternalNote").ReadProtectionPolicy != "never" {
		t.Fatalf("permission decision object = %#v, %t", decisionObject, ok)
	}
	if relationship, ok := view.SharingParentRelationship("WorkItem"); !ok ||
		relationship.ParentObjectAPIName != "Workspace" || relationship.FieldAPIName != "WorkspaceId" {
		t.Fatalf("sharing-parent decision relationship = %#v, %t", relationship, ok)
	}
	workspace, ok := view.Object("Workspace")
	if !ok || workspace.TableType != constants.TableTypeSystemMetadata {
		t.Fatalf("permission decision TableType = %#v, %t", workspace, ok)
	}
	for _, forbidden := range [][]byte{
		[]byte(`"physicalName"`),
		[]byte(`"resolvedBaseReadTable"`),
		[]byte(`"readSource"`),
		[]byte(`"rowRead"`),
		[]byte(`"mutation"`),
		[]byte(`"recordMutation"`),
		[]byte(`"sharingRules":[{`),
		[]byte(`rt-inactive`),
		[]byte(`rt-unrelated`),
		[]byte(`private record type description`),
		[]byte(`private-business-process`),
	} {
		if bytes.Contains(artifact.Bytes(), forbidden) {
			t.Fatalf("least-privilege view contains forbidden fact %q", forbidden)
		}
	}

	decisionObject.Fields[0].APIName = "changed"
	workItem.RecordTypes[0].Name = "changed"
	again, ok := view.EffectivePermissionObject("WorkItem")
	againDecision, decisionOK := view.Object("WorkItem")
	if !ok || !decisionOK || again.RecordTypes[0].Name == "changed" || againDecision.Fields[0].APIName == "changed" {
		t.Fatalf("caller mutated immutable projection: %#v", again)
	}
}

func TestEffectivePermissionProjectionFailsClosedOnInvalidOrMixedAuthority(t *testing.T) {
	input := effectivePermissionCompileInput()
	input.MutationDefinitions[0].RecordType.DeveloperName = " "
	if _, err := CompileCatalogSnapshot(input); err == nil {
		t.Fatal("blank record-type developer name was accepted")
	}

	input = effectivePermissionCompileInput()
	input.MutationDefinitions[0].CatalogRevision = testRevision + 1
	if _, err := CompileCatalogSnapshot(input); !errors.Is(err, ErrArtifactCorrupt) {
		var conflict *ConflictError
		if !errors.As(err, &conflict) || conflict.Kind != ConflictMixedRevision {
			t.Fatalf("mixed revision error = %v", err)
		}
	}

	snapshot := mustOpenSnapshot(t, mustCompile(t, effectivePermissionCompileInput()))
	artifact, err := ProjectRuntimeMetadataView(snapshot, ProjectionKey{
		AppInstallationID: testInstallA,
		CapabilityID:      testEffectivePermissionCapability,
	})
	if err != nil {
		t.Fatal(err)
	}
	view := mustOpenView(t, artifact)
	view.doc.Mutation = emptyMutationDefinitionDocument()
	encoded, err := marshalCanonical(view.doc)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := OpenRuntimeMetadataView(makeArtifact(ArtifactRuntimeMetadataView, encoded)); !errors.Is(err, ErrArtifactCorrupt) {
		t.Fatalf("mixed least-privilege authority error = %v", err)
	}

	view = mustOpenView(t, artifact)
	view.doc.Relationships[0].FieldAPIName = "MissingParentId"
	encoded, err = marshalCanonical(view.doc)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := OpenRuntimeMetadataView(makeArtifact(ArtifactRuntimeMetadataView, encoded)); !errors.Is(err, ErrArtifactCorrupt) {
		t.Fatalf("missing sharing-parent field error = %v", err)
	}
}

func effectivePermissionCompileInput() CompileInput {
	input := neutralCompileInput()
	for i := range input.Objects {
		if input.Objects[i].Metadata.APIName == "Workspace" {
			input.Objects[i].Metadata.TableType = constants.TableTypeSystemMetadata
		}
	}
	input.Projections = []RuntimeProjectionInput{{
		CatalogRevision:   testRevision,
		AppInstallationID: testInstallA,
		CapabilityID:      testEffectivePermissionCapability,
		Profile:           ProjectionEffectivePermissionsV1,
		ObjectAPINames:    []string{"Workspace", "WorkItem"},
	}}
	description := "private record type description"
	processID := "private-business-process"
	input.MutationDefinitions = []MutationDefinitionInput{
		{CatalogRevision: testRevision, RecordType: &models.RecordType{
			ID: "rt-visible", ObjectAPIName: "WorkItem", DeveloperName: "Visible",
			Name: "Visible", Label: "Presentation Label", Description: &description,
			BusinessProcessID: &processID, IsActive: true,
		}},
		{CatalogRevision: testRevision, RecordType: &models.RecordType{
			ID: "rt-hidden", ObjectAPIName: "WorkItem", DeveloperName: "Hidden",
			Name: "Hidden", IsActive: true,
		}},
		{CatalogRevision: testRevision, RecordType: &models.RecordType{
			ID: "rt-inactive", ObjectAPIName: "WorkItem", DeveloperName: "Inactive",
			Name: "Inactive", IsActive: false,
		}},
		{CatalogRevision: testRevision, RecordType: &models.RecordType{
			ID: "rt-unrelated", ObjectAPIName: "Unrelated", DeveloperName: "Unrelated",
			Name: "Unrelated", IsActive: true,
		}},
	}
	return input
}
