package infra

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

// DBQuery executes a SELECT query and returns the results as a slice of maps.
// Each map key is the column name; values are converted to native Go types:
// int64, float64, bool, string, []byte, or nil for NULLs.
func DBQuery(db *sql.DB, query string, args ...any) ([]map[string]any, error) {
	rows, err := db.Query(query, args...)
	if err != nil {
		return nil, fmt.Errorf("db_query: %w", err)
	}
	defer rows.Close()

	cols, err := rows.Columns()
	if err != nil {
		return nil, fmt.Errorf("db_query: columns: %w", err)
	}
	colTypes, err := rows.ColumnTypes()
	if err != nil {
		return nil, fmt.Errorf("db_query: column types: %w", err)
	}

	var results []map[string]any
	for rows.Next() {
		// Use RawBytes so we can inspect the raw value before converting.
		raw := make([]sql.RawBytes, len(cols))
		ptrs := make([]any, len(cols))
		for i := range raw {
			ptrs[i] = &raw[i]
		}
		if err := rows.Scan(ptrs...); err != nil {
			return nil, fmt.Errorf("db_query: scan: %w", err)
		}

		row := make(map[string]any, len(cols))
		for i, col := range cols {
			if raw[i] == nil {
				row[col] = nil
				continue
			}
			row[col] = convertRaw(raw[i], colTypes[i].DatabaseTypeName())
		}
		results = append(results, row)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("db_query: rows: %w", err)
	}
	return results, nil
}

// convertRaw attempts to convert a raw SQL byte slice into a native Go type
// based on the database column type name hint.
func convertRaw(b sql.RawBytes, dbType string) any {
	s := string(b)
	switch dbType {
	case "INTEGER", "INT", "BIGINT", "SMALLINT", "TINYINT", "INT2", "INT4", "INT8":
		if v, err := strconv.ParseInt(s, 10, 64); err == nil {
			return v
		}
	case "REAL", "FLOAT", "DOUBLE", "NUMERIC", "DECIMAL":
		if v, err := strconv.ParseFloat(s, 64); err == nil {
			return v
		}
	case "BOOLEAN", "BOOL":
		if v, err := strconv.ParseBool(s); err == nil {
			return v
		}
	case "BLOB":
		cp := make([]byte, len(b))
		copy(cp, b)
		return cp
	}
	return s
}
