package utils

import (
	"fmt"
	"reflect"
	"strings"
	"sync"
)

// FilterOperator represents supported filter operators
type FilterOperator string

const (
	OpEqual            FilterOperator = "_eq"
	OpNotEqual         FilterOperator = "_neq"
	OpLike             FilterOperator = "_like"
	OpILike            FilterOperator = "_ilike"
	OpIn               FilterOperator = "_in"
	OpNotIn            FilterOperator = "_nin"
	OpGreaterThan      FilterOperator = "_gt"
	OpGreaterThanEqual FilterOperator = "_gte"
	OpLessThan         FilterOperator = "_lt"
	OpLessThanEqual    FilterOperator = "_lte"
	OpBetween          FilterOperator = "_between"
	OpNotBetween       FilterOperator = "_nbetween"
	OpNull             FilterOperator = "_null"
	OpNotNull          FilterOperator = "_nnull"
	OpContains         FilterOperator = "_contains"
	OpNotContains      FilterOperator = "_ncontains"
	OpStartsWith       FilterOperator = "_starts_with"
	OpEndsWith         FilterOperator = "_ends_with"
)

// DynamicFilter represents a single filter condition
type DynamicFilter struct {
	Column   string         `json:"column"`
	Operator FilterOperator `json:"operator"`
	Value    interface{}    `json:"value"`
	LogicOp  string         `json:"logic_op,omitempty"` // AND, OR
}

// FilterGroup represents a group of filters
type FilterGroup struct {
	Filters []DynamicFilter `json:"filters"`
	LogicOp string          `json:"logic_op"` // AND, OR
}

// DynamicQuery represents the complete query structure
type DynamicQuery struct {
	Fields  []string      `json:"fields,omitempty"`
	Filters []FilterGroup `json:"filters,omitempty"`
	Sort    []SortField   `json:"sort,omitempty"`
	Limit   int           `json:"limit"`
	Offset  int           `json:"offset"`
	GroupBy []string      `json:"group_by,omitempty"`
	Having  []FilterGroup `json:"having,omitempty"`
}

// SortField represents sorting configuration
type SortField struct {
	Column string `json:"column"`
	Order  string `json:"order"` // ASC, DESC
}

// QueryBuilder builds SQL queries from dynamic filters
type QueryBuilder struct {
	tableName      string
	columnMapping  map[string]string // Maps API field names to DB column names
	allowedColumns map[string]bool   // Security: only allow specified columns
	paramCounter   int
	mu             *sync.RWMutex
}

// NewQueryBuilder creates a new query builder instance
func NewQueryBuilder(tableName string) *QueryBuilder {
	return &QueryBuilder{
		tableName:      tableName,
		columnMapping:  make(map[string]string),
		allowedColumns: make(map[string]bool),
		paramCounter:   0,
	}
}

// SetColumnMapping sets the mapping between API field names and database column names
func (qb *QueryBuilder) SetColumnMapping(mapping map[string]string) *QueryBuilder {
	qb.columnMapping = mapping
	return qb
}

// SetAllowedColumns sets the list of allowed columns for security
func (qb *QueryBuilder) SetAllowedColumns(columns []string) *QueryBuilder {
	qb.allowedColumns = make(map[string]bool)
	for _, col := range columns {
		qb.allowedColumns[col] = true
	}
	return qb
}

// BuildQuery builds the complete SQL query
func (qb *QueryBuilder) BuildQuery(query DynamicQuery) (string, []interface{}, error) {
	qb.paramCounter = 0

	// Build SELECT clause
	selectClause := qb.buildSelectClause(query.Fields)

	// Build FROM clause
	fromClause := fmt.Sprintf("FROM %s", qb.tableName)

	// Build WHERE clause
	whereClause, whereArgs, err := qb.buildWhereClause(query.Filters)
	if err != nil {
		return "", nil, err
	}

	// Build ORDER BY clause
	orderClause := qb.buildOrderClause(query.Sort)

	// Build GROUP BY clause
	groupClause := qb.buildGroupByClause(query.GroupBy)

	// Build HAVING clause
	havingClause, havingArgs, err := qb.buildHavingClause(query.Having)
	if err != nil {
		return "", nil, err
	}

	// Combine all parts
	sqlParts := []string{selectClause, fromClause}
	args := []interface{}{}

	if whereClause != "" {
		sqlParts = append(sqlParts, "WHERE "+whereClause)
		args = append(args, whereArgs...)
	}

	if groupClause != "" {
		sqlParts = append(sqlParts, groupClause)
	}

	if havingClause != "" {
		sqlParts = append(sqlParts, "HAVING "+havingClause)
		args = append(args, havingArgs...)
	}

	if orderClause != "" {
		sqlParts = append(sqlParts, orderClause)
	}

	// Add pagination
	if query.Limit > 0 {
		qb.paramCounter++
		sqlParts = append(sqlParts, fmt.Sprintf("LIMIT $%d", qb.paramCounter))
		args = append(args, query.Limit)
	}

	if query.Offset > 0 {
		qb.paramCounter++
		sqlParts = append(sqlParts, fmt.Sprintf("OFFSET $%d", qb.paramCounter))
		args = append(args, query.Offset)
	}

	sql := strings.Join(sqlParts, " ")
	return sql, args, nil
}

// buildSelectClause builds the SELECT part of the query
func (qb *QueryBuilder) buildSelectClause(fields []string) string {
	if len(fields) == 0 || (len(fields) == 1 && fields[0] == "*") {
		return "SELECT *"
	}

	var selectedFields []string
	for _, field := range fields {
		if field == "*.*" || field == "*" {
			selectedFields = append(selectedFields, "*")
			continue
		}

		// Check if it's an expression (contains spaces, parentheses, etc.)
		if strings.Contains(field, " ") || strings.Contains(field, "(") || strings.Contains(field, ")") {
			// Expression, add as is
			selectedFields = append(selectedFields, field)
			continue
		}

		// Security check: only allow specified columns (check original field name)
		if len(qb.allowedColumns) > 0 && !qb.allowedColumns[field] {
			continue
		}

		// Map field name if mapping exists
		if mappedCol, exists := qb.columnMapping[field]; exists {
			field = mappedCol
		}

		selectedFields = append(selectedFields, fmt.Sprintf(`"%s"`, field))
	}

	if len(selectedFields) == 0 {
		return "SELECT *"
	}

	return "SELECT " + strings.Join(selectedFields, ", ")
}

// buildWhereClause builds the WHERE part of the query
func (qb *QueryBuilder) buildWhereClause(filterGroups []FilterGroup) (string, []interface{}, error) {
	if len(filterGroups) == 0 {
		return "", nil, nil
	}

	var conditions []string
	var args []interface{}

	for i, group := range filterGroups {
		groupCondition, groupArgs, err := qb.buildFilterGroup(group)
		if err != nil {
			return "", nil, err
		}

		if groupCondition != "" {
			if i > 0 {
				logicOp := "AND"
				if group.LogicOp != "" {
					logicOp = strings.ToUpper(group.LogicOp)
				}
				conditions = append(conditions, logicOp)
			}

			conditions = append(conditions, groupCondition)
			args = append(args, groupArgs...)
		}
	}

	return strings.Join(conditions, " "), args, nil
}

// buildFilterGroup builds conditions for a filter group
func (qb *QueryBuilder) buildFilterGroup(group FilterGroup) (string, []interface{}, error) {
	if len(group.Filters) == 0 {
		return "", nil, nil
	}

	var conditions []string
	var args []interface{}

	for i, filter := range group.Filters {
		condition, filterArgs, err := qb.buildFilterCondition(filter)
		if err != nil {
			return "", nil, err
		}

		if condition != "" {
			if i > 0 {
				logicOp := "AND"
				if filter.LogicOp != "" {
					logicOp = strings.ToUpper(filter.LogicOp)
				} else if group.LogicOp != "" {
					logicOp = strings.ToUpper(group.LogicOp)
				}
				conditions = append(conditions, logicOp)
			}

			conditions = append(conditions, condition)
			args = append(args, filterArgs...)
		}
	}

	return strings.Join(conditions, " "), args, nil
}

// buildFilterCondition builds a single filter condition
func (qb *QueryBuilder) buildFilterCondition(filter DynamicFilter) (string, []interface{}, error) {
	// Security check (check original field name)
	if len(qb.allowedColumns) > 0 && !qb.allowedColumns[filter.Column] {
		return "", nil, nil
	}

	// Map column name if mapping exists
	column := filter.Column
	if mappedCol, exists := qb.columnMapping[column]; exists {
		column = mappedCol
	}

	// Wrap column name in quotes for PostgreSQL
	column = fmt.Sprintf(`"%s"`, column)

	switch filter.Operator {
	case OpEqual:
		if filter.Value == nil {
			return "", nil, nil
		}
		qb.paramCounter++
		return fmt.Sprintf("%s = $%d", column, qb.paramCounter), []interface{}{filter.Value}, nil

	case OpNotEqual:
		if filter.Value == nil {
			return "", nil, nil
		}
		qb.paramCounter++
		return fmt.Sprintf("%s != $%d", column, qb.paramCounter), []interface{}{filter.Value}, nil

	case OpLike:
		if filter.Value == nil {
			return "", nil, nil
		}
		qb.paramCounter++
		return fmt.Sprintf("%s LIKE $%d", column, qb.paramCounter), []interface{}{filter.Value}, nil

	case OpILike:
		if filter.Value == nil {
			return "", nil, nil
		}
		qb.paramCounter++
		return fmt.Sprintf("%s ILIKE $%d", column, qb.paramCounter), []interface{}{filter.Value}, nil

	case OpIn:
		values := qb.parseArrayValue(filter.Value)
		if len(values) == 0 {
			return "", nil, nil
		}

		var placeholders []string
		var args []interface{}
		for _, val := range values {
			qb.paramCounter++
			placeholders = append(placeholders, fmt.Sprintf("$%d", qb.paramCounter))
			args = append(args, val)
		}

		return fmt.Sprintf("%s IN (%s)", column, strings.Join(placeholders, ", ")), args, nil

	case OpNotIn:
		values := qb.parseArrayValue(filter.Value)
		if len(values) == 0 {
			return "", nil, nil
		}

		var placeholders []string
		var args []interface{}
		for _, val := range values {
			qb.paramCounter++
			placeholders = append(placeholders, fmt.Sprintf("$%d", qb.paramCounter))
			args = append(args, val)
		}

		return fmt.Sprintf("%s NOT IN (%s)", column, strings.Join(placeholders, ", ")), args, nil

	case OpGreaterThan:
		if filter.Value == nil {
			return "", nil, nil
		}
		qb.paramCounter++
		return fmt.Sprintf("%s > $%d", column, qb.paramCounter), []interface{}{filter.Value}, nil

	case OpGreaterThanEqual:
		if filter.Value == nil {
			return "", nil, nil
		}
		qb.paramCounter++
		return fmt.Sprintf("%s >= $%d", column, qb.paramCounter), []interface{}{filter.Value}, nil

	case OpLessThan:
		if filter.Value == nil {
			return "", nil, nil
		}
		qb.paramCounter++
		return fmt.Sprintf("%s < $%d", column, qb.paramCounter), []interface{}{filter.Value}, nil

	case OpLessThanEqual:
		if filter.Value == nil {
			return "", nil, nil
		}
		qb.paramCounter++
		return fmt.Sprintf("%s <= $%d", column, qb.paramCounter), []interface{}{filter.Value}, nil

	case OpBetween:
		if filter.Value == nil {
			return "", nil, nil
		}
		values := qb.parseArrayValue(filter.Value)
		if len(values) != 2 {
			return "", nil, fmt.Errorf("between operator requires exactly 2 values")
		}
		qb.paramCounter++
		param1 := qb.paramCounter
		qb.paramCounter++
		param2 := qb.paramCounter
		return fmt.Sprintf("%s BETWEEN $%d AND $%d", column, param1, param2), []interface{}{values[0], values[1]}, nil

	case OpNotBetween:
		if filter.Value == nil {
			return "", nil, nil
		}
		values := qb.parseArrayValue(filter.Value)
		if len(values) != 2 {
			return "", nil, fmt.Errorf("not between operator requires exactly 2 values")
		}
		qb.paramCounter++
		param1 := qb.paramCounter
		qb.paramCounter++
		param2 := qb.paramCounter
		return fmt.Sprintf("%s NOT BETWEEN $%d AND $%d", column, param1, param2), []interface{}{values[0], values[1]}, nil

	case OpNull:
		return fmt.Sprintf("%s IS NULL", column), nil, nil

	case OpNotNull:
		return fmt.Sprintf("%s IS NOT NULL", column), nil, nil

	case OpContains:
		if filter.Value == nil {
			return "", nil, nil
		}
		qb.paramCounter++
		value := fmt.Sprintf("%%%v%%", filter.Value)
		return fmt.Sprintf("%s ILIKE $%d", column, qb.paramCounter), []interface{}{value}, nil

	case OpNotContains:
		if filter.Value == nil {
			return "", nil, nil
		}
		qb.paramCounter++
		value := fmt.Sprintf("%%%v%%", filter.Value)
		return fmt.Sprintf("%s NOT ILIKE $%d", column, qb.paramCounter), []interface{}{value}, nil

	case OpStartsWith:
		if filter.Value == nil {
			return "", nil, nil
		}
		qb.paramCounter++
		value := fmt.Sprintf("%v%%", filter.Value)
		return fmt.Sprintf("%s ILIKE $%d", column, qb.paramCounter), []interface{}{value}, nil

	case OpEndsWith:
		if filter.Value == nil {
			return "", nil, nil
		}
		qb.paramCounter++
		value := fmt.Sprintf("%%%v", filter.Value)
		return fmt.Sprintf("%s ILIKE $%d", column, qb.paramCounter), []interface{}{value}, nil

	default:
		return "", nil, fmt.Errorf("unsupported operator: %s", filter.Operator)
	}
}

// parseArrayValue parses array values from various formats
func (qb *QueryBuilder) parseArrayValue(value interface{}) []interface{} {
	if value == nil {
		return nil
	}

	// If it's already a slice
	if reflect.TypeOf(value).Kind() == reflect.Slice {
		v := reflect.ValueOf(value)
		result := make([]interface{}, v.Len())
		for i := 0; i < v.Len(); i++ {
			result[i] = v.Index(i).Interface()
		}
		return result
	}

	// If it's a string, try to split by comma
	if str, ok := value.(string); ok {
		if strings.Contains(str, ",") {
			parts := strings.Split(str, ",")
			result := make([]interface{}, len(parts))
			for i, part := range parts {
				result[i] = strings.TrimSpace(part)
			}
			return result
		}
		return []interface{}{str}
	}

	return []interface{}{value}
}

// buildOrderClause builds the ORDER BY clause
func (qb *QueryBuilder) buildOrderClause(sortFields []SortField) string {
	if len(sortFields) == 0 {
		return ""
	}

	var orderParts []string
	for _, sort := range sortFields {
		column := sort.Column

		// Security check (check original field name)
		if len(qb.allowedColumns) > 0 && !qb.allowedColumns[column] {
			continue
		}

		if mappedCol, exists := qb.columnMapping[column]; exists {
			column = mappedCol
		}

		order := "ASC"
		if sort.Order != "" {
			order = strings.ToUpper(sort.Order)
		}

		orderParts = append(orderParts, fmt.Sprintf(`"%s" %s`, column, order))
	}

	if len(orderParts) == 0 {
		return ""
	}

	return "ORDER BY " + strings.Join(orderParts, ", ")
}

// buildGroupByClause builds the GROUP BY clause
func (qb *QueryBuilder) buildGroupByClause(groupFields []string) string {
	if len(groupFields) == 0 {
		return ""
	}

	var groupParts []string
	for _, field := range groupFields {
		column := field
		if mappedCol, exists := qb.columnMapping[column]; exists {
			column = mappedCol
		}

		// Security check
		if len(qb.allowedColumns) > 0 && !qb.allowedColumns[column] {
			continue
		}

		groupParts = append(groupParts, fmt.Sprintf(`"%s"`, column))
	}

	if len(groupParts) == 0 {
		return ""
	}

	return "GROUP BY " + strings.Join(groupParts, ", ")
}

// buildHavingClause builds the HAVING clause
func (qb *QueryBuilder) buildHavingClause(havingGroups []FilterGroup) (string, []interface{}, error) {
	if len(havingGroups) == 0 {
		return "", nil, nil
	}

	return qb.buildWhereClause(havingGroups)
}

// BuildCountQuery builds a count query
func (qb *QueryBuilder) BuildCountQuery(query DynamicQuery) (string, []interface{}, error) {
	qb.paramCounter = 0

	// Build FROM clause
	fromClause := fmt.Sprintf("FROM %s", qb.tableName)

	// Build WHERE clause
	whereClause, whereArgs, err := qb.buildWhereClause(query.Filters)
	if err != nil {
		return "", nil, err
	}

	// Build GROUP BY clause
	groupClause := qb.buildGroupByClause(query.GroupBy)

	// Build HAVING clause
	havingClause, havingArgs, err := qb.buildHavingClause(query.Having)
	if err != nil {
		return "", nil, err
	}

	// Combine parts
	sqlParts := []string{"SELECT COUNT(*)", fromClause}
	args := []interface{}{}

	if whereClause != "" {
		sqlParts = append(sqlParts, "WHERE "+whereClause)
		args = append(args, whereArgs...)
	}

	if groupClause != "" {
		sqlParts = append(sqlParts, groupClause)
	}

	if havingClause != "" {
		sqlParts = append(sqlParts, "HAVING "+havingClause)
		args = append(args, havingArgs...)
	}

	sql := strings.Join(sqlParts, " ")
	return sql, args, nil
}
