package sq

import (
	"errors"
	"fmt"
	"gogs.mikescher.com/BlackForestBytes/goext/exerr"
	"reflect"
	"strings"
)

func BuildUpdateStatement[TData any](q Queryable, tableName string, obj TData, idColumn string) (string, PP, error) {
	rval := reflect.ValueOf(obj)
	rtyp := rval.Type()

	params := PP{}

	setClauses := make([]string, 0)

	matchClause := ""

	for i := 0; i < rtyp.NumField(); i++ {

		rsfield := rtyp.Field(i)
		rvfield := rval.Field(i)

		if !rsfield.IsExported() {
			continue
		}

		columnName := rsfield.Tag.Get("db")
		if columnName == "" || columnName == "-" {
			continue
		}

		if idColumn == columnName {
			idValue, err := convertValueToDB(q, rvfield.Interface())
			if err != nil {
				return "", nil, err
			}

			matchClause = fmt.Sprintf("(%s = :%s)", columnName, params.Add(idValue))

			continue
		}

		if rsfield.Type.Kind() == reflect.Ptr && rvfield.IsNil() {

			setClauses = append(setClauses, fmt.Sprintf("%s = NULL", columnName))

		} else {

			val, err := convertValueToDB(q, rvfield.Interface())
			if err != nil {
				return "", nil, err
			}

			setClauses = append(setClauses, fmt.Sprintf("%s = :%s", columnName, params.Add(val)))

		}
	}

	if len(setClauses) == 0 {
		return "", nil, exerr.New(exerr.TypeSQLBuild, "no updates clauses found in object").Build()
	}

	if matchClause == "" {
		return "", nil, exerr.New(exerr.TypeSQLBuild, "id column not found in object").Build()
	}

	//goland:noinspection SqlNoDataSourceInspection
	return fmt.Sprintf("UPDATE %s SET %s WHERE %s", tableName, strings.Join(setClauses, ", "), matchClause), params, nil
}

func BuildInsertStatement[TData any](q Queryable, tableName string, obj TData) (string, PP, error) {
	rval := reflect.ValueOf(obj)
	rtyp := rval.Type()

	params := PP{}

	fields := make([]string, 0)
	values := make([]string, 0)

	for i := 0; i < rtyp.NumField(); i++ {

		rsfield := rtyp.Field(i)
		rvfield := rval.Field(i)

		if !rsfield.IsExported() {
			continue
		}

		columnName := rsfield.Tag.Get("db")
		if columnName == "" || columnName == "-" {
			continue
		}

		if rsfield.Type.Kind() == reflect.Ptr && rvfield.IsNil() {

			fields = append(fields, columnName)
			values = append(values, "NULL")

		} else {

			val, err := convertValueToDB(q, rvfield.Interface())
			if err != nil {
				return "", nil, err
			}

			fields = append(fields, columnName)
			values = append(values, ":"+params.Add(val))

		}
	}

	if len(fields) == 0 {
		return "", nil, exerr.New(exerr.TypeSQLBuild, "no fields found in object").Build()
	}

	//goland:noinspection SqlNoDataSourceInspection
	return fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", tableName, strings.Join(fields, ", "), strings.Join(values, ", ")), params, nil
}

func BuildInsertMultipleStatement[TData any](q Queryable, tableName string, vArr []TData) (string, PP, error) {

	if len(vArr) == 0 {
		return "", nil, errors.New("no data supplied")
	}

	rtyp := reflect.ValueOf(vArr[0]).Type()

	sqlPrefix := ""
	{
		columns := make([]string, 0)

		for i := 0; i < rtyp.NumField(); i++ {
			rsfield := rtyp.Field(i)

			if !rsfield.IsExported() {
				continue
			}

			columnName := rsfield.Tag.Get("db")
			if columnName == "" || columnName == "-" {
				continue
			}

			columns = append(columns, "\""+columnName+"\"")
		}

		sqlPrefix = fmt.Sprintf("INSERT"+" INTO \"%s\" (%s) VALUES", tableName, strings.Join(columns, ", "))
	}

	pp := PP{}

	sqlValuesArr := make([]string, 0)

	for _, v := range vArr {

		rval := reflect.ValueOf(v)

		params := make([]string, 0)

		for i := 0; i < rtyp.NumField(); i++ {

			rsfield := rtyp.Field(i)
			rvfield := rval.Field(i)

			if !rsfield.IsExported() {
				continue
			}

			columnName := rsfield.Tag.Get("db")
			if columnName == "" || columnName == "-" {
				continue
			}

			if rsfield.Type.Kind() == reflect.Ptr && rvfield.IsNil() {

				params = append(params, "NULL")

			} else {

				val, err := convertValueToDB(q, rvfield.Interface())
				if err != nil {
					return "", nil, err
				}

				params = append(params, ":"+pp.Add(val))

			}
		}

		sqlValuesArr = append(sqlValuesArr, fmt.Sprintf("(%s)", strings.Join(params, ", ")))
	}

	sqlstr := fmt.Sprintf("%s %s", sqlPrefix, strings.Join(sqlValuesArr, ", "))

	return sqlstr, pp, nil
}