package records

import (
	"context"
	"fmt"
	"sort"
	"strings"

	"github.com/nexuscrm/kernel/pkg/apierr"
	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/recordlocks"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/metadatarel"
	"github.com/nexuscrm/shared/pkg/models"
)

type recycleBinOperationRepository interface {
	FindRecycleBinEntriesByDeleteOperation(ctx context.Context, operationID string) ([]models.SObject, error)
}

func recycleBinCascadeLineageParentLockKeys(tombstones []recycleBinMutationTombstone) []recordlocks.ParentKey {
	keys := make([]recordlocks.ParentKey, 0, len(tombstones))
	selected := make(map[string]struct{}, len(tombstones))
	for _, tombstone := range tombstones {
		selected[recycleBinTombstoneTreeKey(tombstone.objectName, tombstone.recordID)] = struct{}{}
	}
	for _, tombstone := range tombstones {
		if !tombstone.isCascadeChild() {
			continue
		}
		if _, ok := selected[recycleBinTombstoneTreeKey(tombstone.cascadeParentObjectName, tombstone.cascadeParentRecordID)]; !ok {
			continue
		}
		keys = append(keys, recordlocks.ParentKey{
			ObjectName: tombstone.cascadeParentObjectName,
			RecordID:   tombstone.cascadeParentRecordID,
		})
	}
	return recordlocks.OrderedParentKeys(keys)
}

func (ps *PersistenceService) loadRecycleBinCascadeRestoreTombstones(ctx context.Context, root recycleBinMutationTombstone) ([]recycleBinMutationTombstone, error) {
	if strings.TrimSpace(root.deleteOperationID) == "" {
		return ps.loadLegacyRecycleBinCascadeTombstones(ctx, root, "restore")
	}
	repo, ok := ps.repo.(recycleBinOperationRepository)
	if !ok {
		return nil, appErrors.NewInternalError("recycle-bin cascade restore requires operation lineage repository support", nil)
	}
	rows, err := repo.FindRecycleBinEntriesByDeleteOperation(ctx, root.deleteOperationID)
	if err != nil {
		return nil, appErrors.NewInternalError("failed to load recycle-bin delete operation tombstones", err)
	}
	operationTombstones := make([]recycleBinMutationTombstone, 0, len(rows))
	requestedSeen := false
	for _, row := range rows {
		t, err := recycleBinMutationTombstoneFromRow(row)
		if err != nil {
			return nil, err
		}
		if !strings.EqualFold(t.deleteOperationID, root.deleteOperationID) ||
			!strings.EqualFold(t.deleteRootObjectName, root.deleteRootObjectName) ||
			!strings.EqualFold(t.deleteRootRecordID, root.deleteRootRecordID) {
			continue
		}
		if strings.EqualFold(t.recordID, root.recordID) && strings.EqualFold(t.objectName, root.objectName) {
			requestedSeen = true
		}
		operationTombstones = append(operationTombstones, t)
	}
	if !requestedSeen {
		operationTombstones = append(operationTombstones, root)
	}
	sortRecycleBinRestoreTombstones(operationTombstones)
	if root.isDeleteOperationRoot() {
		if err := ps.validateRecycleBinCascadeTombstoneTree(ctx, root, operationTombstones); err != nil {
			return nil, err
		}
		return operationTombstones, nil
	}
	subtree, err := recycleBinCascadeMutationSubtree(root, operationTombstones, "restore")
	if err != nil {
		return nil, err
	}
	if err := ps.validateRecycleBinCascadeTombstoneSubtree(ctx, root, subtree); err != nil {
		return nil, err
	}
	return subtree, nil
}

func (t recycleBinMutationTombstone) isDeleteOperationRoot() bool {
	return strings.TrimSpace(t.deleteOperationID) != "" &&
		strings.EqualFold(t.deleteRootObjectName, t.objectName) &&
		strings.EqualFold(t.deleteRootRecordID, t.recordID)
}

func (t recycleBinMutationTombstone) isCascadeChild() bool {
	return strings.TrimSpace(t.cascadeParentObjectName) != "" ||
		strings.TrimSpace(t.cascadeParentRecordID) != "" ||
		strings.TrimSpace(t.cascadeFieldAPIName) != "" ||
		t.cascadeDepth > 0
}

func (ps *PersistenceService) validateRecycleBinCascadeTombstoneTree(ctx context.Context, root recycleBinMutationTombstone, tombstones []recycleBinMutationTombstone) error {
	if err := validateRecycleBinCascadeLineageTree(root, tombstones, "restore"); err != nil {
		return err
	}
	for _, tombstone := range tombstones {
		if !tombstone.isCascadeChild() {
			continue
		}
		if err := ps.validateRecycleBinCascadeEdgeMetadata(ctx, tombstone); err != nil {
			return err
		}
	}
	return nil
}

func (ps *PersistenceService) validateRecycleBinCascadeTombstoneSubtree(ctx context.Context, root recycleBinMutationTombstone, tombstones []recycleBinMutationTombstone) error {
	if err := validateRecycleBinCascadeLineageSubtree(root, tombstones, "restore"); err != nil {
		return err
	}
	for _, tombstone := range tombstones {
		if !tombstone.isCascadeChild() {
			continue
		}
		if err := ps.validateRecycleBinCascadeEdgeMetadata(ctx, tombstone); err != nil {
			return err
		}
	}
	return nil
}

func validateRecycleBinCascadeLineageTree(root recycleBinMutationTombstone, tombstones []recycleBinMutationTombstone, surface string) error {
	if len(tombstones) == 0 {
		return apierr.Conflict(fmt.Sprintf("cannot %s recycle-bin cascade tree because delete lineage is empty", surface))
	}
	rootKey := recycleBinTombstoneTreeKey(root.objectName, root.recordID)
	seen := make(map[string]recycleBinMutationTombstone, len(tombstones))
	rootCount := 0
	for _, tombstone := range tombstones {
		if !strings.EqualFold(tombstone.deleteOperationID, root.deleteOperationID) ||
			!strings.EqualFold(tombstone.deleteRootObjectName, root.deleteRootObjectName) ||
			!strings.EqualFold(tombstone.deleteRootRecordID, root.deleteRootRecordID) {
			return apierr.Conflict(fmt.Sprintf("cannot %s recycle-bin cascade tree because delete lineage root fields do not match", surface))
		}
		key := recycleBinTombstoneTreeKey(tombstone.objectName, tombstone.recordID)
		if key == "\x00" {
			return apierr.Conflict(fmt.Sprintf("cannot %s recycle-bin cascade tree because a lineage node is missing object or record identity", surface))
		}
		if _, ok := seen[key]; ok {
			return apierr.Conflict(fmt.Sprintf("cannot %s recycle-bin cascade tree because delete lineage contains duplicate nodes", surface))
		}
		seen[key] = tombstone
		if key == rootKey {
			rootCount++
			if tombstone.cascadeDepth != 0 || tombstone.isCascadeChild() {
				return apierr.Conflict(fmt.Sprintf("cannot %s recycle-bin cascade tree because the root lineage node is malformed", surface))
			}
			continue
		}
		if tombstone.cascadeDepth <= 0 ||
			strings.TrimSpace(tombstone.cascadeParentObjectName) == "" ||
			strings.TrimSpace(tombstone.cascadeParentRecordID) == "" ||
			strings.TrimSpace(tombstone.cascadeFieldAPIName) == "" {
			return apierr.Conflict(fmt.Sprintf("cannot %s recycle-bin cascade tree because a descendant lineage node is incomplete", surface))
		}
	}
	if rootCount != 1 {
		return apierr.Conflict(fmt.Sprintf("cannot %s recycle-bin cascade tree because the root lineage node is missing", surface))
	}
	for _, tombstone := range tombstones {
		key := recycleBinTombstoneTreeKey(tombstone.objectName, tombstone.recordID)
		if key == rootKey {
			continue
		}
		parentKey := recycleBinTombstoneTreeKey(tombstone.cascadeParentObjectName, tombstone.cascadeParentRecordID)
		parent, ok := seen[parentKey]
		if !ok {
			return apierr.Conflict(fmt.Sprintf("cannot %s recycle-bin cascade tree because a descendant parent tombstone is missing", surface))
		}
		if parent.cascadeDepth+1 != tombstone.cascadeDepth {
			return apierr.Conflict(fmt.Sprintf("cannot %s recycle-bin cascade tree because descendant depth does not match its recorded parent", surface))
		}
	}
	return nil
}

func validateRecycleBinCascadeLineageSubtree(root recycleBinMutationTombstone, tombstones []recycleBinMutationTombstone, surface string) error {
	if len(tombstones) == 0 {
		return apierr.Conflict(fmt.Sprintf("cannot %s recycle-bin cascade tree because delete lineage is empty", surface))
	}
	rootKey := recycleBinTombstoneTreeKey(root.objectName, root.recordID)
	seen := make(map[string]recycleBinMutationTombstone, len(tombstones))
	rootCount := 0
	var selectedRoot recycleBinMutationTombstone
	for _, tombstone := range tombstones {
		if !strings.EqualFold(tombstone.deleteOperationID, root.deleteOperationID) ||
			!strings.EqualFold(tombstone.deleteRootObjectName, root.deleteRootObjectName) ||
			!strings.EqualFold(tombstone.deleteRootRecordID, root.deleteRootRecordID) {
			return apierr.Conflict(fmt.Sprintf("cannot %s recycle-bin cascade tree because delete lineage root fields do not match", surface))
		}
		key := recycleBinTombstoneTreeKey(tombstone.objectName, tombstone.recordID)
		if key == "\x00" {
			return apierr.Conflict(fmt.Sprintf("cannot %s recycle-bin cascade tree because a lineage node is missing object or record identity", surface))
		}
		if _, ok := seen[key]; ok {
			return apierr.Conflict(fmt.Sprintf("cannot %s recycle-bin cascade tree because delete lineage contains duplicate nodes", surface))
		}
		seen[key] = tombstone
		if key == rootKey {
			rootCount++
			selectedRoot = tombstone
		}
	}
	if rootCount != 1 {
		return apierr.Conflict(fmt.Sprintf("cannot %s recycle-bin cascade tree because the requested lineage node is missing", surface))
	}
	if selectedRoot.cascadeDepth <= 0 ||
		strings.TrimSpace(selectedRoot.cascadeParentObjectName) == "" ||
		strings.TrimSpace(selectedRoot.cascadeParentRecordID) == "" ||
		strings.TrimSpace(selectedRoot.cascadeFieldAPIName) == "" {
		return apierr.Conflict(fmt.Sprintf("cannot %s recycle-bin cascade tree because the requested descendant lineage node is incomplete", surface))
	}
	for _, tombstone := range tombstones {
		key := recycleBinTombstoneTreeKey(tombstone.objectName, tombstone.recordID)
		if key == rootKey {
			continue
		}
		if tombstone.cascadeDepth <= 0 ||
			strings.TrimSpace(tombstone.cascadeParentObjectName) == "" ||
			strings.TrimSpace(tombstone.cascadeParentRecordID) == "" ||
			strings.TrimSpace(tombstone.cascadeFieldAPIName) == "" {
			return apierr.Conflict(fmt.Sprintf("cannot %s recycle-bin cascade tree because a descendant lineage node is incomplete", surface))
		}
		parentKey := recycleBinTombstoneTreeKey(tombstone.cascadeParentObjectName, tombstone.cascadeParentRecordID)
		parent, ok := seen[parentKey]
		if !ok {
			return apierr.Conflict(fmt.Sprintf("cannot %s recycle-bin cascade tree because a descendant parent tombstone is missing", surface))
		}
		if parent.cascadeDepth+1 != tombstone.cascadeDepth {
			return apierr.Conflict(fmt.Sprintf("cannot %s recycle-bin cascade tree because descendant depth does not match its recorded parent", surface))
		}
	}
	return nil
}

func recycleBinTombstoneTreeKey(objectName, recordID string) string {
	objectName = strings.ToLower(strings.TrimSpace(objectName))
	recordID = strings.ToLower(strings.TrimSpace(recordID))
	if objectName == "" || recordID == "" {
		return "\x00"
	}
	return objectName + "|" + recordID
}

func (ps *PersistenceService) loadLegacyRecycleBinCascadeTombstones(ctx context.Context, root recycleBinMutationTombstone, surface string) ([]recycleBinMutationTombstone, error) {
	if strings.TrimSpace(root.deleteOperationID) != "" {
		return nil, apierr.Conflict(fmt.Sprintf("cannot %s legacy recycle-bin cascade tree because delete lineage is already present", surface))
	}
	return []recycleBinMutationTombstone{root}, nil
}

func (ps *PersistenceService) validateRecycleBinCascadeEdgeMetadata(ctx context.Context, tombstone recycleBinMutationTombstone) error {
	if ps == nil || ps.metadata == nil {
		return apierr.Internal(fmt.Errorf("recycle-bin cascade validation: metadata service is not wired"))
	}
	childSchema := ps.metadata.GetSchema(ctx, tombstone.objectName)
	if childSchema == nil {
		return apierr.Conflict("cannot restore recycle-bin cascade tree because a descendant object is no longer known")
	}
	field := models.FindField(childSchema, tombstone.cascadeFieldAPIName)
	if field == nil || !metadatarel.FieldReferencesParent(*field, tombstone.cascadeParentObjectName) {
		return apierr.Conflict("cannot restore recycle-bin cascade tree because a descendant edge no longer matches relationship metadata")
	}
	relationshipsByField := indexRelationshipsByParentAndField(ps.metadata.GetRelationships(ctx, childSchema.APIName))
	deleteSemantics := metadatarel.ResolveDeleteSemantics(*field, relationshipsByField[strings.ToLower(tombstone.cascadeParentObjectName)+"|"+strings.ToLower(field.APIName)])
	if !deleteSemantics.Cascade {
		return apierr.Conflict("cannot restore recycle-bin cascade tree because a descendant edge is no longer cascade-delete metadata")
	}
	return nil
}

func (ps *PersistenceService) validateRecycleBinRestoreCascadeRecords(ctx context.Context, tombstones []recycleBinMutationTombstone) error {
	for _, tombstone := range tombstones {
		if !tombstone.isCascadeChild() {
			continue
		}
		lockedRecord, err := ps.repo.GetLock(ctx, tombstone.objectName, tombstone.recordID)
		if err != nil {
			return appErrors.NewInternalError(fmt.Sprintf("failed to lock restore cascade descendant %s/%s", tombstone.objectName, tombstone.recordID), err)
		}
		if lockedRecord == nil || !isGuardFieldTruthy(lockedRecord[constants.FieldIsDeleted]) {
			return apierr.Conflict("cannot restore recycle-bin cascade tree because a descendant row is no longer deleted")
		}
		if err := validateRecycleBinCascadeRecordStillMatchesLineage(tombstone, lockedRecord, "restore"); err != nil {
			return err
		}
	}
	return nil
}

func (ps *PersistenceService) validateRecycleBinRestoreCascadeCompleteness(ctx context.Context, tombstones []recycleBinMutationTombstone) error {
	candidateKeys := make(map[string]struct{}, len(tombstones))
	for _, tombstone := range tombstones {
		candidateKeys[recycleBinTombstoneTreeKey(tombstone.objectName, tombstone.recordID)] = struct{}{}
	}
	for _, tombstone := range tombstones {
		for _, edge := range ps.physicalCascadeChildEdges(ctx, tombstone.objectName) {
			deletedChildren, err := ps.repo.GetDeletedChildren(ctx, edge.childObjectName, edge.fieldAPIName, tombstone.recordID)
			if err != nil {
				return appErrors.NewInternalError(
					fmt.Sprintf("failed to recheck deleted children of %s via %s before recycle-bin restore", edge.childObjectName, edge.fieldAPIName),
					err,
				)
			}
			for _, child := range deletedChildren {
				childID := strings.TrimSpace(stringValue(child[constants.FieldID]))
				if childID == "" {
					continue
				}
				lockedChild, err := ps.repo.GetLock(ctx, edge.childObjectName, childID)
				if err != nil {
					return appErrors.NewInternalError(
						fmt.Sprintf("failed to lock deleted child %s/%s before recycle-bin restore", edge.childObjectName, childID),
						err,
					)
				}
				if lockedChild == nil ||
					!isGuardFieldTruthy(lockedChild[constants.FieldIsDeleted]) ||
					!strings.EqualFold(strings.TrimSpace(stringValue(lockedChild[edge.fieldAPIName])), tombstone.recordID) {
					continue
				}
				if _, ok := candidateKeys[recycleBinTombstoneTreeKey(edge.childObjectName, childID)]; !ok {
					return apierr.Conflict("cannot restore recycle-bin record because deleted non-lineage cascade children remain")
				}
			}
		}
	}
	return nil
}

func validateRecycleBinCascadeRecordStillMatchesLineage(tombstone recycleBinMutationTombstone, record models.SObject, surface string) error {
	if !tombstone.isCascadeChild() {
		return nil
	}
	if !strings.EqualFold(strings.TrimSpace(stringValue(record[tombstone.cascadeFieldAPIName])), tombstone.cascadeParentRecordID) {
		return apierr.Conflict(fmt.Sprintf("cannot %s recycle-bin cascade tree because a descendant row no longer matches its recorded cascade edge", surface))
	}
	return nil
}

func sortRecycleBinRestoreTombstones(tombstones []recycleBinMutationTombstone) {
	sort.SliceStable(tombstones, func(i, j int) bool {
		if tombstones[i].cascadeDepth != tombstones[j].cascadeDepth {
			return tombstones[i].cascadeDepth < tombstones[j].cascadeDepth
		}
		if !strings.EqualFold(tombstones[i].objectName, tombstones[j].objectName) {
			return strings.ToLower(tombstones[i].objectName) < strings.ToLower(tombstones[j].objectName)
		}
		return strings.ToLower(tombstones[i].recordID) < strings.ToLower(tombstones[j].recordID)
	})
}

func recycleBinTombstoneObjectNames(tombstones []recycleBinMutationTombstone) []string {
	if len(tombstones) == 0 {
		return nil
	}
	seen := map[string]struct{}{}
	out := make([]string, 0, len(tombstones))
	for _, tombstone := range tombstones {
		objectName := strings.TrimSpace(tombstone.objectName)
		if objectName == "" {
			continue
		}
		key := strings.ToLower(objectName)
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, objectName)
	}
	return out
}

func sameRecycleBinRestoreCandidate(expected, actual recycleBinMutationTombstone) bool {
	return strings.EqualFold(expected.id, actual.id) &&
		strings.EqualFold(expected.recordID, actual.recordID) &&
		strings.EqualFold(expected.objectName, actual.objectName) &&
		strings.EqualFold(expected.deleteOperationID, actual.deleteOperationID) &&
		strings.EqualFold(expected.deleteRootObjectName, actual.deleteRootObjectName) &&
		strings.EqualFold(expected.deleteRootRecordID, actual.deleteRootRecordID) &&
		strings.EqualFold(expected.cascadeParentObjectName, actual.cascadeParentObjectName) &&
		strings.EqualFold(expected.cascadeParentRecordID, actual.cascadeParentRecordID) &&
		strings.EqualFold(expected.cascadeFieldAPIName, actual.cascadeFieldAPIName) &&
		expected.cascadeDepth == actual.cascadeDepth
}
