package infra

import (
	"database/sql"
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

// validCRUDTypes enumera los tipos SQLite aceptados por las funciones CRUD.
var validCRUDTypes = map[string]bool{
	"TEXT":    true,
	"INTEGER": true,
	"REAL":    true,
	"BLOB":    true,
}

// isValidCRUDType indica si el tipo string corresponde a uno soportado.
func isValidCRUDType(t string) bool {
	return validCRUDTypes[strings.ToUpper(t)]
}

// crudFieldByName busca un campo por nombre. Retorna nil si no existe.
func crudFieldByName(res CRUDResource, name string) *CRUDField {
	for i := range res.Fields {
		if res.Fields[i].Name == name {
			return &res.Fields[i]
		}
	}
	return nil
}

// crudColumnNames retorna la lista de nombres de columnas de una tabla sqlite.
// Usa PRAGMA table_info — unica forma portable en SQLite.
func crudColumnNames(db *sql.DB, table string) ([]string, error) {
	rows, err := db.Query(fmt.Sprintf("PRAGMA table_info(%q)", table))
	if err != nil {
		return nil, fmt.Errorf("crud column names: %w", err)
	}
	defer rows.Close()
	var cols []string
	for rows.Next() {
		var cid int
		var name, ctype string
		var notnull, pk int
		var dflt sql.NullString
		if err := rows.Scan(&cid, &name, &ctype, &notnull, &dflt, &pk); err != nil {
			return nil, fmt.Errorf("crud column names: %w", err)
		}
		cols = append(cols, name)
	}
	return cols, nil
}

// crudScanRow escanea una fila generica a map[string]any usando las columnas proporcionadas.
// Usa []any con apuntadores para que database/sql decida el tipo Go.
func crudScanRow(rows *sql.Rows, cols []string) (map[string]any, error) {
	values := make([]any, len(cols))
	scanArgs := make([]any, len(cols))
	for i := range values {
		scanArgs[i] = &values[i]
	}
	if err := rows.Scan(scanArgs...); err != nil {
		return nil, err
	}
	row := make(map[string]any, len(cols))
	for i, col := range cols {
		v := values[i]
		// Normalizar bytes a string (SQLite TEXT llega como []byte cuando no se tipa)
		if b, ok := v.([]byte); ok {
			row[col] = string(b)
		} else {
			row[col] = v
		}
	}
	return row, nil
}

// crudValidateField valida un valor contra las reglas de un campo.
// Retorna nil si todo ok, error con mensaje descriptivo si falla alguna regla.
func crudValidateField(field CRUDField, value any) error {
	if value == nil {
		if field.Required {
			return fmt.Errorf("field %q is required", field.Name)
		}
		return nil
	}
	switch strings.ToUpper(field.Type) {
	case "TEXT":
		s, ok := value.(string)
		if !ok {
			return fmt.Errorf("field %q must be a string", field.Name)
		}
		return crudValidateText(field, s)
	case "INTEGER":
		n, err := crudCoerceInt(value)
		if err != nil {
			return fmt.Errorf("field %q must be an integer", field.Name)
		}
		return crudValidateNumber(field, float64(n))
	case "REAL":
		f, err := crudCoerceFloat(value)
		if err != nil {
			return fmt.Errorf("field %q must be a number", field.Name)
		}
		return crudValidateNumber(field, f)
	case "BLOB":
		return nil
	}
	return nil
}

// crudValidateText aplica min_length, max_length, pattern, enum a un string.
func crudValidateText(field CRUDField, s string) error {
	if v, ok := field.Validations["min_length"]; ok {
		n, err := strconv.Atoi(v)
		if err == nil && len(s) < n {
			return fmt.Errorf("field %q must have at least %d characters", field.Name, n)
		}
	}
	if v, ok := field.Validations["max_length"]; ok {
		n, err := strconv.Atoi(v)
		if err == nil && len(s) > n {
			return fmt.Errorf("field %q must have at most %d characters", field.Name, n)
		}
	}
	if v, ok := field.Validations["pattern"]; ok {
		re, err := regexp.Compile(v)
		if err == nil && !re.MatchString(s) {
			return fmt.Errorf("field %q does not match pattern %q", field.Name, v)
		}
	}
	if v, ok := field.Validations["enum"]; ok {
		options := strings.Split(v, ",")
		matched := false
		for _, opt := range options {
			if strings.TrimSpace(opt) == s {
				matched = true
				break
			}
		}
		if !matched {
			return fmt.Errorf("field %q must be one of: %s", field.Name, v)
		}
	}
	return nil
}

// crudValidateNumber aplica min y max a un valor numerico.
func crudValidateNumber(field CRUDField, f float64) error {
	if v, ok := field.Validations["min"]; ok {
		min, err := strconv.ParseFloat(v, 64)
		if err == nil && f < min {
			return fmt.Errorf("field %q must be >= %s", field.Name, v)
		}
	}
	if v, ok := field.Validations["max"]; ok {
		max, err := strconv.ParseFloat(v, 64)
		if err == nil && f > max {
			return fmt.Errorf("field %q must be <= %s", field.Name, v)
		}
	}
	return nil
}

// crudCoerceInt intenta convertir un valor a int64.
func crudCoerceInt(v any) (int64, error) {
	switch n := v.(type) {
	case int:
		return int64(n), nil
	case int64:
		return n, nil
	case float64:
		if n != float64(int64(n)) {
			return 0, fmt.Errorf("not an integer")
		}
		return int64(n), nil
	case string:
		return strconv.ParseInt(n, 10, 64)
	}
	return 0, fmt.Errorf("not an integer")
}

// crudCoerceFloat intenta convertir un valor a float64.
func crudCoerceFloat(v any) (float64, error) {
	switch n := v.(type) {
	case int:
		return float64(n), nil
	case int64:
		return float64(n), nil
	case float64:
		return n, nil
	case string:
		return strconv.ParseFloat(n, 64)
	}
	return 0, fmt.Errorf("not a number")
}
