package catalogsnapshot

import (
	"fmt"
	"sort"
)

func inputHasEffectivePermissionProjection(values []RuntimeProjectionInput) bool {
	for _, value := range values {
		if value.Profile == ProjectionEffectivePermissionsV1 {
			return true
		}
	}
	return false
}

func projectEffectivePermissionDefinitions(
	source *mutationDefinitionDocument,
	objects []catalogObject,
	included map[string]struct{},
) (*effectivePermissionDefinitionDocument, error) {
	if source == nil {
		return nil, conflict(
			ConflictInvalidProjection,
			"effectivePermissions",
			"effective-permissions projection requires an explicit record-type authority closure",
		)
	}
	recordTypesByObject := make(map[string][]EffectivePermissionRecordType)
	for i, recordType := range source.RecordTypes {
		if !recordType.IsActive {
			continue
		}
		objectKey := foldIdentity(recordType.ObjectAPIName)
		if _, ok := included[objectKey]; !ok {
			continue
		}
		for name, value := range map[string]string{
			"recordTypeId":  recordType.ID,
			"developerName": recordType.DeveloperName,
			"name":          recordType.Name,
		} {
			if err := requireCanonicalIdentity(
				fmt.Sprintf("effectivePermissions.recordTypes[%d].%s", i, name),
				value,
			); err != nil {
				return nil, err
			}
		}
		recordTypesByObject[objectKey] = append(
			recordTypesByObject[objectKey],
			EffectivePermissionRecordType{
				RecordTypeID: recordType.ID, DeveloperName: recordType.DeveloperName,
				Name: recordType.Name,
			},
		)
	}
	for key := range recordTypesByObject {
		sort.Slice(recordTypesByObject[key], func(i, j int) bool {
			return identityLess(
				recordTypesByObject[key][i].RecordTypeID,
				recordTypesByObject[key][j].RecordTypeID,
			)
		})
	}

	doc := &effectivePermissionDefinitionDocument{
		Objects: []EffectivePermissionObject{},
	}
	for _, object := range objects {
		resourceType := object.Metadata.APIName
		key := foldIdentity(resourceType)
		if _, ok := included[key]; !ok {
			continue
		}
		doc.Objects = append(doc.Objects, EffectivePermissionObject{
			ResourceType: resourceType,
			RecordTypes:  recordTypesByObject[key],
		})
	}
	if err := validateEffectivePermissionDefinitions(doc); err != nil {
		return nil, conflict(
			ConflictInvalidProjection, "effectivePermissions", "%v", err,
		)
	}
	return doc, nil
}

func validateEffectivePermissionDefinitions(doc *effectivePermissionDefinitionDocument) error {
	if doc == nil {
		return fmt.Errorf("effective-permission authority is absent")
	}
	seenObjects := make(map[string]struct{}, len(doc.Objects))
	lastObject := ""
	for objectIndex, object := range doc.Objects {
		path := fmt.Sprintf("objects[%d]", objectIndex)
		if err := requireCanonicalIdentity(path+".resourceType", object.ResourceType); err != nil {
			return err
		}
		objectKey := foldIdentity(object.ResourceType)
		if _, duplicate := seenObjects[objectKey]; duplicate ||
			(lastObject != "" && !identityLess(lastObject, object.ResourceType)) {
			return fmt.Errorf("%s is duplicated or not canonically ordered", path)
		}
		seenObjects[objectKey] = struct{}{}
		lastObject = object.ResourceType

		seenFields := make(map[string]struct{}, len(object.FieldNames))
		lastField := ""
		for fieldIndex, fieldName := range object.FieldNames {
			fieldPath := fmt.Sprintf("%s.fieldNames[%d]", path, fieldIndex)
			if err := requireCanonicalIdentity(fieldPath, fieldName); err != nil {
				return err
			}
			fieldKey := foldIdentity(fieldName)
			if _, duplicate := seenFields[fieldKey]; duplicate ||
				(lastField != "" && !identityLess(lastField, fieldName)) {
				return fmt.Errorf("%s is duplicated or not canonically ordered", fieldPath)
			}
			seenFields[fieldKey] = struct{}{}
			lastField = fieldName
		}

		seenRecordTypes := make(map[string]struct{}, len(object.RecordTypes))
		seenDeveloperNames := make(map[string]struct{}, len(object.RecordTypes))
		lastRecordType := ""
		for recordTypeIndex, recordType := range object.RecordTypes {
			recordTypePath := fmt.Sprintf("%s.recordTypes[%d]", path, recordTypeIndex)
			for name, value := range map[string]string{
				"recordTypeId":  recordType.RecordTypeID,
				"developerName": recordType.DeveloperName,
				"name":          recordType.Name,
			} {
				if err := requireCanonicalIdentity(recordTypePath+"."+name, value); err != nil {
					return err
				}
			}
			idKey := recordType.RecordTypeID
			developerKey := foldIdentity(recordType.DeveloperName)
			if _, duplicate := seenRecordTypes[idKey]; duplicate ||
				(lastRecordType != "" && !identityLess(lastRecordType, recordType.RecordTypeID)) {
				return fmt.Errorf("%s is duplicated or not canonically ordered", recordTypePath)
			}
			if _, duplicate := seenDeveloperNames[developerKey]; duplicate {
				return fmt.Errorf("%s has a duplicate developer name", recordTypePath)
			}
			seenRecordTypes[idKey] = struct{}{}
			seenDeveloperNames[developerKey] = struct{}{}
			lastRecordType = recordType.RecordTypeID
		}
	}
	return nil
}

// EffectivePermissionObject returns a detached, immutable projection for one
// scoped object. It never falls back to mutation or repository metadata.
func (v *RuntimeMetadataView) EffectivePermissionObject(
	resourceType string,
) (EffectivePermissionObject, bool) {
	if !v.SupportsEffectivePermissionServing() {
		return EffectivePermissionObject{}, false
	}
	index, ok := v.effectivePermissionObjectsByName[foldIdentity(resourceType)]
	if !ok || v.doc.EffectivePermissions == nil {
		return EffectivePermissionObject{}, false
	}
	object := v.doc.EffectivePermissions.Objects[index]
	object.FieldNames = append([]string(nil), object.FieldNames...)
	object.RecordTypes = append([]EffectivePermissionRecordType(nil), object.RecordTypes...)
	return object, true
}

func (v *RuntimeMetadataView) EffectivePermissionObjects() []EffectivePermissionObject {
	if !v.SupportsEffectivePermissionServing() {
		return nil
	}
	result := make([]EffectivePermissionObject, 0, len(v.doc.EffectivePermissions.Objects))
	for _, object := range v.doc.EffectivePermissions.Objects {
		copy, _ := v.EffectivePermissionObject(object.ResourceType)
		result = append(result, copy)
	}
	return result
}

func (v *RuntimeMetadataView) SupportsEffectivePermissionServing() bool {
	return v != nil && v.doc.Schema == RuntimeMetadataViewSchemaV21 &&
		v.doc.Profile == ProjectionEffectivePermissionsV1 &&
		v.doc.EffectivePermissions != nil && len(v.doc.Objects) != 0
}
