package files

import (
	"context"
	"errors"
	"fmt"
	"testing"

	"github.com/nexuscrm/metadata"
	"github.com/nexuscrm/metadata/catalogsnapshot"
	"github.com/nexuscrm/persistence"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordattachmentsv1"
)

func TestRuntimeAttachmentSourceResolverUsesOnlyCatalogDeclaredIdentifierSegment(t *testing.T) {
	view := runtimeAttachmentSourceTestView(t)
	metadataService, err := metadata.NewRuntimeMetadataService(func(context.Context) (*catalogsnapshot.RuntimeMetadataView, bool) { return view, true })
	if err != nil {
		t.Fatal(err)
	}
	fileRecord := authorizedSystemFileRecord("parent-1", "ignored", "spec.pdf")
	fileRecord[constants.FieldSysFile_ParentObjectAPIName] = "WorkItem"
	// Compatibility source resolution predates attachment delivery metadata.
	// These nullable facts must not hide an otherwise authorized file identity.
	delete(fileRecord, constants.FieldSysFile_MimeType)
	delete(fileRecord, constants.FieldSysFile_Checksum)
	records := &sourceFileRecordStore{
		parents: map[string]models.SObject{"WorkItem/parent-1": {constants.FieldID: "parent-1"}},
		files:   map[string]models.SObject{"file-1": fileRecord},
	}
	lookup := &runtimeAttachmentSourceLookupStub{result: map[string]string{"ABCDEF123456": "file-1"}}
	resolver, err := NewRuntimeAttachmentSourceResolver(lookup, records, allowFilePermissionChecker{}, metadataService)
	if err != nil {
		t.Fatal(err)
	}
	plan, ok := view.AttachmentSourceResolver("document_publication")
	if !ok {
		t.Fatal("serving resolver absent")
	}
	results, err := resolver.ResolveAttachmentSources(context.Background(), view, "WorkItem", "parent-1", plan, []string{"ABCDEF123456", "missing"}, &models.UserSession{ID: "user-1"})
	if err != nil {
		t.Fatal(err)
	}
	if len(results) != 2 || results[0].Outcome != semantic.SourceResolved || results[0].FileID != "file-1" ||
		results[1].Outcome != semantic.SourceNotFound {
		t.Fatalf("results = %#v", results)
	}
	if lookup.calls != 1 || lookup.plan.IdentifierSegment == nil || lookup.plan.IdentifierSegment.StartOffsetBytes != 3 ||
		lookup.plan.IdentifierSegment.LengthBytes != 12 || lookup.parentObjectID != "WorkItem" || lookup.parentRecordID != "parent-1" {
		t.Fatalf("lookup = %#v", lookup)
	}
}

func TestRuntimeAttachmentSourceResolverRejectsNonServingPlanBeforeLookup(t *testing.T) {
	view := runtimeAttachmentSourceTestView(t)
	metadataService, err := metadata.NewRuntimeMetadataService(func(context.Context) (*catalogsnapshot.RuntimeMetadataView, bool) { return view, true })
	if err != nil {
		t.Fatal(err)
	}
	lookup := &runtimeAttachmentSourceLookupStub{}
	resolver, err := NewRuntimeAttachmentSourceResolver(lookup, &sourceFileRecordStore{}, allowFilePermissionChecker{}, metadataService)
	if err != nil {
		t.Fatal(err)
	}
	legacy := catalogsnapshot.RecordAttachmentSourceResolver{Kind: catalogsnapshot.RecordAttachmentSourceByRecordV1}
	if _, err := resolver.ResolveAttachmentSources(context.Background(), view, "WorkItem", "parent-1", legacy, []string{"ABCDEF123456"}, &models.UserSession{ID: "user-1"}); err == nil {
		t.Fatal("legacy non-serving resolver was accepted")
	}
	if lookup.calls != 0 {
		t.Fatalf("lookup calls = %d, want zero", lookup.calls)
	}
}

func TestRuntimeAttachmentSourceResolverRejectsForgedCatalogRelationBeforeLookup(t *testing.T) {
	view := runtimeAttachmentSourceTestView(t)
	metadataService, err := metadata.NewRuntimeMetadataService(func(context.Context) (*catalogsnapshot.RuntimeMetadataView, bool) { return view, true })
	if err != nil {
		t.Fatal(err)
	}
	lookup := &runtimeAttachmentSourceLookupStub{}
	resolver, err := NewRuntimeAttachmentSourceResolver(lookup, &sourceFileRecordStore{}, allowFilePermissionChecker{}, metadataService)
	if err != nil {
		t.Fatal(err)
	}
	declared, ok := view.AttachmentSourceResolver("document_publication")
	if !ok {
		t.Fatal("serving resolver absent")
	}
	forged := declared
	forged.SourceObjectAPIName = "OtherObject"
	if _, err := resolver.ResolveAttachmentSources(context.Background(), view, "WorkItem", "parent-1", forged, []string{"ABCDEF123456"}, &models.UserSession{ID: "user-1"}); err == nil {
		t.Fatal("forged resolver relation was accepted")
	}
	if lookup.calls != 0 {
		t.Fatalf("lookup calls = %d, want zero", lookup.calls)
	}
}

func TestRuntimeAttachmentSourceResolverPropagatesFileAuthorizationCancellation(t *testing.T) {
	view := runtimeAttachmentSourceTestView(t)
	metadataService, err := metadata.NewRuntimeMetadataService(func(context.Context) (*catalogsnapshot.RuntimeMetadataView, bool) { return view, true })
	if err != nil {
		t.Fatal(err)
	}
	fileRecord := authorizedSystemFileRecord("parent-1", "ignored", "spec.pdf")
	fileRecord[constants.FieldSysFile_ParentObjectAPIName] = "WorkItem"
	records := &sourceFileRecordStore{
		parents: map[string]models.SObject{"WorkItem/parent-1": {constants.FieldID: "parent-1"}},
		files:   map[string]models.SObject{"file-1": fileRecord},
	}
	resolverPlan, ok := view.AttachmentSourceResolver("document_publication")
	if !ok {
		t.Fatal("serving resolver absent")
	}
	for _, wantErr := range []error{context.Canceled, context.DeadlineExceeded} {
		t.Run(wantErr.Error(), func(t *testing.T) {
			resolver, err := NewRuntimeAttachmentSourceResolver(
				&runtimeAttachmentSourceLookupStub{result: map[string]string{"ABCDEF123456": "file-1"}},
				records,
				attachmentSourceCancellationPermissionChecker{err: wantErr},
				metadataService,
			)
			if err != nil {
				t.Fatal(err)
			}
			result, err := resolver.ResolveAttachmentSources(context.Background(), view, "WorkItem", "parent-1", resolverPlan, []string{"ABCDEF123456"}, &models.UserSession{ID: "user-1"})
			if result != nil || !errors.Is(err, wantErr) {
				t.Fatalf("result=%#v err=%v, want no result and %v", result, err, wantErr)
			}
		})
	}
}

func TestRuntimeAttachmentSourceResolverCancellationWinsAtAuthorizationReturn(t *testing.T) {
	view := runtimeAttachmentSourceTestView(t)
	metadataService, err := metadata.NewRuntimeMetadataService(func(context.Context) (*catalogsnapshot.RuntimeMetadataView, bool) { return view, true })
	if err != nil {
		t.Fatal(err)
	}
	fileRecord := authorizedSystemFileRecord("parent-1", "ignored", "spec.pdf")
	fileRecord[constants.FieldSysFile_ParentObjectAPIName] = "WorkItem"
	checker := &attachmentSourceCancelAfterPermissionChecker{}
	resolver, err := NewRuntimeAttachmentSourceResolver(
		&runtimeAttachmentSourceLookupStub{result: map[string]string{"ABCDEF123456": "file-1"}},
		&sourceFileRecordStore{
			parents: map[string]models.SObject{"WorkItem/parent-1": {constants.FieldID: "parent-1"}},
			files:   map[string]models.SObject{"file-1": fileRecord},
		},
		checker,
		metadataService,
	)
	if err != nil {
		t.Fatal(err)
	}
	resolverPlan, ok := view.AttachmentSourceResolver("document_publication")
	if !ok {
		t.Fatal("serving resolver absent")
	}
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	checker.cancel = cancel
	result, err := resolver.ResolveAttachmentSources(ctx, view, "WorkItem", "parent-1", resolverPlan, []string{"ABCDEF123456"}, &models.UserSession{ID: "user-1"})
	if result != nil || !errors.Is(err, context.Canceled) {
		t.Fatalf("result=%#v err=%v, want no result and canceled", result, err)
	}
}

type attachmentSourceCancellationPermissionChecker struct{ err error }

func (c attachmentSourceCancellationPermissionChecker) CheckPermissionOrErrorWithUser(_ context.Context, objectName, operation string, _ *models.UserSession) error {
	if objectName == constants.TableFile && operation == constants.PermRead {
		return c.err
	}
	return fmt.Errorf("unexpected permission check %s/%s", objectName, operation)
}

type attachmentSourceCancelAfterPermissionChecker struct{ cancel context.CancelFunc }

func (c *attachmentSourceCancelAfterPermissionChecker) CheckPermissionOrErrorWithUser(_ context.Context, objectName, operation string, _ *models.UserSession) error {
	if objectName != constants.TableFile || operation != constants.PermRead {
		return fmt.Errorf("unexpected permission check %s/%s", objectName, operation)
	}
	if c.cancel != nil {
		c.cancel()
	}
	return nil
}

type runtimeAttachmentSourceLookupStub struct {
	result         map[string]string
	calls          int
	plan           persistence.RecordAttachmentSourcePlan
	parentObjectID string
	parentRecordID string
}

func (s *runtimeAttachmentSourceLookupStub) Lookup(_ context.Context, plan persistence.RecordAttachmentSourcePlan, parentObjectID, parentRecordID string, _ []string) (map[string]string, error) {
	s.calls++
	s.plan = plan
	s.parentObjectID = parentObjectID
	s.parentRecordID = parentRecordID
	return s.result, nil
}

func runtimeAttachmentSourceTestView(t *testing.T) *catalogsnapshot.RuntimeMetadataView {
	t.Helper()
	const revision catalogsnapshot.CatalogRevision = 1
	start := uint16(3)
	workItem := &models.ObjectMetadata{ID: "work-item", APIName: "WorkItem", Label: "Work Item", PluralLabel: "Work Items", SharingModel: constants.SharingModelPublicReadWrite, Fields: []models.FieldMetadata{
		{ID: "wi-id", APIName: constants.FieldID, Label: "ID", Type: constants.FieldTypeString, PhysicalName: "id", IsSystem: true},
		{ID: "wi-link", APIName: "DocumentLink", Label: "Document Link", Type: constants.FieldTypeRichtextarea, PhysicalName: "document_link"},
	}}
	publication := &models.ObjectMetadata{ID: "publication", APIName: "DocumentPublication", Label: "Publication", PluralLabel: "Publications", SharingModel: constants.SharingModelPublicReadWrite, ReadSource: &models.ObjectReadSourceConfig{Kind: "physical_table", PhysicalTable: "_System_DocumentPublication"}, Fields: []models.FieldMetadata{
		{ID: "pub-id", APIName: constants.FieldID, Label: "ID", Type: constants.FieldTypeString, PhysicalName: "id", IsSystem: true},
		{ID: "pub-version", APIName: "DocumentVersionId", Label: "Version", Type: constants.FieldTypeString, PhysicalName: "document_version_id"},
		{ID: "pub-deleted", APIName: constants.FieldIsDeleted, Label: "Deleted", Type: constants.FieldTypeBoolean, PhysicalName: "is_deleted"},
	}}
	artifact, err := catalogsnapshot.CompileCatalogSnapshot(catalogsnapshot.CompileInput{
		OrgID: "org-files", Lineage: catalogsnapshot.Lineage{CatalogRevision: revision, SchemaGeneration: 1, EngineGeneration: 1},
		Objects: []catalogsnapshot.ObjectInput{{CatalogRevision: revision, Metadata: publication}, {CatalogRevision: revision, Metadata: workItem}},
		AttachmentSourceResolvers: []catalogsnapshot.RecordAttachmentSourceResolverInput{{
			CatalogRevision: revision, ResolverID: "document_publication", Kind: catalogsnapshot.RecordAttachmentSourceByRecordV2,
			SourceObjectAPIName: "DocumentPublication", SourceFileReferenceFieldAPIName: "DocumentVersionId", SourceDeletedFieldAPIName: constants.FieldIsDeleted,
			SourceRecordMatcher: &catalogsnapshot.RecordAttachmentSourceRecordMatcher{Kind: catalogsnapshot.RecordAttachmentSourceMatchRecordIDSegmentV2, StartOffsetBytes: &start, LengthBytes: 12},
		}},
		AttachmentSourceFields: []catalogsnapshot.RecordAttachmentSourceFieldInput{{CatalogRevision: revision, ObjectAPIName: "WorkItem", FieldAPIName: "DocumentLink", ResolverID: "document_publication"}},
		Projections:            []catalogsnapshot.RuntimeProjectionInput{{CatalogRevision: revision, AppInstallationID: "install-files", CapabilityID: semantic.CapabilityName, Profile: catalogsnapshot.ProjectionRecordViewV1, ObjectAPINames: []string{"DocumentPublication", "WorkItem"}}},
	})
	if err != nil {
		t.Fatal(err)
	}
	snapshot, err := catalogsnapshot.OpenCatalogSnapshot(artifact)
	if err != nil {
		t.Fatal(err)
	}
	projected, err := catalogsnapshot.ProjectRuntimeMetadataView(snapshot, catalogsnapshot.ProjectionKey{AppInstallationID: "install-files", CapabilityID: semantic.CapabilityName})
	if err != nil {
		t.Fatal(err)
	}
	view, err := catalogsnapshot.OpenRuntimeMetadataView(projected)
	if err != nil {
		t.Fatal(err)
	}
	return view
}
