// pkg/mycelium_client/cmd/main.go
package main

import (
	"context"
	"flag"
	"fmt"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"git.threefold.info/herocode/heroagent/pkg/mycelium_client"
)

type config struct {
	baseURL      string
	command      string
	peerEndpoint string
	message      string
	destination  string
	topic        string
	timeout      int
	wait         bool
	replyTimeout int
	messageID    string
	outputJSON   bool
}

// Commands
const (
	cmdInfo    = "info"
	cmdPeers   = "peers"
	cmdAddPeer = "add-peer"
	cmdDelPeer = "del-peer"
	cmdSend    = "send"
	cmdReceive = "receive"
	cmdReply   = "reply"
	cmdStatus  = "status"
	cmdRoutes  = "routes"
)

func main() {
	// Create config with default values
	cfg := config{
		baseURL:      fmt.Sprintf("http://localhost:%d", mycelium_client.DefaultAPIPort),
		timeout:      30,
		replyTimeout: mycelium_client.DefaultReplyTimeout,
	}

	// Parse command line flags
	flag.StringVar(&cfg.baseURL, "api", cfg.baseURL, "Mycelium API URL")
	flag.IntVar(&cfg.timeout, "timeout", cfg.timeout, "Client timeout in seconds")
	flag.BoolVar(&cfg.outputJSON, "json", false, "Output in JSON format")
	flag.Parse()

	// Get the command
	args := flag.Args()
	if len(args) == 0 {
		printUsage()
		os.Exit(1)
	}

	cfg.command = args[0]
	args = args[1:]

	// Create client
	client := mycelium_client.NewClient(cfg.baseURL)
	client.SetTimeout(time.Duration(cfg.timeout) * time.Second)

	// Create context with cancellation for graceful shutdowns
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Set up signal handling
	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
	go func() {
		<-sigCh
		fmt.Println("\nReceived interrupt signal, shutting down...")
		cancel()
	}()

	// Execute command
	var err error
	switch cfg.command {
	case cmdInfo:
		err = showNodeInfo(ctx, client)

	case cmdPeers:
		err = listPeers(ctx, client, cfg.outputJSON)

	case cmdAddPeer:
		if len(args) < 1 {
			fmt.Println("Missing peer endpoint argument")
			printUsage()
			os.Exit(1)
		}
		cfg.peerEndpoint = args[0]
		err = addPeer(ctx, client, cfg.peerEndpoint)

	case cmdDelPeer:
		if len(args) < 1 {
			fmt.Println("Missing peer endpoint argument")
			printUsage()
			os.Exit(1)
		}
		cfg.peerEndpoint = args[0]
		err = removePeer(ctx, client, cfg.peerEndpoint)

	case cmdSend:
		parseMessageArgs(&cfg, args)
		err = sendMessage(ctx, client, cfg)

	case cmdReceive:
		parseReceiveArgs(&cfg, args)
		err = receiveMessage(ctx, client, cfg)

	case cmdReply:
		parseReplyArgs(&cfg, args)
		err = replyToMessage(ctx, client, cfg)

	case cmdStatus:
		if len(args) < 1 {
			fmt.Println("Missing message ID argument")
			printUsage()
			os.Exit(1)
		}
		cfg.messageID = args[0]
		err = getMessageStatus(ctx, client, cfg.messageID)

	case cmdRoutes:
		var routeType string
		if len(args) > 0 {
			routeType = args[0]
		}
		err = listRoutes(ctx, client, routeType, cfg.outputJSON)

	default:
		fmt.Printf("Unknown command: %s\n", cfg.command)
		printUsage()
		os.Exit(1)
	}

	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		os.Exit(1)
	}
}

func printUsage() {
	fmt.Println("Usage: mycelium-client [flags] COMMAND [args...]")
	fmt.Println("\nFlags:")
	flag.PrintDefaults()
	fmt.Println("\nCommands:")
	fmt.Println("  info                        Get node information")
	fmt.Println("  peers                       List connected peers")
	fmt.Println("  add-peer ENDPOINT           Add a new peer")
	fmt.Println("  del-peer ENDPOINT           Remove a peer")
	fmt.Println("  send [--pk=PK|--ip=IP] [--topic=TOPIC] [--wait] [--reply-timeout=N] MESSAGE")
	fmt.Println("                              Send a message to a destination")
	fmt.Println("  receive [--topic=TOPIC] [--timeout=N]")
	fmt.Println("                              Receive a message")
	fmt.Println("  reply ID [--topic=TOPIC] MESSAGE")
	fmt.Println("                              Reply to a message")
	fmt.Println("  status ID                   Get status of a sent message")
	fmt.Println("  routes [selected|fallback]  List routes (default: selected)")
}

func parseMessageArgs(cfg *config, args []string) {
	// Create a temporary flag set
	fs := flag.NewFlagSet("send", flag.ExitOnError)
	fs.StringVar(&cfg.destination, "pk", "", "Destination public key (hex encoded)")
	fs.StringVar(&cfg.destination, "ip", "", "Destination IP address")
	fs.StringVar(&cfg.topic, "topic", "", "Message topic")
	fs.BoolVar(&cfg.wait, "wait", false, "Wait for reply")
	fs.IntVar(&cfg.replyTimeout, "reply-timeout", cfg.replyTimeout, "Reply timeout in seconds")

	// Parse args
	fs.Parse(args)

	// Remaining args are the message
	remainingArgs := fs.Args()
	if len(remainingArgs) == 0 {
		fmt.Println("Missing message content")
		printUsage()
		os.Exit(1)
	}
	cfg.message = strings.Join(remainingArgs, " ")
}

func parseReceiveArgs(cfg *config, args []string) {
	// Create a temporary flag set
	fs := flag.NewFlagSet("receive", flag.ExitOnError)
	fs.StringVar(&cfg.topic, "topic", "", "Message topic filter")
	fs.IntVar(&cfg.timeout, "timeout", 10, "Receive timeout in seconds")

	// Parse args
	fs.Parse(args)
}

func parseReplyArgs(cfg *config, args []string) {
	if len(args) < 1 {
		fmt.Println("Missing message ID argument")
		printUsage()
		os.Exit(1)
	}

	cfg.messageID = args[0]
	args = args[1:]

	// Create a temporary flag set
	fs := flag.NewFlagSet("reply", flag.ExitOnError)
	fs.StringVar(&cfg.topic, "topic", "", "Message topic")

	// Parse args
	fs.Parse(args)

	// Remaining args are the message
	remainingArgs := fs.Args()
	if len(remainingArgs) == 0 {
		fmt.Println("Missing reply message content")
		printUsage()
		os.Exit(1)
	}
	cfg.message = strings.Join(remainingArgs, " ")
}

func showNodeInfo(ctx context.Context, client *mycelium_client.MyceliumClient) error {
	info, err := client.GetNodeInfo(ctx)
	if err != nil {
		return err
	}

	fmt.Println("Node Information:")
	fmt.Printf("  Subnet: %s\n", info.NodeSubnet)
	return nil
}

func listPeers(ctx context.Context, client *mycelium_client.MyceliumClient, jsonOutput bool) error {
	peers, err := client.ListPeers(ctx)
	if err != nil {
		return err
	}

	if jsonOutput {
		// TODO: Output JSON
		fmt.Printf("Found %d peers\n", len(peers))
	} else {
		fmt.Printf("Connected Peers (%d):\n", len(peers))
		if len(peers) == 0 {
			fmt.Println("  No peers connected")
			return nil
		}

		for i, peer := range peers {
			fmt.Printf("  %d. %s://%s\n", i+1, peer.Endpoint.Proto, peer.Endpoint.SocketAddr)
			fmt.Printf("     Type: %s, State: %s\n", peer.Type, peer.ConnectionState)
			if peer.TxBytes > 0 || peer.RxBytes > 0 {
				fmt.Printf("     TX: %d bytes, RX: %d bytes\n", peer.TxBytes, peer.RxBytes)
			}
		}
	}
	return nil
}

func addPeer(ctx context.Context, client *mycelium_client.MyceliumClient, endpoint string) error {
	if err := client.AddPeer(ctx, endpoint); err != nil {
		return err
	}
	fmt.Printf("Peer added: %s\n", endpoint)
	return nil
}

func removePeer(ctx context.Context, client *mycelium_client.MyceliumClient, endpoint string) error {
	if err := client.RemovePeer(ctx, endpoint); err != nil {
		return err
	}
	fmt.Printf("Peer removed: %s\n", endpoint)
	return nil
}

func sendMessage(ctx context.Context, client *mycelium_client.MyceliumClient, cfg config) error {
	var dst mycelium_client.MessageDestination

	if cfg.destination == "" {
		return fmt.Errorf("destination is required (--pk or --ip)")
	}

	// Determine destination type
	if strings.HasPrefix(cfg.destination, "--pk=") {
		dst.PK = strings.TrimPrefix(cfg.destination, "--pk=")
	} else if strings.HasPrefix(cfg.destination, "--ip=") {
		dst.IP = strings.TrimPrefix(cfg.destination, "--ip=")
	} else {
		// Try to guess format
		if strings.Contains(cfg.destination, ":") {
			dst.IP = cfg.destination
		} else {
			dst.PK = cfg.destination
		}
	}

	// Send message
	payload := []byte(cfg.message)
	reply, id, err := client.SendMessage(ctx, dst, payload, cfg.topic, cfg.wait, cfg.replyTimeout)
	if err != nil {
		return err
	}

	if reply != nil {
		fmt.Println("Received reply:")
		printMessage(reply)
	} else {
		fmt.Printf("Message sent successfully. ID: %s\n", id)
	}

	return nil
}

func receiveMessage(ctx context.Context, client *mycelium_client.MyceliumClient, cfg config) error {
	fmt.Printf("Waiting for message (timeout: %d seconds)...\n", cfg.timeout)
	msg, err := client.ReceiveMessage(ctx, cfg.timeout, cfg.topic, false)
	if err != nil {
		return err
	}

	if msg == nil {
		fmt.Println("No message received within timeout")
		return nil
	}

	fmt.Println("Message received:")
	printMessage(msg)
	return nil
}

func replyToMessage(ctx context.Context, client *mycelium_client.MyceliumClient, cfg config) error {
	if err := client.ReplyToMessage(ctx, cfg.messageID, []byte(cfg.message), cfg.topic); err != nil {
		return err
	}

	fmt.Printf("Reply sent to message ID: %s\n", cfg.messageID)
	return nil
}

func getMessageStatus(ctx context.Context, client *mycelium_client.MyceliumClient, messageID string) error {
	status, err := client.GetMessageStatus(ctx, messageID)
	if err != nil {
		return err
	}

	fmt.Printf("Message Status (ID: %s):\n", messageID)
	for k, v := range status {
		fmt.Printf("  %s: %v\n", k, v)
	}
	return nil
}

func listRoutes(ctx context.Context, client *mycelium_client.MyceliumClient, routeType string, jsonOutput bool) error {
	var routes []mycelium_client.Route
	var err error

	// Default to selected routes
	if routeType == "" || routeType == "selected" {
		routes, err = client.ListSelectedRoutes(ctx)
		if err != nil {
			return err
		}
		fmt.Printf("Selected Routes (%d):\n", len(routes))
	} else if routeType == "fallback" {
		routes, err = client.ListFallbackRoutes(ctx)
		if err != nil {
			return err
		}
		fmt.Printf("Fallback Routes (%d):\n", len(routes))
	} else {
		return fmt.Errorf("unknown route type: %s (use 'selected' or 'fallback')", routeType)
	}

	if jsonOutput {
		// TODO: Output JSON
		fmt.Printf("Found %d routes\n", len(routes))
	} else {
		if len(routes) == 0 {
			fmt.Println("  No routes found")
			return nil
		}

		for i, route := range routes {
			fmt.Printf("  %d. Subnet: %s\n", i+1, route.Subnet)
			fmt.Printf("     Next Hop: %s\n", route.NextHop)
			fmt.Printf("     Metric: %v, Sequence: %d\n", route.Metric, route.Seqno)
		}
	}
	return nil
}

func printMessage(msg *mycelium_client.InboundMessage) {
	payload, err := msg.Decode()
	fmt.Printf("  ID: %s\n", msg.ID)
	fmt.Printf("  From: %s (IP: %s)\n", msg.SrcPK, msg.SrcIP)
	fmt.Printf("  To: %s (IP: %s)\n", msg.DstPK, msg.DstIP)
	if msg.Topic != "" {
		fmt.Printf("  Topic: %s\n", msg.Topic)
	}
	if err != nil {
		fmt.Printf("  Payload (base64): %s\n", msg.Payload)
		fmt.Printf("  Error decoding payload: %v\n", err)
	} else {
		fmt.Printf("  Payload: %s\n", string(payload))
	}
}
