package records

import (
	"context"
	"errors"
	"reflect"
	"sort"
	"strings"

	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/kernel/pkg/recordlocks"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/fieldpermissions"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/permissionauthoringv1"
)

// PermissionAuthoringRecordMutationPlanner keeps ObjectPermissions and
// FieldPermissions on the canonical Records lifecycle while the Permissions
// aggregate owns the outer transaction and convergence disposition.
type PermissionAuthoringRecordMutationPlanner struct {
	records     *PersistenceService
	permissions ports.PermissionAuthoringGrantReader
}

func NewPermissionAuthoringRecordMutationPlanner(
	records *PersistenceService,
	permissions ports.PermissionAuthoringGrantReader,
) (*PermissionAuthoringRecordMutationPlanner, error) {
	if records == nil || permissions == nil {
		return nil, errors.New("permission authoring Records planner dependencies are incomplete")
	}
	return &PermissionAuthoringRecordMutationPlanner{records: records, permissions: permissions}, nil
}

type permissionAuthoringRecordOperation struct {
	apply       func(context.Context) error
	finalRecord models.Record
}

type permissionAuthoringRecordMutationPlan struct {
	records             *PersistenceService
	transactionIdentity interface{}
	operations          []permissionAuthoringRecordOperation
	changes             permissionauthoringv1.Changes
}

func (plan *permissionAuthoringRecordMutationPlan) Changed() bool {
	return plan != nil && (len(plan.changes.Objects) != 0 || len(plan.changes.Fields) != 0)
}

func (plan *permissionAuthoringRecordMutationPlan) FinalChanges() permissionauthoringv1.Changes {
	if plan == nil {
		return permissionauthoringv1.Changes{}
	}
	return permissionauthoringv1.Changes{
		Objects: append([]permissionauthoringv1.ObjectGrantChange(nil), plan.changes.Objects...),
		Fields:  append([]permissionauthoringv1.FieldGrantChange(nil), plan.changes.Fields...),
	}
}

func (plan *permissionAuthoringRecordMutationPlan) Apply(ctx context.Context) error {
	if plan == nil {
		return errors.New("permission authoring Records plan is unavailable")
	}
	transactionIdentity, err := permissionAuthoringRecordsTransactionIdentity(ctx, plan.records)
	if err != nil {
		return err
	}
	if transactionIdentity != plan.transactionIdentity {
		return errors.New("permission authoring Records plan escaped its preparing transaction")
	}
	ctx = withPermissionAuthoringAggregateRecords(ctx)
	return plan.records.RunWithParentLockCoordinator(ctx, func(coordinatorCtx context.Context) error {
		for _, operation := range plan.operations {
			if err := operation.apply(coordinatorCtx); err != nil {
				return err
			}
		}
		return nil
	})
}

func permissionAuthoringRecordsTransactionIdentity(ctx context.Context, records *PersistenceService) (interface{}, error) {
	if records == nil || records.txManager == nil || !records.txManager.IsTransactionActive(ctx) {
		return nil, errors.New("permission authoring Records plan requires an active transaction")
	}
	transactionIdentity := records.txManager.ExtractTx(ctx)
	if transactionIdentity == nil || !reflect.TypeOf(transactionIdentity).Comparable() {
		return nil, errors.New("permission authoring Records plan requires a comparable transaction identity")
	}
	return transactionIdentity, nil
}

func (planner *PermissionAuthoringRecordMutationPlanner) PlanPermissionAuthoringRecordMutation(
	ctx context.Context,
	permissionSetID string,
	objectChanges []permissionauthoringv1.ObjectGrantChange,
	fieldChanges []permissionauthoringv1.FieldGrantChange,
	actor *models.UserSession,
) (ports.PermissionAuthoringRecordMutationPlan, error) {
	if planner == nil || planner.records == nil || planner.permissions == nil ||
		strings.TrimSpace(permissionSetID) == "" || actor == nil || strings.TrimSpace(actor.ID) == "" {
		return nil, errors.New("permission authoring Records planner is unavailable")
	}
	// The aggregate has already fenced Permission State and authorized the
	// current actor. This transaction-local port is therefore a trusted child
	// lifecycle call, not a second public CRUD authorization surface. Records
	// still owns validation, write hooks, row audit, and the physical mutation.
	ctx = withPermissionAuthoringAggregateRecords(ctx)
	transactionIdentity, err := permissionAuthoringRecordsTransactionIdentity(ctx, planner.records)
	if err != nil {
		return nil, err
	}
	plan := &permissionAuthoringRecordMutationPlan{
		records: planner.records, transactionIdentity: transactionIdentity,
	}
	err = planner.records.RunWithParentLockCoordinator(ctx, func(coordinatorCtx context.Context) error {
		// Register the aggregate root with the Records global parent-lock
		// coordinator before any child row is read or locked. The outer owner
		// already holds this PermissionSet FOR UPDATE; reacquiring it is
		// idempotent and gives Records explicit ordering evidence.
		if _, err := planner.records.LockParentRows(coordinatorCtx, []recordlocks.ParentKey{{
			ObjectName: constants.TablePermissionSet,
			RecordID:   permissionSetID,
		}}); err != nil {
			return err
		}
		if err := planner.planObjects(coordinatorCtx, permissionSetID, objectChanges, actor, plan); err != nil {
			return err
		}
		return planner.planFields(coordinatorCtx, permissionSetID, fieldChanges, actor, plan)
	})
	if err != nil {
		return nil, err
	}
	return plan, nil
}

func (planner *PermissionAuthoringRecordMutationPlanner) planObjects(
	ctx context.Context,
	permissionSetID string,
	changes []permissionauthoringv1.ObjectGrantChange,
	actor *models.UserSession,
	plan *permissionAuthoringRecordMutationPlan,
) error {
	if len(changes) == 0 {
		return nil
	}
	rows, err := planner.permissions.ListPermissionAuthoringObjectGrants(ctx, permissionSetID)
	if err != nil {
		return err
	}
	current := make(map[string]ports.PermissionAuthoringObjectGrant, len(rows))
	for _, row := range rows {
		current[strings.ToLower(strings.TrimSpace(row.ResourceType))] = row
	}
	for _, change := range changes {
		identity := permissionAuthoringObjectGrantIdentity(permissionSetID, change.ResourceType)
		row, found := current[strings.ToLower(strings.TrimSpace(change.ResourceType))]
		if found && !row.Deleted && permissionAuthoringObjectValuesEqual(row, change) {
			continue
		}
		updates := permissionAuthoringObjectRecord(permissionSetID, change, false)
		if found {
			if strings.TrimSpace(row.ID) == "" {
				return errors.New("permission authoring Records planner found ObjectPermissions without identity")
			}
			rowID := row.ID
			if row.Deleted {
				if permissionAuthoringObjectChangeEmpty(change) {
					if _, err := planner.prepareInsertOperation(
						ctx, constants.TableObjectPermissions, identity,
						permissionAuthoringObjectRecord(permissionSetID, change, true), actor,
					); err != nil {
						return err
					}
					continue
				}
				operation, err := planner.prepareRestoreOperation(
					ctx, constants.TableObjectPermissions, rowID, identity, updates, actor,
				)
				if err != nil {
					return err
				}
				final := permissionAuthoringObjectChangeFromRecord(operation.finalRecord)
				if permissionAuthoringObjectChangeEmpty(final) {
					continue
				}
				plan.operations = append(plan.operations, operation)
				plan.changes.Objects = append(plan.changes.Objects, final)
				continue
			}
			operation, changed, err := planner.prepareUpdateOperation(
				ctx, constants.TableObjectPermissions, row.ID, identity, updates, actor,
			)
			if err != nil {
				return err
			}
			if changed {
				plan.operations = append(plan.operations, operation)
				plan.changes.Objects = append(plan.changes.Objects, permissionAuthoringObjectChangeFromRecord(operation.finalRecord))
			}
			continue
		}
		if permissionAuthoringObjectChangeEmpty(change) {
			// Validate the referenced resource through the create lifecycle even
			// though an all-false absent grant remains a semantic no-op.
			if _, err := planner.prepareInsertOperation(
				ctx, constants.TableObjectPermissions, identity,
				permissionAuthoringObjectRecord(permissionSetID, change, true), actor,
			); err != nil {
				return err
			}
			continue
		}
		insert := permissionAuthoringObjectRecord(permissionSetID, change, true)
		operation, err := planner.prepareInsertOperation(ctx, constants.TableObjectPermissions, identity, insert, actor)
		if err != nil {
			return err
		}
		final := permissionAuthoringObjectChangeFromRecord(operation.finalRecord)
		if permissionAuthoringObjectChangeEmpty(final) {
			continue
		}
		plan.operations = append(plan.operations, operation)
		plan.changes.Objects = append(plan.changes.Objects, final)
	}
	return nil
}

func (planner *PermissionAuthoringRecordMutationPlanner) planFields(
	ctx context.Context,
	permissionSetID string,
	changes []permissionauthoringv1.FieldGrantChange,
	actor *models.UserSession,
	plan *permissionAuthoringRecordMutationPlan,
) error {
	if len(changes) == 0 {
		return nil
	}
	rows, err := planner.permissions.LockPermissionAuthoringFieldGrants(ctx, permissionSetID)
	if err != nil {
		return err
	}
	sort.Slice(rows, func(i, j int) bool {
		return strings.TrimSpace(rows[i].ID) < strings.TrimSpace(rows[j].ID)
	})
	current := make(map[string][]ports.PermissionAuthoringFieldGrant, len(rows))
	for _, row := range rows {
		key, valid := permissionAuthoringRecordFieldKey(row.ResourceType, row.FieldName)
		if !valid {
			continue
		}
		current[key] = append(current[key], row)
	}
	for _, change := range changes {
		identity := permissionAuthoringFieldGrantIdentity(
			permissionSetID,
			change.ResourceType,
			fieldpermissions.QualifiedField(change.ResourceType, change.FieldName),
		)
		key, valid := permissionAuthoringRecordFieldKey(change.ResourceType, change.FieldName)
		if !valid {
			return errors.New("permission authoring field change has mismatched object identity")
		}
		aliases := current[key]
		updates := permissionAuthoringFieldRecord(permissionSetID, change, false)
		active := permissionAuthoringActiveFieldAliases(aliases)
		if len(active) != 0 {
			allAlreadyFinal := true
			for _, row := range active {
				if !permissionAuthoringFieldValuesEqual(row, change) {
					allAlreadyFinal = false
					break
				}
			}
			if allAlreadyFinal {
				continue
			}
			var preparedOperations []permissionAuthoringRecordOperation
			var finalChange *permissionauthoringv1.FieldGrantChange
			for _, row := range active {
				if strings.TrimSpace(row.ID) == "" {
					return errors.New("permission authoring Records planner found FieldPermissions without identity")
				}
				actual := permissionauthoringv1.FieldGrantChange{
					ResourceType: change.ResourceType,
					FieldName:    fieldpermissions.FieldComponent(change.ResourceType, row.FieldName),
					Read:         row.Read,
					Edit:         row.Edit,
				}
				changed := false
				var operation permissionAuthoringRecordOperation
				if !permissionAuthoringFieldValuesEqual(row, change) {
					var err error
					operation, changed, err = planner.prepareUpdateOperation(
						ctx, constants.TableFieldPermissions, row.ID, identity, updates, actor,
					)
					if err != nil {
						return err
					}
					actual = permissionAuthoringFieldChangeFromRecord(operation.finalRecord)
				}
				if finalChange == nil {
					finalChange = &actual
				} else if finalChange.Read != actual.Read || finalChange.Edit != actual.Edit {
					return appErrors.NewValidationError(
						constants.FieldFieldPermissions_Field,
						"before-save lifecycle produced conflicting final values for one logical field grant",
					)
				}
				if changed {
					preparedOperations = append(preparedOperations, operation)
				}
			}
			if len(preparedOperations) != 0 && finalChange != nil {
				plan.operations = append(plan.operations, preparedOperations...)
				plan.changes.Fields = append(plan.changes.Fields, *finalChange)
			}
			continue
		}
		tombstone, found := permissionAuthoringPreferredFieldTombstone(
			aliases, change.ResourceType, change.FieldName,
		)
		if found {
			if strings.TrimSpace(tombstone.ID) == "" {
				return errors.New("permission authoring Records planner found FieldPermissions without identity")
			}
			if !change.Read && !change.Edit {
				if _, err := planner.prepareInsertOperation(
					ctx, constants.TableFieldPermissions, identity,
					permissionAuthoringFieldRecord(permissionSetID, change, true), actor,
				); err != nil {
					return err
				}
				continue
			}
			operation, err := planner.prepareRestoreOperation(
				ctx, constants.TableFieldPermissions, tombstone.ID, identity, updates, actor,
			)
			if err != nil {
				return err
			}
			final := permissionAuthoringFieldChangeFromRecord(operation.finalRecord)
			if !final.Read && !final.Edit {
				continue
			}
			plan.operations = append(plan.operations, operation)
			plan.changes.Fields = append(plan.changes.Fields, final)
			continue
		}
		if !change.Read && !change.Edit {
			if _, err := planner.prepareInsertOperation(
				ctx, constants.TableFieldPermissions, identity,
				permissionAuthoringFieldRecord(permissionSetID, change, true), actor,
			); err != nil {
				return err
			}
			continue
		}
		insert := permissionAuthoringFieldRecord(permissionSetID, change, true)
		operation, err := planner.prepareInsertOperation(ctx, constants.TableFieldPermissions, identity, insert, actor)
		if err != nil {
			return err
		}
		final := permissionAuthoringFieldChangeFromRecord(operation.finalRecord)
		if !final.Read && !final.Edit {
			continue
		}
		plan.operations = append(plan.operations, operation)
		plan.changes.Fields = append(plan.changes.Fields, final)
	}
	return nil
}

func permissionAuthoringObjectRecord(
	permissionSetID string,
	change permissionauthoringv1.ObjectGrantChange,
	includeIdentity bool,
) map[string]any {
	record := map[string]any{
		constants.FieldObjectPermissions_PermissionsRead:             change.Read,
		constants.FieldObjectPermissions_PermissionsCreate:           change.Create,
		constants.FieldObjectPermissions_PermissionsEdit:             change.Edit,
		constants.FieldObjectPermissions_PermissionsDelete:           change.Delete,
		constants.FieldObjectPermissions_PermissionsViewAllRecords:   change.ViewAll,
		constants.FieldObjectPermissions_PermissionsModifyAllRecords: change.ModifyAll,
	}
	if includeIdentity {
		record[constants.FieldObjectPermissions_ParentID] = permissionSetID
		record[constants.FieldObjectPermissions_ResourceType] = change.ResourceType
	}
	return record
}

func permissionAuthoringFieldRecord(
	permissionSetID string,
	change permissionauthoringv1.FieldGrantChange,
	includeIdentity bool,
) map[string]any {
	record := map[string]any{
		constants.FieldFieldPermissions_PermissionsRead: change.Read,
		constants.FieldFieldPermissions_PermissionsEdit: change.Edit,
	}
	if includeIdentity {
		record[constants.FieldFieldPermissions_ParentID] = permissionSetID
		record[constants.FieldFieldPermissions_ResourceType] = change.ResourceType
		record[constants.FieldFieldPermissions_Field] = fieldpermissions.QualifiedField(change.ResourceType, change.FieldName)
	}
	return record
}

func permissionAuthoringObjectValuesEqual(row ports.PermissionAuthoringObjectGrant, change permissionauthoringv1.ObjectGrantChange) bool {
	return row.Read == change.Read && row.Create == change.Create &&
		row.Edit == change.Edit && row.Delete == change.Delete &&
		row.ViewAll == change.ViewAll && row.ModifyAll == change.ModifyAll
}

func permissionAuthoringFieldValuesEqual(row ports.PermissionAuthoringFieldGrant, change permissionauthoringv1.FieldGrantChange) bool {
	return row.Read == change.Read && row.Edit == change.Edit
}

func permissionAuthoringActiveFieldAliases(rows []ports.PermissionAuthoringFieldGrant) []ports.PermissionAuthoringFieldGrant {
	active := make([]ports.PermissionAuthoringFieldGrant, 0, len(rows))
	for _, row := range rows {
		if !row.Deleted {
			active = append(active, row)
		}
	}
	return active
}

func permissionAuthoringPreferredFieldTombstone(
	rows []ports.PermissionAuthoringFieldGrant,
	objectAPIName string,
	fieldAPIName string,
) (ports.PermissionAuthoringFieldGrant, bool) {
	canonical := fieldpermissions.QualifiedField(objectAPIName, fieldAPIName)
	var fallback ports.PermissionAuthoringFieldGrant
	found := false
	for _, row := range rows {
		if !row.Deleted {
			continue
		}
		if !found {
			fallback = row
			found = true
		}
		if strings.EqualFold(strings.TrimSpace(row.FieldName), canonical) {
			return row, true
		}
	}
	return fallback, found
}

func permissionAuthoringObjectChangeEmpty(change permissionauthoringv1.ObjectGrantChange) bool {
	return !change.Read && !change.Create && !change.Edit && !change.Delete && !change.ViewAll && !change.ModifyAll
}

func permissionAuthoringObjectChangeFromRecord(record models.Record) permissionauthoringv1.ObjectGrantChange {
	return permissionauthoringv1.ObjectGrantChange{
		ResourceType: stringField(record, constants.FieldObjectPermissions_ResourceType),
		Read:         boolField(record, constants.FieldObjectPermissions_PermissionsRead),
		Create:       boolField(record, constants.FieldObjectPermissions_PermissionsCreate),
		Edit:         boolField(record, constants.FieldObjectPermissions_PermissionsEdit),
		Delete:       boolField(record, constants.FieldObjectPermissions_PermissionsDelete),
		ViewAll:      boolField(record, constants.FieldObjectPermissions_PermissionsViewAllRecords),
		ModifyAll:    boolField(record, constants.FieldObjectPermissions_PermissionsModifyAllRecords),
	}
}

func permissionAuthoringFieldChangeFromRecord(record models.Record) permissionauthoringv1.FieldGrantChange {
	resourceType := stringField(record, constants.FieldFieldPermissions_ResourceType)
	return permissionauthoringv1.FieldGrantChange{
		ResourceType: resourceType,
		FieldName:    fieldpermissions.FieldComponent(resourceType, stringField(record, constants.FieldFieldPermissions_Field)),
		Read:         boolField(record, constants.FieldFieldPermissions_PermissionsRead),
		Edit:         boolField(record, constants.FieldFieldPermissions_PermissionsEdit),
	}
}

func permissionAuthoringRecordFieldKey(objectAPIName, fieldAPIName string) (string, bool) {
	objectAPIName = strings.TrimSpace(objectAPIName)
	fieldObjectAPIName, fieldComponent := fieldpermissions.SplitFieldReference(objectAPIName, fieldAPIName)
	fieldComponent = strings.TrimSpace(fieldComponent)
	if objectAPIName == "" || fieldComponent == "" || !strings.EqualFold(objectAPIName, strings.TrimSpace(fieldObjectAPIName)) {
		return "", false
	}
	return strings.ToLower(objectAPIName) + "\x00" + strings.ToLower(fieldComponent), true
}

type permissionAuthoringAggregateRecordsContextKey struct{}

func withPermissionAuthoringAggregateRecords(ctx context.Context) context.Context {
	ctx = constants.WithSecurityContext(
		ctx,
		constants.SystemContext,
		"atomic Permission Set authoring Records lifecycle",
	)
	return context.WithValue(ctx, permissionAuthoringAggregateRecordsContextKey{}, true)
}

func permissionAuthoringAggregateOwnsRecordsConvergence(ctx context.Context) bool {
	owned, _ := ctx.Value(permissionAuthoringAggregateRecordsContextKey{}).(bool)
	return owned
}
