package advance

import (
	"bytes"
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

type (
	Model struct {
		mc   MapColumn
		name string
	}
	AdvancedQuery struct {
		Column Column `json:"column"`
		Exprs  []Expr `json:"exprs"`
	}
	ColumnExprResult struct {
		Column Column
		Result []ExprResult
	}
	// 高级查询参数
	AdvancedQueries []AdvancedQuery
)

var registerModels = make(map[string]Model)

func RegisModel(m Model) {
	if _, ok := registerModels[m.name]; ok {
		panic("register modes exists:" + m.name)
	}
	registerModels[m.name] = m
}

func NewModel(name string, columns []Column) Model {
	return Model{
		name: name,
		mc:   NewMapColumn(columns),
	}
}

func ComputeColumnExpr(queries []AdvancedQuery) []ColumnExprResult {
	var result = make([]ColumnExprResult, 0)
	queries = mergeQuery(queries)
	for i := range queries {
		q := queries[i]
		var tmpResult []ExprResult
		switch q.Column.ValueType {
		case ValueNumber:
			tmpResult = append(JoinColumnExprNumber(q.Exprs))
		case ValueChars:
			tmpResult = append(JoinColumnExprChars(q.Exprs))
		case ValueDate:
			tmpResult = append(JoinColumnExprDate(q.Exprs))
		}
		if len(tmpResult) == 0 {
			continue
		}
		result = append(result, ColumnExprResult{
			Column: q.Column,
			Result: tmpResult,
		})
	}
	return result
}

func JoinColumnExprNumber(expr []Expr) []ExprResult {
	var ec = []exprCompute{NewInExprCompute(expr, ValueNumber), NewRangeExprCompute(expr, ValueNumber), NewLessGreaterExprCompute(expr, ValueNumber), NewNotEqualExprCompute(expr, ValueNumber), NewLikeExprCompute(expr, ValueNumber)}
	return joinExprResult(ec)
}

func JoinColumnExprDate(expr []Expr) []ExprResult {
	var ec = []exprCompute{NewRangeExprCompute(expr, ValueDate), NewRecentDateExprCompute(expr, ValueDate), NewNotEqualExprCompute(expr, ValueChars)}
	return joinExprResult(ec)
}

func JoinColumnExprChars(expr []Expr) []ExprResult {
	var ec = []exprCompute{NewInExprCompute(expr, ValueChars), NewLikeExprCompute(expr, ValueChars), NewNotEqualExprCompute(expr, ValueChars)}
	return joinExprResult(ec)
}

type MapColumn map[string]Column

func NewMapColumn(cols []Column) MapColumn {
	mapColumn := make(map[string]Column)
	for i := range cols {
		mapColumn[cols[i].Column] = cols[i]
	}
	return mapColumn
}

func (m MapColumn) FindItem(col string) Column {
	if item, ok := m[col]; ok {
		return item
	}
	return Column{}
}

type PgSqlGenerator struct{}

func (p PgSqlGenerator) SqlGen(q ColumnExprResult) (string, error) {
	sql := bytes.NewBuffer(nil)
	sql.WriteString("(")
	var wheres []string
	for i := range q.Result {
		item := q.Result[i]
		if err := p.PreCheckAndFormat(&item); err != nil {
			return "", err
		}
		var where string
		switch item.OpChar {
		case In:
			where = p.In(q.Column, item.Value)
		case NotIn:
			where = p.NotIn(q.Column, item.Value)
		case Like:
			where = p.Like(q.Column, item.Value)
		case Range:
			where = p.Range(q.Column, item)
		default:
			where = fmt.Sprintf(" %s %s %v ", q.Column.DbAlias, item.OpChar, item.Value[0])
		}
		if len(where) == 0 {
			continue
		}
		wheres = append(wheres, where)
	}
	sql.WriteString(strings.Join(wheres, sepOr))
	sql.WriteString(")")
	return sql.String(), nil
}
func (p PgSqlGenerator) PreCheckAndFormat(rs *ExprResult) error {
	if rs.ValueType == ValueNumber || rs.ValueType == ValueDate {
		for i := range rs.Value {
			v := rs.Value[i]
			if isInfinity(v) {
				continue
			}
			if _, err := strconv.ParseFloat(fmt.Sprintf("%v", v), 64); err != nil {
				return fmt.Errorf("不是有效的数值类型:%v", v)
			}
		}
	}
	if rs.ValueType == ValueChars {
		for i := range rs.Value {
			v, ok := rs.Value[i].(string)
			err := fmt.Errorf("不是有效的字符串类型:%v", v)
			if !ok {
				return err
			}
			if ok, e := regexp.MatchString("[!%&()*+,-/=?^`'{|}~]", v); ok || e != nil {
				return fmt.Errorf("非法字符:%v", v)
			}
		}
	}
	return nil
}
func (p PgSqlGenerator) In(c Column, values []interface{}) string {
	if len(values) < 1 {
		return ""
	}
	var ret []string
	for i := range values {
		if c.ValueType == ValueNumber {
			ret = append(ret, fmt.Sprintf("%v", values[i]))
		} else {
			ret = append(ret, fmt.Sprintf("'%v'", values[i]))
		}
	}
	return fmt.Sprintf("%s in (%s)", c.DbAlias, strings.Join(ret, ","))
}

func (p PgSqlGenerator) NotIn(c Column, values []interface{}) string {
	if len(values) < 1 {
		return ""
	}
	var ret []string
	for i := range values {
		if c.ValueType == ValueNumber {
			ret = append(ret, fmt.Sprintf("%v", values[i]))
		} else {
			ret = append(ret, fmt.Sprintf("'%v'", values[i]))
		}
	}
	return fmt.Sprintf("%s not in (%s)", c.DbAlias, strings.Join(ret, ","))
}

func (p PgSqlGenerator) Like(c Column, values []interface{}) string {
	if len(values) < 1 {
		return ""
	}
	sql := bytes.NewBuffer(nil)
	sql.WriteString("(")
	var wheres []string
	for i := range values {
		if c.ValueType == ValueNumber {
			wheres = append(wheres, fmt.Sprintf("%s::text like '%%%v%%'", c.DbAlias, values[i]))
		} else {
			wheres = append(wheres, fmt.Sprintf("%s like '%%%v%%'", c.DbAlias, values[i]))
		}
	}
	sql.WriteString(strings.Join(wheres, sepOr))
	sql.WriteString(")")
	return sql.String()
}

func (p PgSqlGenerator) Range(c Column, res ExprResult) string {
	if len(res.Value) != 2 {
		return ""
	}
	sql := bytes.NewBuffer(nil)
	sql.WriteString("(")
	var wheres []string
	if !isInfinity(res.Value[0]) {
		wheres = append(wheres, fmt.Sprintf(" %s %s %v ", c.DbAlias, res.LeftOp, res.Value[0]))
	}
	if !isInfinity(res.Value[1]) {
		wheres = append(wheres, fmt.Sprintf(" %s %s %v ", c.DbAlias, res.RightOp, res.Value[1]))
	}
	sql.WriteString(strings.Join(wheres, sepAnd))
	sql.WriteString(")")
	return sql.String()
}

// AdvancedQuerySql 高级查询Sql生成器
func AdvancedQuerySql(queries []AdvancedQuery) (string, error) {
	if len(queries) == 0 {
		return "", nil
	}
	gen := PgSqlGenerator{}
	results := ComputeColumnExpr(queries)
	if len(results) == 0 {
		return "", nil
	}
	sql := bytes.NewBuffer(nil)
	var wheres []string
	for i := range results {
		condition, err := gen.SqlGen(results[i])
		if err != nil {
			return "", err
		}
		wheres = append(wheres, condition)
	}
	sql.WriteString(strings.Join(wheres, sepAnd))
	// 空条件 ()
	if sql.String() == "()" {
		return "", nil
	}
	if sql.String() == "(())" {
		return "", nil
	}
	return sql.String(), nil
}

func mergeQuery(queries []AdvancedQuery) []AdvancedQuery {
	var rsp = make([]AdvancedQuery, 0)
	var mapColumn = make(map[string]AdvancedQuery)

	for i := range queries {
		var item AdvancedQuery
		var ok bool
		if item, ok = mapColumn[queries[i].Column.Column]; !ok {
			mapColumn[queries[i].Column.Column] = queries[i]
		} else {
			item.Exprs = append(item.Exprs, queries[i].Exprs...)
		}
	}
	for _, v := range mapColumn {
		rsp = append(rsp, v)
	}
	return rsp
}