package catalogsnapshot

import (
	"bytes"
	"testing"

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

const testReportCapability = "runtime.analytics.read-report.v1"

func TestReportExecutionProjectionIsAppScopedImmutableAndVersioned(t *testing.T) {
	input := reportCompileInput()
	original := input.ReportDefinitions[0].Definition
	snapshotArtifact := mustCompile(t, input)
	snapshot := mustOpenSnapshot(t, snapshotArtifact)
	if snapshot.doc.Schema != CatalogSnapshotSchemaV8 {
		t.Fatalf("Catalog schema = %q, want %q", snapshot.doc.Schema, CatalogSnapshotSchemaV8)
	}

	viewArtifact, err := ProjectRuntimeMetadataView(snapshot, ProjectionKey{
		AppInstallationID: testInstallA, CapabilityID: testReportCapability,
	})
	if err != nil {
		t.Fatalf("project report view: %v", err)
	}
	view := mustOpenView(t, viewArtifact)
	if view.doc.Schema != RuntimeMetadataViewSchemaV10 || view.Profile() != ProjectionReportExecutionV1 {
		t.Fatalf("report view schema/profile = %q/%q", view.doc.Schema, view.Profile())
	}
	definition, version, digest, ok := view.ReportDefinition("report-open-work")
	if !ok || version != "3" || digest != testReportDigest('a') {
		t.Fatalf("ReportDefinition lineage = %#v, %q, %q, %v", definition, version, digest, ok)
	}
	if definition.Name != "Open work" || definition.SourceObject != "WorkItem" ||
		len(definition.Columns) != 2 || definition.Columns[1] != "Subject" {
		t.Fatalf("ReportDefinition = %#v", definition)
	}
	if _, _, _, ok := view.ReportDefinition("report-other-app"); ok {
		t.Fatal("report from another AppInstallation entered the view")
	}

	original.Name = "mutated after publication"
	original.Columns[0] = "Done"
	definition, _, _, ok = view.ReportDefinition("report-open-work")
	if !ok || definition.Name != "Open work" || definition.Columns[0] != "Id" {
		t.Fatalf("published report changed after caller mutation: %#v", definition)
	}

	otherArtifact, err := ProjectRuntimeMetadataView(snapshot, ProjectionKey{
		AppInstallationID: testInstallB, CapabilityID: testReportCapability,
	})
	if err != nil {
		t.Fatalf("project other report view: %v", err)
	}
	other := mustOpenView(t, otherArtifact)
	if _, _, _, ok := other.ReportDefinition("report-open-work"); ok {
		t.Fatal("first AppInstallation report entered second AppInstallation view")
	}
	if _, _, _, ok := other.ReportDefinition("report-other-app"); !ok {
		t.Fatal("second AppInstallation report is absent")
	}
}

func TestReportEffectiveMetadataRoundTripPreservesExactIR(t *testing.T) {
	input := reportCompileInput()
	effective, err := BuildEffectiveMetadata(input)
	if err != nil {
		t.Fatalf("BuildEffectiveMetadata: %v", err)
	}
	opened, err := OpenEffectiveMetadata(effective.Digest(), effective.Bytes())
	if err != nil {
		t.Fatalf("OpenEffectiveMetadata: %v", err)
	}
	compiled, err := CompileEffectiveMetadata(opened)
	if err != nil {
		t.Fatalf("CompileEffectiveMetadata: %v", err)
	}
	direct := mustCompile(t, input)
	if !bytes.Equal(compiled.Bytes(), direct.Bytes()) || compiled.Digest() != direct.Digest() {
		t.Fatal("effective metadata round-trip changed the report Catalog snapshot")
	}
}

func TestEmptyReportExecutionProjectionPublishesNoDefinitionAuthority(t *testing.T) {
	input := neutralCompileInput()
	input.Projections = append(input.Projections, RuntimeProjectionInput{
		CatalogRevision: testRevision, AppInstallationID: testInstallA,
		CapabilityID: testReportCapability, Profile: ProjectionReportExecutionV1,
		ObjectAPINames: []string{"Unrelated", "WorkItem", "Workspace"},
	})
	snapshot := mustOpenSnapshot(t, mustCompile(t, input))
	artifact, err := ProjectRuntimeMetadataView(snapshot, ProjectionKey{
		AppInstallationID: testInstallA, CapabilityID: testReportCapability,
	})
	if err != nil {
		t.Fatalf("project empty report view: %v", err)
	}
	view := mustOpenView(t, artifact)
	if view.Profile() != ProjectionReportExecutionV1 || view.doc.Schema != RuntimeMetadataViewSchemaV10 {
		t.Fatalf("empty report view schema/profile = %q/%q", view.doc.Schema, view.Profile())
	}
	if len(view.doc.Reports) != 0 || len(view.doc.Definitions) != 0 {
		t.Fatalf("empty report view acquired definition authority: reports=%d definitions=%d", len(view.doc.Reports), len(view.doc.Definitions))
	}
	if _, _, _, ok := view.ReportDefinition("repository-fallback"); ok {
		t.Fatal("empty report view resolved a definition outside Catalog")
	}
}

func TestReportDefinitionFailsClosedBeforePublication(t *testing.T) {
	tests := map[string]func(*CompileInput){
		"mixed revision": func(input *CompileInput) {
			input.ReportDefinitions[0].CatalogRevision++
		},
		"product source token": func(input *CompileInput) {
			input.ReportDefinitions[0].Definition.SourceRef = "SalesforceReportType"
		},
		"product filter logic": func(input *CompileInput) {
			input.ReportDefinitions[0].Definition.FilterLogic = "1 AND 2"
		},
		"dashboard degradation": func(input *CompileInput) {
			input.ReportDefinitions[0].Definition.DegradeOnError = true
		},
		"missing field": func(input *CompileInput) {
			input.ReportDefinitions[0].Definition.Columns = []string{"Missing"}
		},
		"unknown scope": func(input *CompileInput) {
			input.ReportDefinitions[0].Definition.Scope = "teamMaybe"
		},
		"foreign definition in projection": func(input *CompileInput) {
			input.Projections[len(input.Projections)-2].DefinitionComponentIDs =
				append(input.Projections[len(input.Projections)-2].DefinitionComponentIDs, "definition.other")
		},
	}
	for name, mutate := range tests {
		t.Run(name, func(t *testing.T) {
			input := reportCompileInput()
			mutate(&input)
			if _, err := CompileCatalogSnapshot(input); err == nil {
				t.Fatal("CompileCatalogSnapshot accepted invalid report authority")
			}
		})
	}
}

func reportCompileInput() CompileInput {
	input := neutralCompileInput()
	input.ReportDefinitions = []ReportDefinitionInput{
		{
			CatalogRevision: testRevision, ComponentID: "component.report.open-work",
			DefinitionVersion: "3", ContentDigest: testReportDigest('a'),
			Definition: &models.ReportDefinition{
				ID: "report-open-work", Name: "Open work", SourceObject: "WorkItem",
				DisplayMode: "TABULAR", Scope: "mine", Columns: []string{"Id", "Subject"},
				Filters: []models.ReportFilter{{Column: "Done", Operator: "equals", Value: "false"}},
			},
		},
		{
			CatalogRevision: testRevision, ComponentID: "component.report.other-app",
			DefinitionVersion: "1", ContentDigest: testReportDigest('b'),
			Definition: &models.ReportDefinition{
				ID: "report-other-app", Name: "Other app", SourceObject: "Unrelated",
				DisplayMode: "SUMMARY", Grouping: []string{"Id"},
				Aggregates: []models.ReportAggregate{{Field: "Id", Type: "COUNT"}},
			},
		},
	}
	allObjects := []string{"Unrelated", "WorkItem", "Workspace"}
	input.Projections = append(input.Projections,
		RuntimeProjectionInput{
			CatalogRevision: testRevision, AppInstallationID: testInstallA, CapabilityID: testReportCapability,
			Profile: ProjectionReportExecutionV1, ObjectAPINames: allObjects,
			DefinitionComponentIDs: []string{"component.report.open-work"},
		},
		RuntimeProjectionInput{
			CatalogRevision: testRevision, AppInstallationID: testInstallB, CapabilityID: testReportCapability,
			Profile: ProjectionReportExecutionV1, ObjectAPINames: allObjects,
			DefinitionComponentIDs: []string{"component.report.other-app"},
		},
	)
	return input
}

func testReportDigest(value byte) Digest {
	return Digest("sha256:" + string(bytes.Repeat([]byte{value}, 64)))
}
