package persistence

import (
	"context"
	"database/sql"
	"fmt"

	pkgerrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/query"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

// LockExclusiveSiblingResetRows locks active siblings that share a parent
// partition with the winner row and returns their pre-reset images. The caller
// performs the system-owned per-row update and side effects while these locks
// remain held.
func (r *RecordRepository) LockExclusiveSiblingResetRows(ctx context.Context, schema *models.ObjectMetadata, fieldAPIName, partitionFieldAPIName string, partitionValue interface{}, winnerID string) ([]models.SObject, error) {
	if schema == nil {
		return nil, pkgerrors.NewValidationError("object", "schema is required")
	}
	if !isValidFieldName(schema.APIName) {
		return nil, pkgerrors.NewValidationError("object", fmt.Sprintf("invalid object name: %s", schema.APIName))
	}
	if !isValidFieldName(fieldAPIName) {
		return nil, pkgerrors.NewValidationError("field", fmt.Sprintf("invalid field name: %s", fieldAPIName))
	}
	if !isValidFieldName(partitionFieldAPIName) {
		return nil, pkgerrors.NewValidationError("partitionField", fmt.Sprintf("invalid partition field name: %s", partitionFieldAPIName))
	}
	exec := r.GetExecutor(ctx)
	if _, ok := exec.(*sql.Tx); !ok {
		return nil, pkgerrors.NewValidationError("transaction", fmt.Sprintf("transaction required for locking exclusive siblings of %s", schema.APIName))
	}

	selectRows := query.From(schema.APIName).
		WithMetadata(schema).
		Select([]string{"*"}).
		Where(partitionFieldAPIName, partitionValue).
		Where(fieldAPIName, true).
		WhereNot(constants.FieldID, winnerID)
	if schemaHasIsDeleted(schema) {
		selectRows = selectRows.WhereRaw(fmt.Sprintf("(`%s`.`%s` = 0 OR `%s`.`%s` IS NULL)", schema.APIName, constants.FieldIsDeleted, schema.APIName, constants.FieldIsDeleted), nil)
	}
	selectQuery := selectRows.Build()
	selectQuery.SQL += " FOR UPDATE"
	rows, err := exec.QueryContext(ctx, selectQuery.SQL, selectQuery.Params...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	oldRows, err := query.ScanRowsToSObjects(rows)
	if err != nil {
		return nil, err
	}
	if len(oldRows) == 0 {
		return nil, nil
	}
	for i := range oldRows {
		oldRows[i] = r.normalizeRecord(oldRows[i], schema)
	}
	return oldRows, nil
}
