package cli

import (
	"bytes"
	"encoding/json"
	"fmt"
	"git.nspix.com/golang/kos/util/pool"
	"github.com/mattn/go-runewidth"
	"reflect"
	"strconv"
	"strings"
	"time"
)

func isNormalKind(kind reflect.Kind) bool {
	normalKinds := []reflect.Kind{
		reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int,
		reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint,
		reflect.Float32, reflect.Float64,
		reflect.String,
	}
	for _, k := range normalKinds {
		if k == kind {
			return true
		}
	}
	return false
}

func serializeMap(val map[any]any) ([]byte, error) {
	var (
		canFormat bool
		width     int
		maxWidth  int
	)
	canFormat = true
	for k, v := range val {
		if !isNormalKind(reflect.Indirect(reflect.ValueOf(k)).Kind()) || !isNormalKind(reflect.Indirect(reflect.ValueOf(v)).Kind()) {
			canFormat = false
			break
		}
	}
	if !canFormat {
		return json.MarshalIndent(val, "", "\t")
	}
	ms := make(map[string]string)
	for k, v := range val {
		sk := fmt.Sprint(k)
		ms[sk] = fmt.Sprint(v)
		width = runewidth.StringWidth(sk)
		if width > maxWidth {
			maxWidth = width
		}
	}
	buffer := pool.GetBuffer()
	defer pool.PutBuffer(buffer)
	for k, v := range ms {
		buffer.WriteString(fmt.Sprintf("%-"+strconv.Itoa(maxWidth+4)+"s %s\n", k, v))
	}
	return buffer.Bytes(), nil
}

func printBorder(w *bytes.Buffer, ws []int) {
	for _, l := range ws {
		w.WriteString("+")
		w.WriteString(strings.Repeat("-", l+2))
	}
	w.WriteString("+\n")
}

func toString(v any) string {
	switch t := v.(type) {
	case float32, float64:
		return fmt.Sprintf("%.2f", t)
	case time.Time:
		return t.Format("2006-01-02 15:04:05")
	default:
		return fmt.Sprint(v)
	}
}

func printArray(vals [][]any) (buf []byte) {
	var (
		cell      string
		str       string
		widths    []int
		maxLength int
		width     int
		rows      [][]string
	)
	rows = make([][]string, 0, len(vals))
	for _, value := range vals {
		if len(value) > maxLength {
			maxLength = len(value)
		}
	}
	widths = make([]int, maxLength)
	for _, vs := range vals {
		rl := len(vs)
		row := make([]string, rl)
		for i, val := range vs {
			str = toString(val)
			if rl > 1 {
				width = runewidth.StringWidth(str)
				if width > widths[i] {
					widths[i] = width
				}
			}
			row[i] = str
		}
		rows = append(rows, row)
	}
	buffer := pool.GetBuffer()
	defer pool.PutBuffer(buffer)
	printBorder(buffer, widths)
	for index, row := range rows {
		size := len(row)
		for i, w := range widths {
			cell = ""
			buffer.WriteString("|")
			if size > i {
				cell = row[i]
			}
			buffer.WriteString(" ")
			buffer.WriteString(cell)
			cl := runewidth.StringWidth(cell)
			if w > cl {
				buffer.WriteString(strings.Repeat(" ", w-cl))
			}
			buffer.WriteString(" ")
		}
		buffer.WriteString("|\n")
		if index == 0 {
			printBorder(buffer, widths)
		}
	}
	printBorder(buffer, widths)
	return buffer.Bytes()
}

func serializeArray(val []any) (buf []byte, err error) {
	var (
		ok              bool
		vs              [][]any
		normalFormat    bool
		isArrayElement  bool
		isStructElement bool
		columnName      string
	)
	normalFormat = true
	for _, row := range val {
		kind := reflect.Indirect(reflect.ValueOf(row)).Kind()
		if !isNormalKind(kind) {
			normalFormat = false
		}
		if kind == reflect.Array || kind == reflect.Slice {
			isArrayElement = true
		}
		if kind == reflect.Struct {
			isStructElement = true
		}
	}
	if normalFormat {
		goto __END
	}
	if isArrayElement {
		vs = make([][]any, 0, len(val))
		for _, v := range val {
			rv := reflect.Indirect(reflect.ValueOf(v))
			if rv.Kind() == reflect.Array || rv.Kind() == reflect.Slice {
				row := make([]any, 0, rv.Len())
				for i := 0; i < rv.Len(); i++ {
					if isNormalKind(rv.Index(i).Elem().Kind()) || rv.Index(i).Interface() == nil {
						row = append(row, rv.Index(i).Interface())
					} else {
						goto __END
					}
				}
				vs = append(vs, row)
			} else {
				goto __END
			}
		}
	}
	if isStructElement {
		vs = make([][]any, 0, len(val))
		for i, v := range val {
			rv := reflect.Indirect(reflect.ValueOf(v))
			if rv.Kind() == reflect.Struct {
				if i == 0 {
					row := make([]any, 0, rv.Type().NumField())
					for j := 0; j < rv.Type().NumField(); j++ {
						st := rv.Type().Field(j).Tag
						if columnName, ok = st.Lookup("name"); !ok {
							columnName = strings.ToUpper(rv.Type().Field(j).Name)
						}
						row = append(row, columnName)
					}
					vs = append(vs, row)
				}
				row := make([]any, 0, rv.Type().NumField())
				for j := 0; j < rv.Type().NumField(); j++ {
					row = append(row, rv.Field(j).Interface())
				}
				vs = append(vs, row)
			} else {
				goto __END
			}
		}
	}
	buf = printArray(vs)
	return
__END:
	return json.MarshalIndent(val, "", "\t")
}

func serialize(val any) (buf []byte, err error) {
	var (
		refVal reflect.Value
	)
	refVal = reflect.Indirect(reflect.ValueOf(val))
	switch refVal.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		buf = []byte(strconv.FormatInt(refVal.Int(), 10))
	case reflect.Float32, reflect.Float64:
		buf = []byte(strconv.FormatFloat(refVal.Float(), 'f', -1, 64))
	case reflect.String:
		buf = []byte(refVal.String())
	case reflect.Slice, reflect.Array:
		if refVal.Type().Elem().Kind() == reflect.Uint8 {
			buf = refVal.Bytes()
		} else {
			as := make([]any, 0, refVal.Len())
			for i := 0; i < refVal.Len(); i++ {
				as = append(as, refVal.Index(i).Interface())
			}
			buf, err = serializeArray(as)
		}
	case reflect.Map:
		ms := make(map[any]any)
		keys := refVal.MapKeys()
		for _, key := range keys {
			ms[key.Interface()] = refVal.MapIndex(key).Interface()
		}
		buf, err = serializeMap(ms)
	default:
		buf, err = json.MarshalIndent(refVal.Interface(), "", "\t")
	}
	return
}