package infra

import (
	"database/sql"
	"fmt"
	"net/http"
	"strconv"
	"strings"
)

// CRUDListHandler retorna un http.HandlerFunc que lista registros de la tabla del recurso
// con paginacion, orden y filtros tomados de los query params.
// Query params soportados:
//   - page (default 1)
//   - per_page (default 20, max 100)
//   - sort_by (columna valida; default "created_at")
//   - sort_dir ("asc" o "desc"; default "desc")
//   - filter_<field>=<valor> para WHERE exactos (solo campos definidos en el recurso)
//
// Si el recurso es SoftDelete, se agrega automaticamente "WHERE deleted_at IS NULL".
// Retorna un CRUDListResult serializado como JSON.
func CRUDListHandler(res CRUDResource, db *sql.DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		params := parseCRUDListParams(res, r)

		// Construir WHERE
		where := []string{}
		args := []any{}
		if res.SoftDelete {
			where = append(where, "deleted_at IS NULL")
		}
		for col, val := range params.Filters {
			where = append(where, fmt.Sprintf("%s = ?", col))
			args = append(args, val)
		}
		whereSQL := ""
		if len(where) > 0 {
			whereSQL = " WHERE " + strings.Join(where, " AND ")
		}

		// COUNT total
		countSQL := fmt.Sprintf("SELECT COUNT(*) FROM %s%s", res.Table, whereSQL)
		var total int
		if err := db.QueryRow(countSQL, args...).Scan(&total); err != nil {
			HTTPErrorResponse(w, HTTPError{Status: http.StatusInternalServerError, Code: "db_error", Message: err.Error()})
			return
		}

		// SELECT paginado
		offset := (params.Page - 1) * params.PerPage
		selectSQL := fmt.Sprintf(
			"SELECT * FROM %s%s ORDER BY %s %s LIMIT ? OFFSET ?",
			res.Table, whereSQL, params.SortBy, strings.ToUpper(params.SortDir),
		)
		selectArgs := append(append([]any{}, args...), params.PerPage, offset)
		rows, err := db.Query(selectSQL, selectArgs...)
		if err != nil {
			HTTPErrorResponse(w, HTTPError{Status: http.StatusInternalServerError, Code: "db_error", Message: err.Error()})
			return
		}
		defer rows.Close()

		cols, err := rows.Columns()
		if err != nil {
			HTTPErrorResponse(w, HTTPError{Status: http.StatusInternalServerError, Code: "db_error", Message: err.Error()})
			return
		}

		items := []map[string]any{}
		for rows.Next() {
			row, err := crudScanRow(rows, cols)
			if err != nil {
				HTTPErrorResponse(w, HTTPError{Status: http.StatusInternalServerError, Code: "db_error", Message: err.Error()})
				return
			}
			items = append(items, row)
		}
		if err := rows.Err(); err != nil {
			HTTPErrorResponse(w, HTTPError{Status: http.StatusInternalServerError, Code: "db_error", Message: err.Error()})
			return
		}

		totalPages := 0
		if params.PerPage > 0 {
			totalPages = (total + params.PerPage - 1) / params.PerPage
		}
		result := CRUDListResult{
			Items:      items,
			Total:      total,
			Page:       params.Page,
			PerPage:    params.PerPage,
			TotalPages: totalPages,
		}
		HTTPJSONResponse(w, http.StatusOK, result)
	}
}

// parseCRUDListParams extrae CRUDListParams desde los query params, aplicando
// defaults y validando los nombres de campo contra la definicion del recurso
// para evitar SQL injection en sort_by y filter_*.
func parseCRUDListParams(res CRUDResource, r *http.Request) CRUDListParams {
	q := r.URL.Query()

	page, _ := strconv.Atoi(q.Get("page"))
	if page < 1 {
		page = 1
	}

	perPage, _ := strconv.Atoi(q.Get("per_page"))
	if perPage < 1 {
		perPage = 20
	}
	if perPage > 100 {
		perPage = 100
	}

	sortBy := q.Get("sort_by")
	if sortBy == "" || !isSortableColumn(res, sortBy) {
		sortBy = "created_at"
	}

	sortDir := strings.ToLower(q.Get("sort_dir"))
	if sortDir != "asc" && sortDir != "desc" {
		sortDir = "desc"
	}

	filters := map[string]string{}
	for key, vals := range q {
		if !strings.HasPrefix(key, "filter_") {
			continue
		}
		col := strings.TrimPrefix(key, "filter_")
		if crudFieldByName(res, col) == nil {
			continue // campo desconocido, se ignora (defensa SQLi)
		}
		if len(vals) > 0 {
			filters[col] = vals[0]
		}
	}

	return CRUDListParams{
		Page:    page,
		PerPage: perPage,
		SortBy:  sortBy,
		SortDir: sortDir,
		Filters: filters,
	}
}

// isSortableColumn indica si la columna pertenece al recurso o es una columna base.
func isSortableColumn(res CRUDResource, col string) bool {
	if col == "id" || col == "created_at" || col == "updated_at" {
		return true
	}
	if res.SoftDelete && col == "deleted_at" {
		return true
	}
	return crudFieldByName(res, col) != nil
}
