package api

import (
	"agent/commons/adata"
	"agent/commons/debug"
	"agent/commons/utils"
	"agent/defines/devent/daction"
	"agent/defines/devent/dcategory"
	"agent/defines/devent/dkind"
	"agent/modules/console/internal/include"
	"context"
	"encoding/json"

	"github.com/google/uuid"
)

func AgentConsoleSetOutput(ctx context.Context, client IClient,
	consoleUuid UUID, commandUuid UUID, lines []string,
) error {
	jsonEvent, err := agentConsoleSetOutputEvent(consoleUuid, commandUuid, lines)
	if err != nil {
		return err
	}
	_, err = client.Execute(ctx, jsonEvent)
	return err
}

func agentConsoleSetOutputEvent(
	consoleUuid UUID, commandUuid UUID, lines []string,
) (IEvent, error) {
	meta := map[string]any{
		"console_uuid": consoleUuid,
	}
	if commandUuid != uuid.Nil {
		meta["command_uuid"] = commandUuid
	}

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

	return NewEvent(
		dkind.AgentToService,
		[]Category{dcategory.Agent, dcategory.Console, dcategory.Output},
		daction.Set,
		meta,

		NewEventOpts{
			Data: adata.NewDataFromMem(data),

			Response: &EventResponseOpts{
				Exist: utils.PointerTo(false),
			},
		},
	), nil
}

type ReqeustCommands struct {
	ConsoleUuid     UUID
	LastCommandUuid UUID
	CommandLimit    int
}

func (c ReqeustCommands) MarshalJSON() ([]byte, error) {
	jsonData := map[string]any{
		"uuid":      c.ConsoleUuid,
		"last_uuid": c.LastCommandUuid,
		"limit":     c.CommandLimit,
	}
	return json.Marshal(jsonData)
}

func (c *ReqeustCommands) UnmarshalJSON(data []byte) error {
	jsonData := map[string]any{}
	if err := json.Unmarshal(data, &jsonData); err != nil {
		return err
	}
	if val, ok := jsonData["uuid"].(string); ok {
		parsedUuid, err := uuid.Parse(val)
		if err != nil {
			return err
		}
		c.ConsoleUuid = parsedUuid
	}
	if val, ok := jsonData["last_uuid"].(string); ok {
		parsedUuid, err := uuid.Parse(val)
		if err != nil {
			return err
		}
		c.LastCommandUuid = parsedUuid
	}
	if val, ok := jsonData["limit"].(float64); ok {
		c.CommandLimit = int(val)
	}
	return nil
}

type AgentConsoleInputGetListResult struct {
	Uuid     UUID
	Interval int
	Commands AgentConsoleInputGetListResultCommands
}

func (r AgentConsoleInputGetListResult) MarshalJSON() ([]byte, error) {
	jsonData := map[string]any{
		"uuid":     r.Uuid,
		"interval": r.Interval,
		"commands": r.Commands,
	}
	return json.Marshal(jsonData)
}

func (r *AgentConsoleInputGetListResult) UnmarshalJSON(data []byte) error {
	jsonData := map[string]any{}
	if err := json.Unmarshal(data, &jsonData); err != nil {
		return err
	}
	if val, ok := jsonData["uuid"].(string); ok {
		parsedUuid, err := uuid.Parse(val)
		if err != nil {
			return err
		}
		r.Uuid = parsedUuid
	}
	if val, ok := jsonData["interval"].(float64); ok {
		r.Interval = int(val)
	}
	if val, ok := jsonData["commands"].(map[string]any); ok {
		commandBytes, err := json.Marshal(val)
		if err != nil {
			return err
		}
		var commands AgentConsoleInputGetListResultCommands
		if err = json.Unmarshal(commandBytes, &commands); err != nil {
			return err
		}
		r.Commands = commands
	}
	return nil
}

type AgentConsoleInputGetListResultCommands struct {
	LastUuid UUID
	Items    []include.CommandItem
}

func (c AgentConsoleInputGetListResultCommands) MarshalJSON() ([]byte, error) {
	jsonData := map[string]any{
		"last_uuid": c.LastUuid,
		"items":     c.Items,
	}
	return json.Marshal(jsonData)
}

func (c *AgentConsoleInputGetListResultCommands) UnmarshalJSON(data []byte) error {
	jsonData := map[string]any{}
	if err := json.Unmarshal(data, &jsonData); err != nil {
		return err
	}
	if val, ok := jsonData["last_uuid"].(string); ok {
		parsedUuid, err := uuid.Parse(val)
		if err != nil {
			return err
		}
		c.LastUuid = parsedUuid
	}
	if val, ok := jsonData["items"].([]any); ok {
		for _, item := range val {
			itemBytes, err := json.Marshal(item)
			if err != nil {
				return err
			}
			var commandItem include.CommandItem
			if err = json.Unmarshal(itemBytes, &commandItem); err != nil {
				return err
			}
			c.Items = append(c.Items, commandItem)
		}
	}
	return nil
}

func AgentConsoleInputGetList(ctx context.Context, client IClient,
	req ...ReqeustCommands,
) (res []AgentConsoleInputGetListResult, err error) {
	event, err := agentConsoleInputGetListEvent(req...)
	if err != nil {
		return nil, err
	}
	data, err := ExecuteAndGetData(ctx, client, event)
	if err != nil {
		return nil, err
	} else if data != nil {
		defer func() {
			if err != nil {
				debug.LogErr(string(data))
			}
		}()
		return res, json.Unmarshal(data, &res)
	}
	return nil, nil
}

func agentConsoleInputGetListEvent(req ...ReqeustCommands) (IEvent, error) {
	meta := map[string]any{}

	if len(req) > 0 {
		meta["commands"] = req
	}

	return NewEvent(
		dkind.AgentToService,
		[]Category{dcategory.Agent, dcategory.Console, dcategory.Input},
		daction.Get,
		meta,

		NewEventOpts{
			Response: &EventResponseOpts{
				Exist:    utils.PointerTo(true),
				CanEmpty: utils.PointerTo(true),
				DataOnly: utils.PointerTo(true),
			},
		},
	), nil
}

func AgentConsoleInputSetProcessed(ctx context.Context, client IClient,
	commands map[UUID][]UUID,
) error {
	jsonEvent := agentConsoleInputSetProcessedEvent(commands)
	_, err := client.Execute(ctx, jsonEvent)
	return err
}

func agentConsoleInputSetProcessedEvent(commands map[UUID][]UUID) IEvent {
	meta := map[string]any{
		"commands": commands,
	}

	return NewEvent(
		dkind.AgentToService,
		[]Category{dcategory.Agent, dcategory.Console, dcategory.Input, dcategory.Status, dcategory.Processed},
		daction.Set,
		meta,

		NewEventOpts{
			Response: &EventResponseOpts{
				Exist: utils.PointerTo(false),
			},
		},
	)
}
