package utils

import (
	"database/sql"
)

// SQLResult holds the result of a SQL query.
//
// It contains the count of rows, the columns present, and the actual row data.
type SQLResult struct {
	Count   int           // Count is the number of rows returned.
	Columns []string      // Columns is the slice of column names.
	Rows    []interface{} // Rows is a slice of row data, where each row is a map of column name to value.
}

// UnmarshalSQLRows converts sql.Rows into a more structured SQLResult.
//
// This function takes *sql.Rows as input and attempts to unmarshal the data into
// a SQLResult struct. It handles different SQL data types by using the appropriate
// sql.Null* types during scanning. It returns a pointer to a SQLResult or an error.
//
// The function closes the sql.Rows when finished.
func UnmarshalSQLRows(rows *sql.Rows) (*SQLResult, error) {
	defer func() {
		_ = rows.Close()
	}()
	columnTypes, err := rows.ColumnTypes()
	if err != nil {
		return nil, err
	}
	result := &SQLResult{}
	result.Columns, err = rows.Columns()
	if err != nil {
		return nil, err
	}

	count := len(columnTypes)
	for rows.Next() {
		result.Count++
		scanArgs := make([]interface{}, count)
		for i, v := range columnTypes {
			switch v.DatabaseTypeName() {
			case "VARCHAR", "TEXT", "UUID", "TIMESTAMP":
				scanArgs[i] = new(sql.NullString)
			case "BOOL":
				scanArgs[i] = new(sql.NullBool)
			case "INT4":
				scanArgs[i] = new(sql.NullInt64)
			default:
				scanArgs[i] = new(sql.NullString)
			}
		}
		err := rows.Scan(scanArgs...)
		if err != nil {
			// Return the result accumulated so far along with the error.
			return result, err
		}
		masterData := make(map[string]interface{})
		for i, v := range columnTypes {
			if z, ok := (scanArgs[i]).(*sql.NullBool); ok {
				masterData[v.Name()] = z.Bool
				continue
			}

			if z, ok := (scanArgs[i]).(*sql.NullString); ok {
				masterData[v.Name()] = z.String
				continue
			}

			if z, ok := (scanArgs[i]).(*sql.NullInt64); ok {
				masterData[v.Name()] = z.Int64
				continue
			}

			if z, ok := (scanArgs[i]).(*sql.NullFloat64); ok {
				masterData[v.Name()] = z.Float64
				continue
			}

			if z, ok := (scanArgs[i]).(*sql.NullInt32); ok {
				masterData[v.Name()] = z.Int32
				continue
			}

			masterData[v.Name()] = scanArgs[i]
		}
		result.Rows = append(result.Rows, masterData)
	}
	return result, nil
}
