//go:build issue_tracker_conformance

package records

import (
	"context"
	"encoding/json"
	"errors"
	"reflect"
	"sort"
	"strings"
	"testing"
	"time"

	issuetrackerconformance "github.com/nexuscrm/app-packs-conformance/issue-tracker"
	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/metadata"
	"github.com/nexuscrm/permissions"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
	accesssemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordaccessv1"
)

const issueTrackerConformanceRecordID = "ISS-CONFORMANCE-001"

func TestIssueTrackerConformance_RealRuntimeRecordsAdapter(t *testing.T) {
	user := &models.UserSession{ID: "fixture-user", ProfileID: constants.ProfileStandardUser}
	record := models.SObject{
		constants.FieldID:      issueTrackerConformanceRecordID,
		constants.FieldOwnerID: user.ID,
		"Archived":             true,
		"Disabled":             false,
		"DueDate":              "2026-07-11",
		"EmptySummary":         "",
		"OptionalValue":        nil,
		"ProjectId":            "PRJ-001",
		"RetryCount":           int32(7),
		"SecretNote":           "must be removed by FLS",
		"StoryPoints":          json.Number("12.50"),
		"Subject":              "Non-CRM point read",
		"UpdatedAt":            "2026-07-11T09:10:11Z",
		"ZeroCount":            int64(0),
		"ZeroEstimate":         json.Number("0.00"),
	}
	selection := []issuetrackerconformance.FieldSelection{
		{Name: "UpdatedAt", Kind: issuetrackerconformance.FieldDateTime},
		{Name: "SecretNote", Kind: issuetrackerconformance.FieldString},
		{Name: "ZeroEstimate", Kind: issuetrackerconformance.FieldDecimal},
		{Name: "StoryPoints", Kind: issuetrackerconformance.FieldDecimal},
		{Name: "Subject", Kind: issuetrackerconformance.FieldString},
		{Name: "RetryCount", Kind: issuetrackerconformance.FieldInteger},
		{Name: "ProjectId", Kind: issuetrackerconformance.FieldReference},
		{Name: "OptionalValue", Kind: issuetrackerconformance.FieldString},
		{Name: "NeverStored", Kind: issuetrackerconformance.FieldString},
		{Name: "EmptySummary", Kind: issuetrackerconformance.FieldString},
		{Name: "DueDate", Kind: issuetrackerconformance.FieldDate},
		{Name: "Disabled", Kind: issuetrackerconformance.FieldBoolean},
		{Name: "Archived", Kind: issuetrackerconformance.FieldBoolean},
		{Name: "ZeroCount", Kind: issuetrackerconformance.FieldInteger},
	}
	wantFields := []issuetrackerconformance.RootField{
		{Name: "Archived", Kind: issuetrackerconformance.FieldBoolean, Value: true},
		{Name: "Disabled", Kind: issuetrackerconformance.FieldBoolean, Value: false},
		{Name: "DueDate", Kind: issuetrackerconformance.FieldDate, Value: "2026-07-11"},
		{Name: "EmptySummary", Kind: issuetrackerconformance.FieldString, Value: ""},
		{Name: "OptionalValue", Kind: issuetrackerconformance.FieldString, Null: true},
		{Name: "ProjectId", Kind: issuetrackerconformance.FieldReference, Value: "PRJ-001"},
		{Name: "RetryCount", Kind: issuetrackerconformance.FieldInteger, Value: int64(7)},
		{Name: "StoryPoints", Kind: issuetrackerconformance.FieldDecimal, Value: "12.50"},
		{Name: "Subject", Kind: issuetrackerconformance.FieldString, Value: "Non-CRM point read"},
		{Name: "UpdatedAt", Kind: issuetrackerconformance.FieldDateTime, Value: "2026-07-11T09:10:11Z"},
		{Name: "ZeroCount", Kind: issuetrackerconformance.FieldInteger, Value: int64(0)},
		{Name: "ZeroEstimate", Kind: issuetrackerconformance.FieldDecimal, Value: "0.00"},
	}

	tests := []struct {
		name       string
		ctx        func() context.Context
		adapter    *ContractService
		want       issuetrackerconformance.Outcome
		wantFields []issuetrackerconformance.RootField
	}{
		{
			name:       "typed-scalars-null-absence-fls-and-order",
			adapter:    newIssueTrackerPointReadAdapter(record, true, map[string]bool{"secretnote": false}),
			want:       issuetrackerconformance.OutcomeOK,
			wantFields: wantFields,
		},
		{
			name:    "missing-record",
			adapter: newIssueTrackerPointReadAdapter(nil, true, nil),
			want:    issuetrackerconformance.OutcomeMissing,
		},
		{
			name: "rls-denied-is-masked-as-missing",
			adapter: newIssueTrackerPointReadAdapter(cloneConformanceRecord(record, map[string]any{
				constants.FieldOwnerID: "different-owner",
			}), true, nil),
			want: issuetrackerconformance.OutcomeMissing,
		},
		{
			name:    "object-read-denied-is-forbidden",
			adapter: newIssueTrackerPointReadAdapter(record, false, nil),
			want:    issuetrackerconformance.OutcomeForbidden,
		},
		{
			name:    "context-canceled",
			adapter: newIssueTrackerPointReadAdapter(record, true, nil),
			ctx: func() context.Context {
				ctx, cancel := context.WithCancel(context.Background())
				cancel()
				return ctx
			},
			want: issuetrackerconformance.OutcomeCanceled,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var _ contract.Records = tt.adapter
			var _ issuetrackerconformance.PointReader = tt.adapter
			consumer, err := issuetrackerconformance.NewConsumer(tt.adapter, issuetrackerconformance.CurrentInProcessErrorClassifier)
			if err != nil {
				t.Fatalf("NewConsumer: %v", err)
			}
			ctx := context.Background()
			if tt.ctx != nil {
				ctx = tt.ctx()
			}
			got, err := consumer.Read(ctx, issuetrackerconformance.Request{
				RecordID: issueTrackerConformanceRecordID,
				Fields:   selection,
			}, user)
			if err != nil {
				t.Fatalf("Read: %v", err)
			}
			if got.Outcome != tt.want {
				t.Fatalf("outcome = %q, want %q", got.Outcome, tt.want)
			}
			if tt.wantFields == nil {
				tt.wantFields = []issuetrackerconformance.RootField{}
			}
			if !reflect.DeepEqual(got.Fields, tt.wantFields) {
				t.Fatalf("fields = %#v, want %#v", got.Fields, tt.wantFields)
			}
		})
	}
}

func TestIssueTrackerRecordAccessConformance_RealRuntimeRecordsAdapter(t *testing.T) {
	vectors, err := issuetrackerconformance.RecordAccessVectors()
	if err != nil {
		t.Fatal(err)
	}
	for _, vector := range vectors {
		vector := vector
		t.Run(vector.Name, func(t *testing.T) {
			adapter, user := newIssueTrackerRecordAccessAdapter(vector.Name)
			ctx := context.Background()
			cancel := func() {}
			switch vector.Name {
			case "canceled-before-runtime-work":
				ctx, cancel = context.WithCancel(ctx)
				cancel()
			case "deadline-exceeded-during-evaluation":
				ctx, cancel = context.WithTimeout(ctx, 5*time.Millisecond)
			}
			defer cancel()

			err := vector.Verify(ctx, func(ctx context.Context, request accesssemantic.Request) (accesssemantic.Response, error) {
				decisions, evaluateErr := adapter.EvaluateRecordAccess(ctx, request.ObjectID, request.RecordIDs, user)
				if evaluateErr != nil {
					return accesssemantic.Response{}, issueTrackerRecordAccessFailure(evaluateErr)
				}
				ids := make([]string, 0, len(decisions))
				for id, decision := range decisions {
					if decision.Read {
						ids = append(ids, id)
					}
				}
				sort.Strings(ids)
				response := accesssemantic.Response{
					Results: make([]accesssemantic.Result, 0, len(ids)),
					Receipts: accesssemantic.Receipts{
						ProtocolVersion: "1.0", CatalogRevision: 41, SchemaGeneration: 7,
						EngineGeneration: 9, SecurityEpoch: 13, CapabilityVersion: accesssemantic.CapabilityName,
					},
				}
				for _, id := range ids {
					decision := decisions[id]
					response.Results = append(response.Results, accesssemantic.Result{RecordID: id, Access: accesssemantic.Access{
						Read: decision.Read, Update: decision.Update, Delete: decision.Delete, Locked: decision.Locked,
					}})
				}
				return response, nil
			})
			if err != nil {
				t.Fatal(err)
			}
		})
	}
}

func issueTrackerRecordAccessFailure(err error) error {
	switch {
	case errors.Is(err, context.Canceled):
		return &accesssemantic.Failure{Category: accesssemantic.FailureCanceled}
	case errors.Is(err, context.DeadlineExceeded):
		return &accesssemantic.Failure{Category: accesssemantic.FailureDeadlineExceeded}
	}
	var status interface{ HTTPStatus() int }
	if errors.As(err, &status) && status.HTTPStatus() == 403 {
		return &accesssemantic.Failure{Category: accesssemantic.FailureForbidden}
	}
	return &accesssemantic.Failure{Category: accesssemantic.FailureInternal}
}

type issueTrackerRecordAccessRecordRepo struct {
	ports.RecordRepository
	records     map[string]map[string]models.SObject
	objectTypes map[string]string
	blockID     string
}

func (r *issueTrackerRecordAccessRecordRepo) FindMany(ctx context.Context, object string, ids []string) (map[string]models.SObject, error) {
	result := make(map[string]models.SObject)
	for _, id := range ids {
		if id == r.blockID {
			<-ctx.Done()
			return nil, ctx.Err()
		}
		if record := r.records[object][id]; record != nil {
			result[id] = cloneConformanceRecord(record, nil)
		}
	}
	return result, nil
}

func (r *issueTrackerRecordAccessRecordRepo) FindObjectTypesForIDs(_ context.Context, ids []string, _ []string) (map[string]string, error) {
	result := make(map[string]string)
	for _, id := range ids {
		if object := r.objectTypes[id]; object != "" {
			result[id] = object
		}
	}
	return result, nil
}

type issueTrackerRecordAccessMetadataRepo struct {
	ports.MetadataRepository
	schemas []*models.ObjectMetadata
}

func (r *issueTrackerRecordAccessMetadataRepo) GetAllSchemas(context.Context) ([]*models.ObjectMetadata, error) {
	return append([]*models.ObjectMetadata(nil), r.schemas...), nil
}

func (*issueTrackerRecordAccessMetadataRepo) GetAllRelationships(context.Context) ([]*models.Relationship, error) {
	return nil, nil
}

func (*issueTrackerRecordAccessMetadataRepo) GetSharingRules(context.Context, string) ([]*models.SharingRule, error) {
	return nil, nil
}

type issueTrackerRecordAccessPermissionRepo struct {
	ports.PermissionRepository
	objectPermissions map[string]models.ObjectPermissions
	manualReadIDs     map[string]bool
	approvalLockedID  string
}

func (*issueTrackerRecordAccessPermissionRepo) GetAllRoles(context.Context) ([]*models.UserRole, error) {
	return nil, nil
}

func (r *issueTrackerRecordAccessPermissionRepo) LoadEffectiveObjectPermission(_ context.Context, _ *models.UserSession, object string) (*models.ObjectPermissions, error) {
	permission, ok := r.objectPermissions[object]
	if !ok {
		return &models.ObjectPermissions{SobjectType: object}, nil
	}
	copy := permission
	return &copy, nil
}

func (*issueTrackerRecordAccessPermissionRepo) GetEffectiveSystemPermissions(context.Context, string, string) (map[string]bool, error) {
	return map[string]bool{"ReadWorkItem": true}, nil
}

func (*issueTrackerRecordAccessPermissionRepo) GetEffectiveCustomPermissions(context.Context, string, string) (map[string]bool, error) {
	return map[string]bool{}, nil
}

func (*issueTrackerRecordAccessPermissionRepo) IsUserInGroup(context.Context, string, string) (bool, error) {
	return false, nil
}

func (r *issueTrackerRecordAccessPermissionRepo) GetManualShareAccessLevels(_ context.Context, _ string, recordID, _ string) ([]string, error) {
	if r.manualReadIDs[recordID] {
		return []string{constants.PermRead}, nil
	}
	return nil, nil
}

func (*issueTrackerRecordAccessPermissionRepo) GetTeamMemberAccessLevel(context.Context, string, string, string) (*string, error) {
	return nil, nil
}

func (r *issueTrackerRecordAccessPermissionRepo) GetPendingApprovalLockInfo(_ context.Context, recordID string) (string, string, error) {
	if recordID == r.approvalLockedID {
		return "approval-process", "admin", nil
	}
	return "", "", nil
}

func (*issueTrackerRecordAccessPermissionRepo) HasPendingApprovalWorkitemForUser(context.Context, string, string) (bool, error) {
	return false, nil
}

func (*issueTrackerRecordAccessPermissionRepo) GetUserGroupIDs(context.Context, string) ([]string, error) {
	return nil, nil
}

func (*issueTrackerRecordAccessPermissionRepo) GetManualShareRecordIDs(context.Context, string, string) ([]string, error) {
	return nil, nil
}

func (*issueTrackerRecordAccessPermissionRepo) GetTeamMemberRecordIDs(context.Context, string, string) ([]string, error) {
	return nil, nil
}

func (*issueTrackerRecordAccessPermissionRepo) GetGroupMemberIDs(context.Context, []string) ([]string, error) {
	return nil, nil
}

func newIssueTrackerRecordAccessAdapter(vectorName string) (*ContractService, *models.UserSession) {
	user := &models.UserSession{ID: "fixture-user", ProfileID: constants.ProfileStandardUser}
	field := func(name string) models.FieldMetadata {
		return models.FieldMetadata{APIName: name, PhysicalName: name, Type: constants.FieldTypeString}
	}
	workItem := &models.ObjectMetadata{
		APIName: "WorkItem", SharingModel: constants.SharingModelPrivate,
		Fields:   []models.FieldMetadata{field(constants.FieldID), field(constants.FieldOwnerID), field("EditorId"), field("DeleterId"), {APIName: "Locked", PhysicalName: "Locked", Type: constants.FieldTypeBoolean}},
		LockRule: issueTrackerStringPointer("Locked == true"),
		AccessPolicy: &models.ObjectAccessPolicy{Mode: "derived",
			ObjectRead:   &models.AccessPolicyExpr{Kind: "systemPermission", APIName: "ReadWorkItem"},
			ObjectEdit:   &models.AccessPolicyExpr{Kind: "recordFieldEqualsCurrentUser", Field: "EditorId"},
			ObjectDelete: &models.AccessPolicyExpr{Kind: "recordFieldEqualsCurrentUser", Field: "DeleterId"},
		},
	}
	plain := func(name string) *models.ObjectMetadata {
		return &models.ObjectMetadata{APIName: name, SharingModel: constants.SharingModelPrivate,
			Fields: []models.FieldMetadata{field(constants.FieldID), field(constants.FieldOwnerID)}}
	}
	artifact := plain("Artifact")
	principal := plain("Principal")
	principal.AccessPolicy = &models.ObjectAccessPolicy{WriteValidation: &models.RecordWriteValidationAccessPolicy{
		Validators: []models.RecordWriteValidationPolicy{{Phase: "before_delete", Capability: constants.RecordWriteValidationUserDeleteLifecycle}},
	}}
	restricted := plain("RestrictedArtifact")
	denied := plain("DeniedArtifact")
	recordsByObject := map[string]map[string]models.SObject{
		"WorkItem": {
			"W-1": {constants.FieldID: "W-1", constants.FieldOwnerID: user.ID, "EditorId": user.ID, "DeleterId": "other", "Locked": false},
			"W-2": {constants.FieldID: "W-2", constants.FieldOwnerID: user.ID, "EditorId": "other", "DeleterId": user.ID, "Locked": false},
			"W-3": {constants.FieldID: "W-3", constants.FieldOwnerID: user.ID, "EditorId": user.ID, "DeleterId": user.ID, "Locked": false},
			"W-4": {constants.FieldID: "W-4", constants.FieldOwnerID: user.ID, "EditorId": user.ID, "DeleterId": user.ID, "Locked": true},
			"W-5": {constants.FieldID: "W-5", constants.FieldOwnerID: user.ID, "EditorId": user.ID, "DeleterId": user.ID, "Locked": false},
			"W-6": {constants.FieldID: "W-6", constants.FieldOwnerID: user.ID, "EditorId": user.ID, "DeleterId": user.ID, "Locked": false},
			"W-7": {constants.FieldID: "W-7", constants.FieldOwnerID: user.ID, "EditorId": "other", "DeleterId": "other", "Locked": false},
			"W-9": {constants.FieldID: "W-9", constants.FieldOwnerID: user.ID, "EditorId": user.ID, "DeleterId": user.ID, "Locked": false},
		},
		"Artifact": {
			"A-1": {constants.FieldID: "A-1", constants.FieldOwnerID: user.ID},
			"A-2": {constants.FieldID: "A-2", constants.FieldOwnerID: "other"},
			"A-3": {constants.FieldID: "A-3", constants.FieldOwnerID: "other"},
			"A-5": {constants.FieldID: "A-5", constants.FieldOwnerID: "other"},
		},
		"Principal":          {"P-1": {constants.FieldID: "P-1", constants.FieldOwnerID: user.ID}},
		"RestrictedArtifact": {"R-1": {constants.FieldID: "R-1", constants.FieldOwnerID: user.ID}},
		"DeniedArtifact":     {"DENIED-GROUP": {constants.FieldID: "DENIED-GROUP", constants.FieldOwnerID: user.ID}},
	}
	repo := &issueTrackerRecordAccessRecordRepo{
		records:     recordsByObject,
		objectTypes: map[string]string{"W-5": "WorkItem", "A-5": "Artifact", "DENIED-GROUP": "DeniedArtifact"},
	}
	if vectorName == "deadline-exceeded-during-evaluation" {
		repo.blockID = "W-9"
	}
	permissionRepo := &issueTrackerRecordAccessPermissionRepo{
		objectPermissions: map[string]models.ObjectPermissions{
			"Artifact":           {SobjectType: "Artifact", PermissionsRead: true, PermissionsEdit: true, PermissionsDelete: true},
			"Principal":          {SobjectType: "Principal", PermissionsRead: true, PermissionsEdit: true, PermissionsDelete: true},
			"RestrictedArtifact": {SobjectType: "RestrictedArtifact"},
			"DeniedArtifact":     {SobjectType: "DeniedArtifact"},
		},
		manualReadIDs: map[string]bool{"A-2": true, "A-5": true}, approvalLockedID: "W-3",
	}
	metadataService := metadata.NewMetadataService(&issueTrackerRecordAccessMetadataRepo{
		schemas: []*models.ObjectMetadata{workItem, artifact, principal, restricted, denied},
	}, nil, nil)
	permissionService := permissions.NewPermissionService(permissionRepo, metadataService, nil)
	persistenceService := &PersistenceService{repo: repo, metadata: metadataService, permissions: permissionService}
	return NewContractService(persistenceService), user
}

func issueTrackerStringPointer(value string) *string { return &value }

func newIssueTrackerPointReadAdapter(record models.SObject, grantRead bool, fieldRead map[string]bool) *ContractService {
	schema := &models.ObjectMetadata{
		APIName:      issuetrackerconformance.IssueObject,
		SharingModel: constants.SharingModelPrivate,
	}
	metaSvc := metadata.NewMetadataService(&stubMetaRepoRMR{schema: schema}, nil, nil)
	permissionSvc := permissions.NewPermissionService(&stubPermRepoRMR{
		grantRead: grantRead,
		fieldRead: fieldRead,
	}, metaSvc, nil)
	repo := &stubRecordRepoRMR{
		findOneFn: func(_ context.Context, objectName, recordID string) (models.SObject, error) {
			if !strings.EqualFold(objectName, issuetrackerconformance.IssueObject) || recordID != issueTrackerConformanceRecordID {
				return nil, nil
			}
			return record, nil
		},
	}
	return NewContractService(&PersistenceService{
		repo:        repo,
		permissions: permissionSvc,
		metadata:    metaSvc,
	})
}

func cloneConformanceRecord(record models.SObject, overrides map[string]any) models.SObject {
	clone := make(models.SObject, len(record)+len(overrides))
	for key, value := range record {
		clone[key] = value
	}
	for key, value := range overrides {
		clone[key] = value
	}
	return clone
}
