package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"os"
	"os/signal"
	"path/filepath"
	"syscall"

	"git.debros.io/DeBros/network/pkg/anyoneproxy"
	"git.debros.io/DeBros/network/pkg/config"
	"git.debros.io/DeBros/network/pkg/logging"
	"git.debros.io/DeBros/network/pkg/node"
	"go.uber.org/zap"
)

func setup_logger(component logging.Component) (logger *logging.ColoredLogger) {
	var err error

	logger, err = logging.NewColoredLogger(component, true)
	if err != nil {
		log.Fatalf("Failed to create logger: %v", err)
	}

	return logger
}

func parse_and_return_network_flags() (dataDir, nodeID *string, p2pPort, rqlHTTP, rqlRaft *int, disableAnon *bool, rqlJoinAddr *string, help *bool) {
	logger := setup_logger(logging.ComponentNode)

	dataDir = flag.String("data", "", "Data directory (auto-detected if not provided)")
	nodeID = flag.String("id", "", "Node identifier (for running multiple local nodes)")
	p2pPort = flag.Int("p2p-port", 4001, "LibP2P listen port")
	rqlHTTP = flag.Int("rqlite-http-port", 5001, "RQLite HTTP API port")
	rqlRaft = flag.Int("rqlite-raft-port", 7001, "RQLite Raft port")
	disableAnon = flag.Bool("disable-anonrc", false, "Disable Anyone proxy routing (defaults to enabled on 127.0.0.1:9050)")
	rqlJoinAddr = flag.String("rqlite-join-address", "", "RQLite address to join (e.g., /ip4/)")
	help = flag.Bool("help", false, "Show help")
	flag.Parse()

	logger.Info("Successfully parsed all flags and arguments.")

	return
}

func disable_anon_proxy(disableAnon *bool) bool {
	anyoneproxy.SetDisabled(*disableAnon)
	logger := setup_logger(logging.ComponentAnyone)

	if *disableAnon {
		logger.Info("Anyone proxy routing is disabled. This means the node will not use the default Tor proxy for anonymous routing.\n")
	}

	return true
}

func check_if_should_open_help(help *bool) {
	if *help {
		flag.Usage()
		return
	}
}

func select_data_dir(dataDir *string, nodeID *string) {
	logger := setup_logger(logging.ComponentNode)

	if *nodeID == "" {
		*dataDir = "./data/node"
	}

	logger.Info("Successfully selected Data Directory of: %s", zap.String("dataDir", *dataDir))
}

func startNode(ctx context.Context, cfg *config.Config, port int) error {
	logger := setup_logger(logging.ComponentNode)

	// Create and start node using the unified node implementation
	n, err := node.NewNode(cfg)
	if err != nil {
		logger.Error("failed to create node: %v", zap.Error(err))
	}

	if err := n.Start(ctx); err != nil {
		logger.Error("failed to start node: %v", zap.Error(err))
	}

	// Save the peer ID to a file for CLI access (especially useful for bootstrap)
	peerID := n.GetPeerID()
	peerInfoFile := filepath.Join(cfg.Node.DataDir, "peer.info")
	peerMultiaddr := fmt.Sprintf("/ip4/0.0.0.0/tcp/%d/p2p/%s", port, peerID)

	if err := os.WriteFile(peerInfoFile, []byte(peerMultiaddr), 0644); err != nil {
		logger.Error("Failed to save peer info: %v", zap.Error(err))
	} else {
		logger.Info("Peer info saved to: %s", zap.String("path", peerInfoFile))
		logger.Info("Bootstrap multiaddr: %s", zap.String("path", peerMultiaddr))
	}

	logger.Info("Node started successfully")

	// Wait for context cancellation
	<-ctx.Done()

	// Stop node
	return n.Stop()
}

// load_args_into_config applies command line argument overrides to the config
func load_args_into_config(cfg *config.Config, p2pPort, rqlHTTP, rqlRaft *int, rqlJoinAddr *string) {
	logger := setup_logger(logging.ComponentNode)

	// Apply RQLite HTTP port override
	if *rqlHTTP != 5001 {
		cfg.Database.RQLitePort = *rqlHTTP
		logger.ComponentInfo(logging.ComponentNode, "Overriding RQLite HTTP port", zap.Int("port", *rqlHTTP))
	}

	// Apply RQLite Raft port override
	if *rqlRaft != 7001 {
		cfg.Database.RQLiteRaftPort = *rqlRaft
		logger.ComponentInfo(logging.ComponentNode, "Overriding RQLite Raft port", zap.Int("port", *rqlRaft))
	}

	// Apply P2P port override
	if *p2pPort != 4001 {
		cfg.Node.ListenAddresses = []string{
			fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", *p2pPort),
		}
		logger.ComponentInfo(logging.ComponentNode, "Overriding P2P port", zap.Int("port", *p2pPort))
	}

	// Apply RQLite join address
	if *rqlJoinAddr != "" {
		cfg.Database.RQLiteJoinAddress = *rqlJoinAddr
		logger.ComponentInfo(logging.ComponentNode, "Setting RQLite join address", zap.String("address", *rqlJoinAddr))
	}
}

func main() {
	logger := setup_logger(logging.ComponentNode)

	dataDir, nodeID, p2pPort, rqlHTTP, rqlRaft, disableAnon, rqlJoinAddr, help := parse_and_return_network_flags()

	disable_anon_proxy(disableAnon)
	check_if_should_open_help(help)
	select_data_dir(dataDir, nodeID)

	// Load Node Configuration
	var cfg *config.Config
	cfg = config.DefaultConfig()
	logger.ComponentInfo(logging.ComponentNode, "Default configuration loaded successfully")

	// Apply command line argument overrides
	load_args_into_config(cfg, p2pPort, rqlHTTP, rqlRaft, rqlJoinAddr)
	logger.ComponentInfo(logging.ComponentNode, "Command line arguments applied to configuration")

	// LibP2P uses configurable port (default 4001); RQLite uses 5001 (HTTP) and 7001 (Raft)
	port := *p2pPort

	logger.ComponentInfo(logging.ComponentNode, "Node configuration summary",
		zap.Strings("listen_addresses", cfg.Node.ListenAddresses),
		zap.Int("rqlite_http_port", cfg.Database.RQLitePort),
		zap.Int("rqlite_raft_port", cfg.Database.RQLiteRaftPort),
		zap.Int("p2p_port", port),
		zap.Strings("bootstrap_peers", cfg.Discovery.BootstrapPeers),
		zap.String("rqlite_join_address", cfg.Database.RQLiteJoinAddress),
		zap.String("data_directory", *dataDir))

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

	// Start node in a goroutine
	errChan := make(chan error, 1)
	doneChan := make(chan struct{})
	go func() {
		if err := startNode(ctx, cfg, port); err != nil {
			errChan <- err
		}
		close(doneChan)
	}()

	// Wait for interrupt signal or error
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)

	select {
	case err := <-errChan:
		logger.ComponentError(logging.ComponentNode, "Failed to start node", zap.Error(err))
		os.Exit(1)
	case <-c:
		logger.ComponentInfo(logging.ComponentNode, "Shutting down node...")
		cancel()
		// Wait for node goroutine to finish cleanly
		<-doneChan
		logger.ComponentInfo(logging.ComponentNode, "Node shutdown complete")
	}
}
