package records

import (
	"context"
	"fmt"
	"reflect"
	"strings"
	"sync"

	"github.com/nexuscrm/kernel/pkg/schemacatalog"
)

// PermissionStateAuthority is the narrow transaction-bound revision seam used
// by generic record mutations. The persistence implementation owns SQL,
// transaction validation, and once-per-outer-transaction coalescing.
type PermissionStateAuthority interface {
	RequestAdvanceIfChanged(context.Context, bool) error
	RequestPermissionSetAuthoringAdvance(context.Context, string) error
}

// SetPermissionStateAuthority installs the shared revision authority for
// production mutation composition graphs and focused tests.
func (ps *PersistenceService) SetPermissionStateAuthority(authority PermissionStateAuthority) error {
	if ps == nil || authority == nil {
		return fmt.Errorf("records: permission state authority is required")
	}
	ps.permissionStateAuthority = authority
	return nil
}

var (
	permissionStateMutationInventoryOnce sync.Once
	permissionStateMutationInventory     schemacatalog.PermissionStateMutationInventory
	permissionStateMutationInventoryErr  error
)

func managedPermissionStateMutationSurface(tableName string) (schemacatalog.PermissionStateMutationSurface, bool, error) {
	permissionStateMutationInventoryOnce.Do(func() {
		permissionStateMutationInventory, permissionStateMutationInventoryErr =
			schemacatalog.ManagedPermissionStateMutationInventory()
	})
	if permissionStateMutationInventoryErr != nil {
		return schemacatalog.PermissionStateMutationSurface{}, false, permissionStateMutationInventoryErr
	}
	surface, ok := permissionStateMutationInventory.SurfaceForTable(strings.TrimSpace(tableName))
	return surface, ok, nil
}

func managedPermissionStateDeleteSurfaces(tableName string) ([]schemacatalog.PermissionStateMutationSurface, error) {
	permissionStateMutationInventoryOnce.Do(func() {
		permissionStateMutationInventory, permissionStateMutationInventoryErr =
			schemacatalog.ManagedPermissionStateMutationInventory()
	})
	if permissionStateMutationInventoryErr != nil {
		return nil, permissionStateMutationInventoryErr
	}
	return permissionStateMutationInventory.SurfacesAffectedByDelete(tableName), nil
}

func (ps *PersistenceService) permissionStateRequiresPessimisticTransaction(tableName string) bool {
	_, managed, err := managedPermissionStateMutationSurface(tableName)
	return err != nil || managed || managedPermissionSetDefinitionWriteGuardRequiresPessimisticTransaction(tableName)
}

func (ps *PersistenceService) permissionStateDeleteRequiresPessimisticTransaction(tableName string) bool {
	surfaces, err := managedPermissionStateDeleteSurfaces(tableName)
	return err != nil || len(surfaces) > 0
}

func (ps *PersistenceService) requestPermissionStateAdvanceForMutation(
	ctx context.Context,
	tableName string,
	before map[string]interface{},
	after map[string]interface{},
) error {
	return ps.requestPermissionStateAdvanceForRecordMutation(ctx, tableName, before, after, false)
}

// aggregateOwned is a private, call-local fact from the prepared Records
// applicator, not an inheritable context privilege. A nested lifecycle write
// must still invalidate the definition it changes.
func (ps *PersistenceService) requestPermissionStateAdvanceForRecordMutation(
	ctx context.Context, tableName string, before, after map[string]interface{}, aggregateOwned bool,
) error {
	changed, err := permissionStateSemanticProjectionChanged(tableName, before, after)
	if err != nil {
		return err
	}
	if !changed {
		return nil
	}
	if ps == nil || ps.permissionStateAuthority == nil {
		return fmt.Errorf("records: permission state authority is required")
	}
	if !aggregateOwned {
		if err := ps.requestPermissionSetRevisionForRecordMutation(ctx, tableName, before, after); err != nil {
			return err
		}
	}
	return ps.permissionStateAuthority.RequestAdvanceIfChanged(ctx, true)
}

func (ps *PersistenceService) requestPermissionStateAdvanceForDelete(
	ctx context.Context,
	tableName string,
	before map[string]interface{},
	after map[string]interface{},
) error {
	changed, err := permissionStateDeleteProjectionChanged(tableName, before, after)
	if err != nil {
		return err
	}
	if !changed {
		return nil
	}
	if ps == nil || ps.permissionStateAuthority == nil {
		return fmt.Errorf("records: permission state authority is required")
	}
	if err := ps.requestPermissionSetRevisionForRecordMutation(ctx, tableName, before, after); err != nil {
		return err
	}
	return ps.permissionStateAuthority.RequestAdvanceIfChanged(ctx, true)
}

func (ps *PersistenceService) requestPermissionSetRevisionForRecordMutation(ctx context.Context, tableName string, before, after map[string]interface{}) error {
	relation, found, err := managedPermissionSetDefinitionRelation(tableName)
	if err != nil || !found || relation.Role != schemacatalog.PermissionSetDefinitionRoleModeledGrant {
		return err
	}
	// Both identities matter if a permitted lifecycle rebinds the parent.
	// Existing canonical parent planning locks both before child DML.
	seen := make(map[string]bool, 2)
	for _, record := range []map[string]interface{}{before, after} {
		value, _ := permissionStateRecordField(record, relation.ParentField)
		id, _ := value.(string)
		id = strings.TrimSpace(id)
		if id == "" || seen[id] {
			continue
		}
		seen[id] = true
		if err := ps.permissionStateAuthority.RequestPermissionSetAuthoringAdvance(ctx, id); err != nil {
			return err
		}
	}
	return nil
}

func permissionStateDeleteProjectionChanged(
	tableName string,
	before map[string]interface{},
	after map[string]interface{},
) (bool, error) {
	if before == nil {
		return false, nil
	}
	surfaces, err := managedPermissionStateDeleteSurfaces(tableName)
	if err != nil || len(surfaces) > 0 {
		return len(surfaces) > 0, err
	}
	return permissionStateSemanticProjectionChanged(tableName, before, after)
}

func permissionStateSemanticProjectionChanged(
	tableName string,
	before map[string]interface{},
	after map[string]interface{},
) (bool, error) {
	if before != nil && after == nil {
		surfaces, err := managedPermissionStateDeleteSurfaces(tableName)
		return len(surfaces) > 0, err
	}
	surface, managed, err := managedPermissionStateMutationSurface(tableName)
	if err != nil || !managed {
		return false, err
	}
	if before == nil || after == nil {
		return before != nil || after != nil, nil
	}
	for _, fieldName := range surface.SemanticFields {
		beforeValue, beforePresent := permissionStateRecordField(before, fieldName)
		afterValue, afterPresent := permissionStateRecordField(after, fieldName)
		if beforePresent != afterPresent || !reflect.DeepEqual(beforeValue, afterValue) {
			return true, nil
		}
	}
	return false, nil
}

func permissionStateRecordField(record map[string]interface{}, fieldName string) (interface{}, bool) {
	if record == nil {
		return nil, false
	}
	if value, ok := record[fieldName]; ok {
		return value, true
	}
	for key, value := range record {
		if strings.EqualFold(key, fieldName) {
			return value, true
		}
	}
	return nil, false
}
