package include

import (
	"agent/defines/devent/dfield"
	"agent/defines/devent/dfield/dresponse"
	"encoding/json"
	"io"

	"github.com/google/uuid"
)

type JsonEvent struct {
	Event  map[string]any
	Data   io.Reader
	Closer io.Closer
}

type EventKind uint16
type EventAction uint16
type EventStatus uint16
type EventCategory uint16
type EventPriority uint16
type EventDataType uint16
type EventType uint16
type EventOwnerType uint16
type EventOpt int

type IEvent interface {
	Signature() string

	CmdId() int
	CmdTag() string
	Uuid() uuid.UUID
	Kind() EventKind
	Category() []EventCategory
	Action() EventAction
	Type() EventType
	Status() EventStatus
	Priority() EventPriority
	Meta() map[string]any
	MetaSize() int64
	Data() (data IData)
	Data2() ([]byte, error)
	DataSize() int64
	SetDataSize(size int64)
	DataMaxSize() int64
	DataType() EventDataType

	SetOptValue(key any, val any)
	OptValue(key any) (val any, ok bool)

	JsonEvent(stream ...bool) (res JsonEvent, err error)
}

type RawEvent struct {
	CmdId  int
	CmdTag string
	// Уникальный идентификатор
	VirtualUuid uuid.UUID
	// Вид (направление) ивента (number)
	Kind EventKind
	// Категория ивента (number)
	Category []EventCategory
	// Операция (number)
	Action EventAction
	// Тип ивента
	Type EventType
	// Статус
	Status EventStatus
	// Приоритет
	Priority EventPriority
	// Метаданные
	Meta map[string]any
	// Размер метаданных
	MetaSize int64
	// Размер данных привязанных к ивенту
	Data     string
	DataSize int64
	// Максимальный размер данных, который может содержать ивент.
	// Если значение не задано, оно определяется из конфига
	DataMaxSize int64
	DataType    EventDataType
}

func (r RawEvent) MarshalJSON() (data []byte, err error) {
	res := map[string]any{}

	if r.CmdId > 0 {
		res[dfield.CmdId] = r.CmdId
	}
	if len(r.CmdTag) > 0 {
		res[dfield.CmdTag] = r.CmdTag
	}
	if r.VirtualUuid != uuid.Nil {
		res[dfield.VirtualUuid] = r.VirtualUuid
	}
	if r.Kind > 0 {
		res[dfield.Kind] = r.Kind
	}
	if len(r.Category) > 0 {
		res[dfield.Category] = r.Category
	}
	if r.Action > 0 {
		res[dfield.Action] = r.Action
	}
	if r.Type > 0 {
		res[dfield.Type] = r.Type
	}
	if r.Status > 0 {
		res[dfield.Status] = r.Status
	}
	if r.Priority > 0 {
		res[dfield.Priority] = r.Priority
	}
	if len(r.Meta) > 0 {
		res[dfield.Meta] = r.Meta
	}
	if r.MetaSize > 0 {
		res[dfield.MetaSize] = r.MetaSize
	}
	if len(r.Data) > 0 {
		res[dfield.Data] = r.Data
	}
	if r.DataSize > 0 {
		res[dfield.DataSize] = r.DataSize
	}
	if r.DataMaxSize > 0 {
		res[dfield.DataMaxSize] = r.DataMaxSize
	}
	if r.DataType > 0 {
		res[dfield.DataType] = r.DataType
	}

	return json.Marshal(res)
}

func (r *RawEvent) UnmarshalJSON(data []byte) error {
	if r == nil {
		return nil
	}
	m := map[string]any{}
	if err := json.Unmarshal(data, &m); err != nil {
		return err
	}

	if raw, ok := m[dfield.CmdId]; ok {
		val, _ := raw.(float64)
		r.CmdId = int(val)
	}
	if raw, ok := m[dfield.CmdTag]; ok {
		val, _ := raw.(string)
		r.CmdTag = val
	}
	if raw, ok := m[dfield.VirtualUuid]; ok {
		val, _ := raw.(string)
		r.VirtualUuid, _ = uuid.Parse(val)
	}
	if raw, ok := m[dfield.Kind]; ok {
		val, _ := raw.(float64)
		r.Kind = EventKind(val)
	}
	if raw, ok := m[dfield.Category]; ok {
		items, _ := raw.([]any)
		r.Category = make([]EventCategory, 0, len(items))
		for _, item := range items {
			val, _ := item.(float64)
			r.Category = append(r.Category, EventCategory(val))
		}
	}
	if raw, ok := m[dfield.Action]; ok {
		val, _ := raw.(float64)
		r.Action = EventAction(val)
	}
	if raw, ok := m[dfield.Type]; ok {
		val, _ := raw.(float64)
		r.Type = EventType(val)
	}
	if raw, ok := m[dfield.Status]; ok {
		val, _ := raw.(float64)
		r.Status = EventStatus(val)
	}
	if raw, ok := m[dfield.Priority]; ok {
		val, _ := raw.(float64)
		r.Priority = EventPriority(val)
	}
	if raw, ok := m[dfield.Meta]; ok {
		r.Meta, _ = raw.(map[string]any)
	}
	if raw, ok := m[dfield.MetaSize]; ok {
		val, _ := raw.(float64)
		r.MetaSize = int64(val)
	}
	if raw, ok := m[dfield.Data]; ok {
		r.Data, _ = raw.(string)
	}
	if raw, ok := m[dfield.DataSize]; ok {
		val, _ := raw.(float64)
		r.DataSize = int64(val)
	}
	if raw, ok := m[dfield.DataMaxSize]; ok {
		val, _ := raw.(float64)
		r.DataMaxSize = int64(val)
	}
	if raw, ok := m[dfield.DataType]; ok {
		val, _ := raw.(float64)
		r.DataType = EventDataType(val)
	}

	return nil
}

type EventRequestOpts = EventRequest

type EventRequest struct {
	// Если "true", ивент будет отправлен параллельно с другими ивентами (по умолчанию - последовательно)
	Parallel bool
	// Обработчик с помощью которого будет отправлен ивент. По умолчанию dhandlers.Json.
	Handler int
	// Option/Reserverd. Указывает на требование отправки ивента в бинарном виде (а не, например, в
	// текстовом json)
	Binary *bool
	// Option. Список HTTP-заголовков которые следует добавить в запрос
	Headers map[string]string
	// Option. Список HTTP Cookies которые следует добавить в запрос
	Cookies map[string]string
	// Option. Настройки мусора.
	MinTrashSize *int64
	MaxTrashSize *int64

	// Option. Настройка http.FormData
	FormData struct {
		// name:value, значения которые будут добавлены в форму
		Values map[string]string

		Event struct {
			// Имя поля в котором будет отправлен файл
			Field string
			// Имя файла в котором будет отправлен ивент
			Filename string
		}
	}
}

// EventResponse содержит поля из Schema(event.response)
type EventResponse struct {
	// Обработчик с помощью которого будет возвращаен ивент
	// Пример: 'application/octet-stream'
	Handler string
	// Тип данных, который будет устновлен при возвращении ивента.
	// Пример: 'application/json'
	Type string
	// Данные зашифрованы
	Encrypt bool
	//  Данные обернуты в base64
	Base64 bool
	// Если "true", в ответе придет только event_data (без event_body)
	DataOnly bool
	// Если "false", данные не ожидаются.
	Exist bool
	// Если "возможно", ответ будет отправлен ввиде файла (для http в Attachment)
	SendFile bool
	// Указывается имя файла, используется с полем "SendFile"
	Filename string
	// Если задано, при отправке данных ввиде файла (тип данных для http, указывается в Content-Type) быдет
	// вычеслен автоматически.
	TypeFromFile bool
	// Настройка параметров мусора
	MinTrashSize int64
	MaxTrashSize int64

	// Следующие поля используются только клиентом (т.е. на сервис не отправляюстя)

	// Если "true", данные могут отсутствовать.
	CanEmpty bool
	// Путь к файлу, куда нужно сохранить данные.
	ToFilePath          string
	MaxDataSizeInMemory int64
}

func (e EventResponse) MarshalJSON() (data []byte, err error) {
	// Все поля опциональны, за исклчением "Exist"
	res := map[string]any{
		dresponse.Exist: e.Exist,
	}

	if len(e.Handler) > 0 {
		res[dresponse.Handler] = e.Handler
	}
	if len(e.Type) > 0 {
		res[dresponse.Type] = e.Type
	}
	if e.Encrypt {
		res[dresponse.Encrypt] = e.Encrypt
	}
	if e.Base64 {
		res[dresponse.Base64] = e.Base64
	}
	if e.DataOnly {
		res[dresponse.DataOnly] = e.DataOnly
	}

	if e.SendFile {
		res[dresponse.SendFile] = e.SendFile
	}
	if len(e.Filename) > 0 {
		res[dresponse.Filename] = e.Filename
	}
	if e.TypeFromFile {
		res[dresponse.TypeFromFile] = e.TypeFromFile
	}
	if e.MinTrashSize > -1 {
		res[dresponse.MinTrashSize] = e.MinTrashSize
	}
	if e.MaxTrashSize > -1 {
		res[dresponse.MaxTrashSize] = e.MaxTrashSize
	}

	return json.Marshal(res)
}

func (e *EventResponse) UnmarshalJSON(data []byte) error {
	if e == nil {
		return nil
	}
	m := map[string]any{}
	if err := json.Unmarshal(data, &m); err != nil {
		return err
	}

	if raw, ok := m[dresponse.Handler]; ok {
		val, _ := raw.(string)
		e.Handler = val
	}
	if raw, ok := m[dresponse.Type]; ok {
		val, _ := raw.(string)
		e.Type = val
	}
	if raw, ok := m[dresponse.Encrypt]; ok {
		val, _ := raw.(bool)
		e.Encrypt = val
	}
	if raw, ok := m[dresponse.Base64]; ok {
		val, _ := raw.(bool)
		e.Base64 = val
	}
	if raw, ok := m[dresponse.DataOnly]; ok {
		val, _ := raw.(bool)
		e.DataOnly = val
	}
	if raw, ok := m[dresponse.Exist]; ok {
		val, _ := raw.(bool)
		e.Exist = val
	}
	if raw, ok := m[dresponse.SendFile]; ok {
		val, _ := raw.(bool)
		e.SendFile = val
	}
	if raw, ok := m[dresponse.Filename]; ok {
		val, _ := raw.(string)
		e.Filename = val
	}
	if raw, ok := m[dresponse.TypeFromFile]; ok {
		val, _ := raw.(string)
		e.ToFilePath = val
	}
	if raw, ok := m[dresponse.MinTrashSize]; ok {
		val, _ := raw.(int64)
		e.MinTrashSize = int64(val)
	} else {
		e.MinTrashSize = -1
	}
	if raw, ok := m[dresponse.MaxTrashSize]; ok {
		val, _ := raw.(int64)
		e.MaxTrashSize = int64(val)
	} else {
		e.MaxTrashSize = -1
	}

	return nil
}

type EventResponseOpts struct {
	Handler      string
	Type         string
	Encrypt      *bool
	Base64       *bool
	DataOnly     *bool
	Exist        *bool
	SendFile     *bool
	Filename     string
	TypeFromFile *bool
	MinTrashSize *int64
	MaxTrashSize *int64

	CanEmpty            *bool
	ToFilePath          string
	MaxDataSizeInMemory int64
}
