package api

import (
	"agent/client/events"
	"agent/commons/adata"
	"agent/commons/utime"
	"agent/defines/devent/daction"
	"agent/defines/devent/dcategory"
	"agent/defines/devent/dkind"
	"context"
	"encoding/base64"
	"encoding/json"
)

type AgentLoginResult struct {
	Config map[string]any

	Token struct {
		Value string
	}

	SrvTime int64
	MaskKey []byte
}

func (r AgentLoginResult) MarshalJSON() ([]byte, error) {
	jsonData := map[string]any{
		"config": r.Config,
		"token": map[string]any{
			"value": r.Token.Value,
		},
		"srv_time": r.SrvTime,
		"mask_key": r.MaskKey,
	}
	return json.Marshal(jsonData)
}

func (r *AgentLoginResult) UnmarshalJSON(data []byte) error {
	jsonData := map[string]any{}
	if err := json.Unmarshal(data, &jsonData); err != nil {
		return err
	}
	if val, ok := jsonData["config"].(map[string]any); ok {
		r.Config = val
	}
	if tokenData, ok := jsonData["token"].(map[string]any); ok {
		if val, ok := tokenData["value"].(string); ok {
			r.Token.Value = val
		}
	}
	if val, ok := jsonData["srv_time"].(float64); ok {
		r.SrvTime = int64(val)
	}
	if raw, ok := jsonData["mask_key"].(string); ok {
		val, err := base64.StdEncoding.DecodeString(raw)
		if err != nil {
			return err
		}
		r.MaskKey = val
	}
	return nil
}

func AgentLogin(ctx context.Context, client IClient, startupInfo map[string]any) (res AgentLoginResult, err error) {
	event, err := agentLoginEvent(startupInfo)
	if err != nil {
		return res, nil
	}
	data, err := events.ExecuteAndGetData(ctx, client, event)
	if err != nil {
		return res, err
	}
	return res, json.Unmarshal(data, &res)
}

func agentLoginEvent(startupInfo map[string]any) (events.IEvent, error) {
	meta := map[string]any{
		"utime": utime.NanoNow(),
	}

	data, err := json.Marshal(startupInfo)
	if err != nil {
		return nil, err
	}

	minTrashSize := int64(0)
	maxTrashSize := int64(1024)

	return events.NewEvent(
		dkind.AgentToService,
		[]events.EventCategory{dcategory.Agent},
		daction.Login,
		meta,

		events.NewEventOpts{
			Data: adata.NewDataFromMem(data),

			Request: &events.EventRequestOpts{
				Parallel:     true,
				MinTrashSize: &minTrashSize,
				MaxTrashSize: &maxTrashSize,
			},
			Response: &events.EventResponseOpts{
				MinTrashSize: &minTrashSize,
				MaxTrashSize: &maxTrashSize,
			},
		},
	), nil
}

type AgentPingResult struct {
	Utime int64
}

func (r AgentPingResult) MarshalJSON() ([]byte, error) {
	jsonData := map[string]any{
		"utime": r.Utime,
	}
	return json.Marshal(jsonData)
}

func (r *AgentPingResult) UnmarshalJSON(data []byte) error {
	jsonData := map[string]any{}
	if err := json.Unmarshal(data, &jsonData); err != nil {
		return err
	}
	if val, ok := jsonData["utime"].(float64); ok {
		r.Utime = int64(val)
	}
	return nil
}

func AgentPing(ctx context.Context, client IClient,
	utime int64,
) (res AgentPingResult, err error) {
	event := agentPingEvent(utime)
	data, err := events.ExecuteAndGetData(ctx, client, event)
	if err != nil {
		return res, err
	}
	return res, json.Unmarshal(data, &res)
}

func agentPingEvent(utime int64) events.IEvent {
	meta := map[string]any{}

	if utime > 0 {
		meta["utime"] = utime
	}

	minTrashSize := int64(32)
	maxTrashSize := int64(128)

	return events.NewEvent(
		dkind.AgentToService,
		[]events.EventCategory{dcategory.Agent},
		daction.Ping,
		meta,

		events.NewEventOpts{
			Request: &events.EventRequestOpts{
				Parallel:     true,
				MinTrashSize: &minTrashSize,
				MaxTrashSize: &maxTrashSize,
			},
			Response: &events.EventResponseOpts{
				MinTrashSize: &minTrashSize,
				MaxTrashSize: &maxTrashSize,
			},
		},
	)
}

func AgentConfig(ctx context.Context, client IClient) (res map[string]any, err error) {
	event := agentConfigEvent()
	data, err := events.ExecuteAndGetData(ctx, client, event)
	if err != nil {
		return res, err
	}
	return res, json.Unmarshal(data, &res)
}

func agentConfigEvent() events.IEvent {
	meta := map[string]any{}

	minTrashSize := int64(0)
	maxTrashSize := int64(1024)

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

		events.NewEventOpts{
			Request: &events.EventRequestOpts{
				Parallel:     true,
				MinTrashSize: &minTrashSize,
				MaxTrashSize: &maxTrashSize,
			},
			Response: &events.EventResponseOpts{
				MinTrashSize: &minTrashSize,
				MaxTrashSize: &maxTrashSize,
			},
		},
	)
}
