package starrocks

import (
	"database/sql"
	"fmt"
	"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
	"gorm.io/gorm"
	"reflect"
	"strings"
)

func Query(params QueryOptions, queryFunc func(params QueryOptions) (*sql.Rows, error)) (*domain.DataTable, error) {
	rows, err := queryFunc(params)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	dataTable := &domain.DataTable{}
	dataTable.Data, err = ScanRows(rows)

	//rows.Columns()
	return dataTable, err
}

type QueryOptions struct {
	TableName string
	Select    []*domain.Field
	Where     []Condition
	Offset    int
	Limit     int
	Context   *domain.Context
}

func (o *QueryOptions) SetOffsetLimit(pageNumber, pageSize int) {
	if pageNumber == 0 {
		pageNumber = 1
	}
	if pageSize == 0 {
		pageSize = 20
	}
	o.Offset = (pageNumber - 1) * pageSize
	o.Limit = pageSize
}

func (o *QueryOptions) SetCondition(conditions []domain.Condition) {
	for _, c := range conditions {
		o.Where = append(o.Where, Condition{
			Condition: c,
		})
	}
}

type Condition struct {
	domain.Condition
	Distinct bool
}

func (c Condition) SetWhere(q *gorm.DB) {
	if len(c.Like) > 0 {
		q.Where(fmt.Sprintf("%v like '%%%v%%'", c.Field.SQLName, c.Like))
	}
	if len(c.In) > 0 {
		q.Where(fmt.Sprintf("%v in %v", c.Field.SQLName, c.InArgs(c.In)))
	}
	if len(c.Ex) > 0 {
		in := c.InArgs(c.Ex)
		q.Where(fmt.Sprintf("%v not in %v", c.Field.SQLName, in))
	}
	if c.Distinct {
		q.Distinct(c.Field.SQLName)
	}
	if len(c.Order) > 0 {
		q.Order(fmt.Sprintf("%v %v", c.Field.SQLName, c.Order))
	}
}
func (c Condition) InArgs(args interface{}) string {
	bytes := make([]byte, 0)
	bytes = appendIn(bytes, reflect.ValueOf(args))
	return string(bytes)
}

func appendIn(b []byte, slice reflect.Value) []byte {
	sliceLen := slice.Len()
	b = append(b, '(')
	for i := 0; i < sliceLen; i++ {
		if i > 0 {
			b = append(b, ',')
		}

		elem := slice.Index(i)
		if elem.Kind() == reflect.Interface {
			elem = elem.Elem()
		}

		if elem.Kind() == reflect.Slice {
			//b = appendIn(b, elem)
		} else {
			b = appendValue(b, elem)
		}
	}
	b = append(b, ')')
	return b
}

func appendValue(b []byte, v reflect.Value) []byte {
	if v.Kind() == reflect.Ptr && v.IsNil() {

		return append(b, "NULL"...)
	}
	if v.Kind() == reflect.Int || v.Kind() == reflect.Int64 || v.Kind() == reflect.Float64 {
		return append(b, []byte(v.String())...)
	}
	b = append(b, []byte("'")...)
	b = append(b, []byte(v.String())...)
	b = append(b, []byte("'")...)
	return b
}

func DefaultQueryFunc(params QueryOptions) (*sql.Rows, error) {
	query := DB.Table(params.TableName)
	rows, err := query.Rows()
	if err != nil {
		return nil, err
	}
	return rows, nil
}

func WrapQueryFuncWithDB(db *gorm.DB) func(QueryOptions) (*sql.Rows, error) {
	return func(params QueryOptions) (*sql.Rows, error) {
		query := db.Table(params.TableName)
		queryWithoutLimitOffset(query, params)
		if params.Offset > 0 {
			query.Offset(params.Offset)
		}
		if params.Limit > 0 {
			query.Limit(params.Limit)
		}
		if params.Context != nil {
			query.Where(fmt.Sprintf("context->>'companyId'='%v'", params.Context.CompanyId))
			//query.Where("context->>'companyId'='?'", params.Context.CompanyId)
		}
		rows, err := query.Rows()
		if err != nil {
			return nil, err
		}
		return rows, nil
	}
}

func SetTable(query *gorm.DB, tableName string) {
	query.Statement.Table = tableName
}

func queryWithoutLimitOffset(query *gorm.DB, params QueryOptions) {
	if len(params.Select) > 0 {
		fields := make([]string, 0)
		for _, f := range params.Select {
			fields = append(fields, f.SQLName)
		}
		query.Select(strings.Join(fields, ","))
	}
	if len(params.Where) > 0 {
		for _, w := range params.Where {
			w.SetWhere(query)
		}
	}
}

func QueryCount(params QueryOptions) (int64, error) {
	var total int64
	query := DB.Table(params.TableName)
	queryWithoutLimitOffset(query, params)
	query.Count(&total)
	return total, query.Error
}

func WrapQueryCountWithDB(params QueryOptions, db *gorm.DB) func() (int64, error) {
	return func() (int64, error) {
		var total int64
		query := db.Table(params.TableName)
		queryWithoutLimitOffset(query, params)
		query.Count(&total)
		return total, query.Error
	}
}