package records

import (
	"context"
	"errors"
	"fmt"
	"reflect"
	"strings"
	"sync/atomic"

	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/ports"
)

type externalIDUpsertAdoptionGuardContextKey struct{}
type externalIDUpsertAdoptionGuardUpdateContextKey struct{}

type externalIDUpsertAdoptionGuard struct {
	objectName    string
	recordID      string
	fields        []string
	identityField string
	identityValue string
	updateClaimed atomic.Bool
}

type externalIDUpsertAdoptionGuardMutationPhase uint8

const (
	externalIDUpsertAdoptionGuardUniquenessPrecheck externalIDUpsertAdoptionGuardMutationPhase = iota + 1
	externalIDUpsertAdoptionGuardRepositoryUpdate
)

type externalIDUpsertAdoptionGuardMutationError struct {
	owner *externalIDUpsertAdoptionGuard
	phase externalIDUpsertAdoptionGuardMutationPhase
	cause error
}

// externalIDUpsertAdoptionConflictError is the clean business boundary exposed
// only after direct-mutation provenance and the exact guard index are proved.
// It deliberately stores diagnostics as text, not as an error cause, so an
// InternalError used while classifying repository provenance cannot remain in
// the unwrap chain and override the dedicated Flow conflict branch.
type externalIDUpsertAdoptionConflictError struct {
	message string
}

func (e *externalIDUpsertAdoptionConflictError) Error() string {
	return e.message
}

func (e *externalIDUpsertAdoptionConflictError) Unwrap() error {
	return ports.ErrExternalIDUpsertAdoptionGuardConflict
}

func newExternalIDUpsertAdoptionConflictError(message string, diagnostic error) error {
	if diagnostic != nil {
		message = fmt.Sprintf("%s (%s)", message, diagnostic.Error())
	}
	return &externalIDUpsertAdoptionConflictError{message: message}
}

func (e *externalIDUpsertAdoptionGuardMutationError) Error() string {
	return e.cause.Error()
}

func (e *externalIDUpsertAdoptionGuardMutationError) Unwrap() error {
	return e.cause
}

func withExternalIDUpsertAdoptionGuard(
	ctx context.Context,
	objectName string,
	recordID string,
	fields []string,
) (context.Context, *externalIDUpsertAdoptionGuard) {
	return withExternalIDUpsertAdoptionGuardAndIdentity(ctx, objectName, recordID, fields, "", "")
}

func withExternalIDUpsertAdoptionGuardAndIdentity(
	ctx context.Context,
	objectName string,
	recordID string,
	fields []string,
	identityField string,
	identityValue string,
) (context.Context, *externalIDUpsertAdoptionGuard) {
	if len(fields) == 0 && identityField == "" {
		return ctx, nil
	}
	guard := &externalIDUpsertAdoptionGuard{
		objectName:    objectName,
		recordID:      recordID,
		fields:        append([]string(nil), fields...),
		identityField: identityField,
		identityValue: identityValue,
	}
	return context.WithValue(ctx, externalIDUpsertAdoptionGuardContextKey{}, guard), guard
}

// enterExternalIDUpsertAdoptionGuardUpdate binds the guard to the one direct
// Update invocation made by atomic upsert. A nested Update inheriting the same
// context masks this binding, even when it targets the same object and record,
// so hook-owned mutations cannot borrow the outer operation's provenance.
func enterExternalIDUpsertAdoptionGuardUpdate(
	ctx context.Context,
	objectName string,
	recordID string,
) context.Context {
	guard, _ := ctx.Value(externalIDUpsertAdoptionGuardContextKey{}).(*externalIDUpsertAdoptionGuard)
	if guard == nil ||
		!strings.EqualFold(guard.objectName, objectName) ||
		guard.recordID != recordID ||
		!guard.updateClaimed.CompareAndSwap(false, true) {
		return context.WithValue(ctx, externalIDUpsertAdoptionGuardUpdateContextKey{}, (*externalIDUpsertAdoptionGuard)(nil))
	}
	return context.WithValue(ctx, externalIDUpsertAdoptionGuardUpdateContextKey{}, guard)
}

func claimExternalIDUpsertAdoptionGuardMutation(
	ctx context.Context,
	objectName string,
	recordID string,
	phase externalIDUpsertAdoptionGuardMutationPhase,
) *externalIDUpsertAdoptionGuard {
	guard, _ := ctx.Value(externalIDUpsertAdoptionGuardUpdateContextKey{}).(*externalIDUpsertAdoptionGuard)
	if guard == nil || len(guard.fields) == 0 ||
		!strings.EqualFold(guard.objectName, objectName) || guard.recordID != recordID {
		return nil
	}
	switch phase {
	case externalIDUpsertAdoptionGuardUniquenessPrecheck,
		externalIDUpsertAdoptionGuardRepositoryUpdate:
		return guard
	default:
		return nil
	}
}

func externalIDUpsertAdoptionGuardDirectUpdateActive(
	ctx context.Context,
	objectName string,
	recordID string,
) bool {
	guard, _ := ctx.Value(externalIDUpsertAdoptionGuardUpdateContextKey{}).(*externalIDUpsertAdoptionGuard)
	return guard != nil && len(guard.fields) > 0 &&
		strings.EqualFold(guard.objectName, objectName) && guard.recordID == recordID
}

func wrapExternalIDUpsertAdoptionGuardMutationError(
	owner *externalIDUpsertAdoptionGuard,
	phase externalIDUpsertAdoptionGuardMutationPhase,
	cause error,
) error {
	if owner == nil || cause == nil {
		return cause
	}
	return &externalIDUpsertAdoptionGuardMutationError{owner: owner, phase: phase, cause: cause}
}

func externalIDUpsertAdoptionGuardDirectMutationConflict(
	err error,
	owner *externalIDUpsertAdoptionGuard,
) bool {
	if owner == nil {
		return false
	}
	var mutationErr *externalIDUpsertAdoptionGuardMutationError
	if !errors.As(err, &mutationErr) || mutationErr == nil || mutationErr.owner != owner {
		return false
	}
	return mutationErr.phase == externalIDUpsertAdoptionGuardUniquenessPrecheck ||
		mutationErr.phase == externalIDUpsertAdoptionGuardRepositoryUpdate
}

// validateExternalIDUpsertAdoptionGuard evaluates the exact final post-before-
// save image while the target row is held by the canonical Update lock. The
// external identity that selected an existing owner must remain unchanged. An
// existing blank adoption guard is claimable; an existing non-blank guard is
// adoptable only when the final incoming value is exactly equal. This keeps
// create-or-adopt bound to one requested identity without letting another
// provider identity overwrite an already-owned row.
func (ps *PersistenceService) validateExternalIDUpsertAdoptionGuard(
	ctx context.Context,
	objectName string,
	recordID string,
	oldRecord recordValues,
	finalRecord recordValues,
) error {
	guard, _ := ctx.Value(externalIDUpsertAdoptionGuardUpdateContextKey{}).(*externalIDUpsertAdoptionGuard)
	if guard == nil || !strings.EqualFold(guard.objectName, objectName) || guard.recordID != recordID {
		return nil
	}

	if guard.identityField != "" {
		requestedIdentityOwner, err := ps.externalIDUpsertDatabaseValueOwnedByRecord(
			ctx,
			objectName,
			guard.identityField,
			guard.identityValue,
			recordID,
		)
		if err != nil {
			return err
		}
		if !requestedIdentityOwner {
			oldValue, _ := lookupRecordValueCaseInsensitive(oldRecord, guard.identityField)
			return fmt.Errorf(
				"atomic external-ID upsert locked %s record %s with %s=%v, but the database no longer resolves requested identity %q to that owner",
				objectName,
				recordID,
				guard.identityField,
				oldValue,
				guard.identityValue,
			)
		}
		finalValue, finalPresent := lookupRecordValueCaseInsensitive(finalRecord, guard.identityField)
		finalIdentity, finalIsString := finalValue.(string)
		if !finalPresent || !finalIsString || strings.TrimSpace(finalIdentity) == "" {
			return fmt.Errorf(
				"atomic external-ID upsert final identity for %s.%s changed from %q to %v",
				objectName,
				guard.identityField,
				guard.identityValue,
				finalValue,
			)
		}
		if finalIdentity != guard.identityValue {
			finalIdentityOwner, err := ps.externalIDUpsertDatabaseValueOwnedByRecord(
				ctx,
				objectName,
				guard.identityField,
				finalIdentity,
				recordID,
			)
			if err != nil {
				return err
			}
			if !finalIdentityOwner {
				return fmt.Errorf(
					"atomic external-ID upsert final identity for %s.%s changed from %q to %v",
					objectName,
					guard.identityField,
					guard.identityValue,
					finalValue,
				)
			}
		}
	}

	for _, field := range guard.fields {
		oldValue, _ := lookupRecordValueCaseInsensitive(oldRecord, field)
		finalValue, finalPresent := lookupRecordValueCaseInsensitive(finalRecord, field)
		if !finalPresent || isBlankExternalIDUpsertAdoptionGuardValue(finalValue) {
			return fmt.Errorf("guarded external-ID upsert requires a non-blank final value for %s.%s", objectName, field)
		}
		if isBlankExternalIDUpsertAdoptionGuardValue(oldValue) || reflect.DeepEqual(oldValue, finalValue) {
			continue
		}
		finalGuardOwner, err := ps.externalIDUpsertDatabaseValueOwnedByRecord(
			ctx,
			objectName,
			field,
			finalValue,
			recordID,
		)
		if err != nil {
			return err
		}
		if finalGuardOwner {
			continue
		}
		return fmt.Errorf(
			"refusing to adopt %s record %s because ownership guard %s is already occupied: %w",
			objectName,
			recordID,
			field,
			ports.ErrExternalIDUpsertAdoptionGuardConflict,
		)
	}
	return nil
}

// externalIDUpsertDatabaseValueOwnedByRecord delegates equality to the same
// repository/SQL comparison that selected the unique owner. This is deliberate:
// default tables use a case-insensitive collation, and deployments may use an
// even broader collation equivalence than Go string or Unicode-fold equality.
func (ps *PersistenceService) externalIDUpsertDatabaseValueOwnedByRecord(
	ctx context.Context,
	objectName string,
	fieldName string,
	value any,
	recordID string,
) (bool, error) {
	owner, err := ps.repo.FindOneByField(ctx, objectName, fieldName, value)
	if err != nil {
		return false, appErrors.NewInternalError(
			fmt.Sprintf("failed to verify database equality for %s.%s", objectName, fieldName),
			err,
		)
	}
	return owner != nil && extractRecordID(owner) == recordID, nil
}

func isBlankExternalIDUpsertAdoptionGuardValue(value any) bool {
	if value == nil {
		return true
	}
	if stringValue, ok := value.(string); ok {
		return strings.TrimSpace(stringValue) == ""
	}
	return false
}
