package records

import (
	"context"
	"database/sql"
	"strings"

	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/persistence"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

type groupMembershipClosureMutationKind string

const (
	groupMembershipClosureMutationInsert     groupMembershipClosureMutationKind = "insert"
	groupMembershipClosureMutationUpdate     groupMembershipClosureMutationKind = "update"
	groupMembershipClosureMutationDelete     groupMembershipClosureMutationKind = "delete"
	groupMembershipClosureMutationBulkInsert groupMembershipClosureMutationKind = "bulk_insert"
)

type groupMembershipClosureTxStateKey struct{}

type groupMembershipClosureTxState struct {
	dirty bool
}

func (ps *PersistenceService) runGroupMembershipAwareTransaction(ctx context.Context, objectName string, fn func(context.Context) error) error {
	return ps.runGroupMembershipAwareTransactionForObjects(ctx, []string{objectName}, fn)
}

func (ps *PersistenceService) runGroupMembershipAwarePessimisticTransaction(ctx context.Context, objectName string, fn func(context.Context) error) error {
	return ps.runGroupMembershipAwarePessimisticTransactionForObjects(ctx, []string{objectName}, fn)
}

func (ps *PersistenceService) runGroupMembershipAwareTransactionForObjects(ctx context.Context, objectNames []string, fn func(context.Context) error) error {
	withParentLockCoordinator := func(txCtx context.Context) error {
		return ps.runParentLockCoordinatorIfActive(txCtx, fn)
	}
	if !groupMembershipClosureHasSourceObject(objectNames) {
		return ps.RunInTransaction(ctx, withParentLockCoordinator)
	}

	if ps.txManager != nil && ps.txManager.IsTransactionActive(ctx) {
		if groupMembershipClosureStateFromContext(ctx) == nil {
			return appErrors.NewInternalError("group membership closure source mutation requires source-aware transaction", nil)
		}
		return withParentLockCoordinator(ctx)
	}

	if ps.rawDB == nil {
		return appErrors.NewInternalError("group membership closure source mutation requires raw database access", nil)
	}

	var transactionErr error
	lockErr := persistence.WithGroupMembershipClosureLock(ctx, ps.rawDB, func(lockedCtx context.Context) error {
		transactionErr = ps.RunInTransaction(lockedCtx, func(txCtx context.Context) error {
			// Retry attempts must not share derived-state bookkeeping. A failed
			// transaction may have marked or rebuilt its closure before rollback;
			// the next attempt must decide from its own mutations.
			attemptCtx := context.WithValue(txCtx, groupMembershipClosureTxStateKey{}, &groupMembershipClosureTxState{})
			if err := withParentLockCoordinator(attemptCtx); err != nil {
				return err
			}
			return ps.rebuildGroupMembershipClosureIfDirty(attemptCtx)
		})
		return transactionErr
	})
	if lockErr != nil && transactionErr == nil {
		return appErrors.NewInternalError("group membership closure lock failed", lockErr)
	}
	return lockErr
}

func (ps *PersistenceService) runGroupMembershipAwarePessimisticTransactionForObjects(ctx context.Context, objectNames []string, fn func(context.Context) error) error {
	_, err := ps.runGroupMembershipAwarePessimisticTransactionForObjectsWithOutcome(ctx, objectNames, fn)
	return err
}

// runGroupMembershipAwarePessimisticTransactionForObjectsWithOutcome is the
// Records-owned business-effect boundary for durable workers. It preserves the
// same lifecycle-batch, parent-lock, and group-membership closure state as the
// direct CRUD paths while exposing the terminal transaction outcome needed to
// distinguish a proven rollback from an ambiguous commit.
func (ps *PersistenceService) runGroupMembershipAwarePessimisticTransactionForObjectsWithOutcome(
	ctx context.Context,
	objectNames []string,
	fn func(context.Context) error,
) (ports.TransactionCompletionOutcome, error) {
	withParentLockCoordinator := func(txCtx context.Context) error {
		return ps.runParentLockCoordinatorIfActive(txCtx, fn)
	}
	if !groupMembershipClosureHasSourceObject(objectNames) {
		return ps.RunInPessimisticTransactionWithOutcome(ctx, withParentLockCoordinator)
	}

	if ps.txManager != nil && ps.txManager.IsTransactionActive(ctx) {
		if groupMembershipClosureStateFromContext(ctx) == nil {
			return "", appErrors.NewInternalError("group membership closure source mutation requires source-aware transaction", nil)
		}
		return ps.RunInPessimisticTransactionWithOutcome(ctx, withParentLockCoordinator)
	}

	if ps.rawDB == nil {
		return "", appErrors.NewInternalError("group membership closure source mutation requires raw database access", nil)
	}

	var transactionErr error
	var completion ports.TransactionCompletionOutcome
	lockErr := persistence.WithGroupMembershipClosureLock(ctx, ps.rawDB, func(lockedCtx context.Context) error {
		completion, transactionErr = ps.RunInPessimisticTransactionWithOutcome(lockedCtx, func(txCtx context.Context) error {
			// WithPessimisticRetry may invoke this callback more than once. Keep
			// dirty state attempt-local so a rollback cannot suppress or force the
			// derived closure rebuild in a later attempt.
			attemptCtx := context.WithValue(txCtx, groupMembershipClosureTxStateKey{}, &groupMembershipClosureTxState{})
			if err := withParentLockCoordinator(attemptCtx); err != nil {
				return err
			}
			return ps.rebuildGroupMembershipClosureIfDirty(attemptCtx)
		})
		return transactionErr
	})
	if lockErr != nil && transactionErr == nil {
		return completion, appErrors.NewInternalError("group membership closure lock failed", lockErr)
	}
	return completion, lockErr
}

func (ps *PersistenceService) markGroupMembershipClosureDirtyAfterMutation(ctx context.Context, objectName string, kind groupMembershipClosureMutationKind, changedFields models.SObject) error {
	if !groupMembershipClosureMutationAffectsClosure(objectName, kind, changedFields) {
		return nil
	}

	state := groupMembershipClosureStateFromContext(ctx)
	if state == nil {
		return appErrors.NewInternalError("group membership closure source mutation missing transaction state", nil)
	}
	state.dirty = true
	return nil
}

func (ps *PersistenceService) rebuildGroupMembershipClosureIfDirty(ctx context.Context) error {
	state := groupMembershipClosureStateFromContext(ctx)
	if state == nil || !state.dirty {
		return nil
	}

	var exec persistence.Executor
	if ps.txManager != nil {
		if rawTx := ps.txManager.ExtractTx(ctx); rawTx != nil {
			if tx, ok := rawTx.(*sql.Tx); ok {
				exec = tx
			}
		}
	}
	if exec == nil {
		return appErrors.NewInternalError("group membership closure rebuild requires an active SQL transaction", nil)
	}
	if err := persistence.RebuildGroupMembershipClosure(ctx, exec); err != nil {
		return err
	}
	state.dirty = false
	return nil
}

func groupMembershipClosureStateFromContext(ctx context.Context) *groupMembershipClosureTxState {
	if ctx == nil {
		return nil
	}
	state, _ := ctx.Value(groupMembershipClosureTxStateKey{}).(*groupMembershipClosureTxState)
	return state
}

func groupMembershipClosureHasSourceObject(objectNames []string) bool {
	for _, objectName := range objectNames {
		if groupMembershipClosureSourceObject(objectName) {
			return true
		}
	}
	return false
}

// RequiresSourceAwareRecordTransaction reports whether a record mutation needs
// the group-membership source-aware transaction path.
func RequiresSourceAwareRecordTransaction(objectName string) bool {
	return groupMembershipClosureSourceObject(objectName)
}

func groupMembershipClosureSourceObject(objectName string) bool {
	switch {
	case strings.EqualFold(objectName, constants.TableGroupMember):
		return true
	case strings.EqualFold(objectName, constants.TableGroup):
		return true
	case strings.EqualFold(objectName, constants.TableUser):
		return true
	case strings.EqualFold(objectName, constants.TableUserRole):
		return true
	default:
		return false
	}
}

func groupMembershipClosureMutationAffectsClosure(objectName string, kind groupMembershipClosureMutationKind, changedFields models.SObject) bool {
	if !groupMembershipClosureSourceObject(objectName) {
		return false
	}
	if strings.EqualFold(objectName, constants.TableGroupMember) {
		return true
	}
	switch kind {
	case groupMembershipClosureMutationInsert, groupMembershipClosureMutationDelete, groupMembershipClosureMutationBulkInsert:
		return true
	case groupMembershipClosureMutationUpdate:
		return groupMembershipClosureUpdateAffectsClosure(objectName, changedFields)
	default:
		return false
	}
}

func groupMembershipClosureUpdateAffectsClosure(objectName string, changedFields models.SObject) bool {
	switch {
	case strings.EqualFold(objectName, constants.TableGroup):
		return sObjectHasField(changedFields, constants.FieldIsDeleted) ||
			sObjectHasField(changedFields, constants.FieldGroup_Type) ||
			sObjectHasField(changedFields, constants.FieldGroup_UserRoleID)
	case strings.EqualFold(objectName, constants.TableUser):
		return sObjectHasField(changedFields, constants.FieldIsDeleted) ||
			sObjectHasField(changedFields, constants.FieldUserRoleID)
	case strings.EqualFold(objectName, constants.TableUserRole):
		return sObjectHasField(changedFields, constants.FieldIsDeleted) ||
			sObjectHasField(changedFields, constants.FieldUserRole_ParentRoleID)
	default:
		return false
	}
}

func sObjectHasField(data models.SObject, fieldName string) bool {
	for key := range data {
		if strings.EqualFold(key, fieldName) {
			return true
		}
	}
	return false
}
