package rest

import (
	"fmt"
	"io"
	"reflect"

	"git.ali33.ru/fcg-xvii/go-tools/json"
)

func prefixFieldName(prefix, fieldName string) string {
	if len(prefix) > 0 {
		return fmt.Sprintf("%s.%s", prefix, fieldName)
	}
	return fieldName
}

// конверторы

// Int64ToBytes упаковывает int64 в срез байтов заданной длины
func Int64ToBytes(num int64, byteCount int) []byte {
	bytes := make([]byte, byteCount)
	for i := 0; i < byteCount; i++ {
		shift := uint((byteCount - 1 - i) * 8)
		bytes[i] = byte(num >> shift)
	}
	return bytes
}

// BytesToInt64 конвертирует срез байтов в int64
func BytesToInt64(bytes []byte) int64 {
	var num int64
	for _, b := range bytes {
		num = (num << 8) | int64(b)
	}
	return num
}

// ioError возвращает ошибку ввода выввода
func ioError(field string, err error) IErrorArgs {
	return NewError(
		"ErrIO",
		json.Map{
			"field": field,
			"error": err.Error(),
		},
	)
}

// чтение

// ReadBuf считывает срез заданного колличества байт и возвращает его
func ReadBuf(r io.Reader, size int, field string) ([]byte, IErrorArgs) {
	buf := make([]byte, size)
	if _, err := r.Read(buf); err != nil {
		return nil, ioError(field, err)
	}
	return buf, nil
}

// ReadBufSize считывает размер байтового среза, затем сам срез и возвращает его.
// lenSize - количество байтов, которыми описывается размер основного среза (см. ReadInt64)
func ReadBufSize(r io.Reader, lenSize int, field string) ([]byte, IErrorArgs) {
	var size int64
	if err := ReadInt64(r, lenSize, field+"_size", &size); err != nil {
		return nil, err
	}
	buf, err := ReadBuf(r, int(size), field)
	return buf, err
}

// ReadInt64 Считывает срез заданного размера, который конвертируется в число типа int64.
// size - размер среза (может быть от 1 до 8 байт).
// Результат записывается в значение указателя result
func ReadInt64(r io.Reader, size int, field string, result *int64) IErrorArgs {
	buf := make([]byte, size)
	if _, err := r.Read(buf); err != nil {
		return ioError(field, err)
	}
	*result = BytesToInt64(buf)
	return nil
}

// ReadString считывает строку (длина строки, затем строку), см. ReadBufSize
func ReadString(r io.Reader, lenSize int, field string, result *string) IErrorArgs {
	strBuf, err := ReadBufSize(r, lenSize, field)
	if err != nil {
		return err
	}
	*result = string(strBuf)
	return nil
}

// ReadByte считывает 1 байт
func ReadByte(r io.Reader, field string, result *byte) IErrorArgs {
	buf, err := ReadBuf(r, 1, field)
	if err != nil {
		return err
	}
	*result = buf[0]
	return nil
}

// запись

// WriteBuf записывает срез байтов
func WriteBuf(w io.Writer, buf []byte, field string) IErrorArgs {
	if _, err := w.Write(buf); err != nil {
		return ioError(field, err)
	}
	return nil
}

// WriteInt64 конвертирует число типа int64 в срез заданного размера и записывает его
func WriteInt64(w io.Writer, val int64, size int, field string) IErrorArgs {
	buf := Int64ToBytes(val, size)
	return WriteBuf(w, buf, field)
}

// WriteString записывает строку в поток.
// Сначала записывается размер строки (lenSize - количество байт в срезе размера).
// Далее строка конвертируется в байтовый срез и записывается в поток
func WriteString(w io.Writer, val, field string, lenSize int) IErrorArgs {
	// длина
	if err := WriteInt64(w, int64(len(val)), lenSize, field+"_size"); err != nil {
		return err
	}
	// строка
	if len(val) == 0 {
		return nil
	}
	return WriteBuf(w, []byte(val), field)
}

// WriteBuf записывает 1 байт в поток
func WriteByte(w io.Writer, val byte, field string) IErrorArgs {
	return WriteBuf(w, []byte{val}, field)
}

// WriteBufSize записывает срез байтов в поток.
// Сначала записывается размер среза (lenSize - количество байт в срезе размера).
// Далее в поток записывается сам срез
func WriteBufSize(w io.Writer, val []byte, lenSize int, field string) IErrorArgs {
	// длина
	if err := WriteInt64(w, int64(len(val)), lenSize, field+"_size"); err != nil {
		return err
	}
	// буфер
	return WriteBuf(w, val, field)
}

// ObjectFieldKeys позволяет рекурсивно получить список вложенных объектов запроса для предварительного формирования их в контексте ответа
func ObjectFieldKeys(l []any) (res json.Map) {
	res = make(json.Map)
	t := reflect.TypeOf(res)
	for _, v := range l {
		rv := reflect.ValueOf(v)
		if reflect.ValueOf(v).Kind() == reflect.Map {
			if rv.CanConvert(t) {
				m := rv.Convert(t).Interface().(json.Map)
				if name := m.String("name", ""); len(name) > 0 {
					res[name] = ObjectFieldKeys(m.Slice("fields", []any{}))
				}
			}
		}
	}
	return
}