package api

import (
	"agent/client"
	"agent/client/events"
	"agent/commons/adata"
	"agent/commons/debug"
	"agent/commons/utils"
	"agent/defines/devent/daction"
	"agent/defines/devent/dcategory"
	"agent/defines/devent/dkind"
	"bytes"
	"context"
	"encoding/json"

	"github.com/google/uuid"
)

type AgentNextEventsOpts struct {
	Priority        *int64
	Category        []events.EventCategory
	IncludeReceived bool
}

type AgentNextEventsResult struct {
	Status AgentNextEventsResultStatus
	Extra  AgentNextEventsResultExtra
	Events []events.RawEvent
}

func (r AgentNextEventsResult) MarshalJSON() ([]byte, error) {
	jsonData := map[string]any{
		"status": r.Status,
		"extra":  r.Extra,
		"events": r.Events,
	}
	return json.Marshal(jsonData)
}

func (r *AgentNextEventsResult) UnmarshalJSON(data []byte) error {
	jsonData := map[string]any{}
	if err := json.Unmarshal(data, &jsonData); err != nil {
		return err
	}
	if val, ok := jsonData["status"].(map[string]any); ok {
		statusData, err := json.Marshal(val)
		if err != nil {
			return err
		}
		if err = json.Unmarshal(statusData, &r.Status); err != nil {
			return err
		}
	}
	if val, ok := jsonData["extra"].(map[string]any); ok {
		extraData, err := json.Marshal(val)
		if err != nil {
			return err
		}
		if err = json.Unmarshal(extraData, &r.Extra); err != nil {
			return err
		}
	}
	if val, ok := jsonData["events"].([]any); ok {
		for _, v := range val {
			eventData, err := json.Marshal(v)
			if err != nil {
				return err
			}
			var rawEvent events.RawEvent
			if err = json.Unmarshal(eventData, &rawEvent); err != nil {
				return err
			}
			r.Events = append(r.Events, rawEvent)
		}
	}
	return nil
}

type AgentNextEventsResultStatus struct {
	LastIndex     int64
	ReceivedUuids []uuid.UUID
	Errors        int
	Size          int
}

func (s AgentNextEventsResultStatus) MarshalJSON() ([]byte, error) {
	jsonData := map[string]any{
		"last_index":     s.LastIndex,
		"received_uuids": s.ReceivedUuids,
		"errors":         s.Errors,
		"size":           s.Size,
	}
	return json.Marshal(jsonData)
}

func (s *AgentNextEventsResultStatus) UnmarshalJSON(data []byte) error {
	jsonData := map[string]any{}
	if err := json.Unmarshal(data, &jsonData); err != nil {
		return err
	}
	if val, ok := jsonData["last_index"].(float64); ok {
		s.LastIndex = int64(val)
	}
	if val, ok := jsonData["received_uuids"].([]any); ok {
		for _, v := range val {
			if strVal, ok := v.(string); ok {
				if uuidVal, err := uuid.Parse(strVal); err == nil {
					s.ReceivedUuids = append(s.ReceivedUuids, uuidVal)
				}
			}
		}
	}
	if val, ok := jsonData["errors"].(float64); ok {
		s.Errors = int(val)
	}
	if val, ok := jsonData["size"].(float64); ok {
		s.Size = int(val)
	}
	return nil
}

type AgentNextEventsResultExtra struct {
	ConfigVersion int64
}

func (e AgentNextEventsResultExtra) MarshalJSON() ([]byte, error) {
	jsonData := map[string]any{
		"config_version": e.ConfigVersion,
	}
	return json.Marshal(jsonData)
}

func (e *AgentNextEventsResultExtra) UnmarshalJSON(data []byte) error {
	decoder := json.NewDecoder(bytes.NewReader(data))
	decoder.UseNumber()

	jsonData := map[string]any{}
	err := decoder.Decode(&jsonData)
	if err != nil {
		return err
	}

	if num, ok := jsonData["config_version"].(json.Number); ok {
		val, err := num.Int64()
		if err != nil {
			return err
		}
		e.ConfigVersion = val
	}

	return nil
}

func AgentNextEvents(ctx context.Context, client client.IClient,
	lastIndex int64, opts ...AgentNextEventsOpts,
) (res AgentNextEventsResult, err error) {
	event := agentNextEventsEvent(lastIndex, opts...)
	meta, data, err := events.ExecuteAndGetMetaAndData(ctx, client, event)
	if err != nil {
		return res, err
	}
	if res, err = utils.ConvertTo[AgentNextEventsResult](meta); err != nil {
		return res, err
	}
	if len(data) > 0 {
		defer func() {

			if err != nil {
				debug.LogErrf("Data unmarshal error: %v", err)
				debug.LogErrf("Data: %s", string(data))
			}
		}()
		return res, json.Unmarshal(data, &res.Events)
	}
	return res, nil
}

func agentNextEventsEvent(lastIndex int64, opts ...AgentNextEventsOpts) events.IEvent {
	meta := map[string]any{
		"last_index": lastIndex,
	}

	if len(opts) > 0 {
		o := opts[0]
		if len(o.Category) > 0 {
			meta["category"] = o.Category
		}
		if o.Priority != nil {
			meta["priority"] = o.Priority
		}
		if o.IncludeReceived {
			meta["include_received"] = o.IncludeReceived
		}
	}

	return events.NewEvent(
		dkind.AgentToService,
		[]events.EventCategory{dcategory.Agent, dcategory.Event, dcategory.Next},
		daction.Get,
		meta,
	)
}

func AgentRequestEventData(ctx context.Context, client client.IClient,
	uuid uuid.UUID,
) (data adata.IData, err error) {
	eventIn := agentRequestEventDataEvent(uuid)
	eventOut, err := client.Execute(ctx, eventIn)
	if err != nil {
		return nil, err
	}
	return eventOut.Data(), nil
}

func agentRequestEventDataEvent(uuid uuid.UUID) events.IEvent {
	meta := map[string]any{
		"uuid": uuid,
	}
	return events.NewEvent(
		dkind.AgentToService,
		[]events.EventCategory{dcategory.Agent, dcategory.Event, dcategory.Data},
		daction.Get,
		meta,

		events.NewEventOpts{
			Response: &events.EventResponseOpts{
				DataOnly: utils.PointerTo(true),
			},
		},
	)
}
