package edgecmd

import (
	"fmt"
	"io"
	"net"
	"os"
	"sort"
	"strings"

	"github.com/spf13/cobra"

	"iop/packages/go/config"
)

// envCmd prints the resolved environment for the bundled edge binary so the
// user can verify config discovery, advertise host, log path, transport
// address, artifact/bootstrap URL, and OpenAI-compatible URL without starting
// edge. It does not bind any port or connect to nodes.
func envCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "env",
		Short: "Print resolved edge environment without starting edge",
		RunE: func(cmd *cobra.Command, _ []string) error {
			cfg, path, err := LoadEdgeConfig(cmd)
			if err != nil {
				return err
			}
			logPath, err := ResolveEdgeLogPath(cfg.Logging.Path)
			if err != nil {
				return err
			}
			cfg.Bootstrap.ArtifactDir = ResolveBootstrapArtifactDir(cfg.Bootstrap.ArtifactDir)
			renderEnv(cmd.OutOrStdout(), envReport{
				ConfigPath: path,
				BinaryDir:  binaryDir(),
				LogPath:    logPath,
				Advertise:  resolveAdvertiseHost(cfg.Server.AdvertiseHost),
				Server:     cfg.Server,
				Bootstrap:  cfg.Bootstrap,
				OpenAI:     cfg.OpenAI,
				A2A:        cfg.A2A,
				Nodes:      cfg.Nodes,
			})
			return nil
		},
	}
}

type advertise struct {
	Host   string
	Source string
}

func resolveAdvertiseHost(explicit string) advertise {
	if explicit != "" {
		return advertise{Host: explicit, Source: "explicit"}
	}
	if ip := nonLoopbackIPv4(); ip != "" {
		return advertise{Host: ip, Source: "auto"}
	}
	if hn, err := os.Hostname(); err == nil && hn != "" {
		return advertise{Host: hn, Source: "auto"}
	}
	return advertise{Host: "127.0.0.1", Source: "auto"}
}

func nonLoopbackIPv4() string {
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		return ""
	}
	for _, a := range addrs {
		ipnet, ok := a.(*net.IPNet)
		if !ok || ipnet.IP.IsLoopback() {
			continue
		}
		ip4 := ipnet.IP.To4()
		if ip4 == nil {
			continue
		}
		return ip4.String()
	}
	return ""
}

// addressFor combines an advertise host with the port from a listen string
// (e.g. "0.0.0.0:9090" -> "<host>:9090"). If the listen string is invalid it
// is returned unchanged.
func addressFor(host, listen string) string {
	_, port, err := net.SplitHostPort(listen)
	if err != nil || port == "" {
		return listen
	}
	return net.JoinHostPort(host, port)
}

func defaultArtifactBaseURL(host string, listen ...string) string {
	bootstrapListen := "0.0.0.0:18080"
	if len(listen) > 0 && listen[0] != "" {
		bootstrapListen = listen[0]
	}
	return "http://" + addressFor(host, bootstrapListen)
}

func bootstrapScriptURL(baseURL, target string) string {
	return strings.TrimSuffix(baseURL, "/") + "/bootstrap/node-" + target + ".sh"
}

func bootstrapPowerShellScriptURL(baseURL, target string) string {
	return strings.TrimSuffix(baseURL, "/") + "/bootstrap/node-" + target + ".ps1"
}

func universalBootstrapScriptURL(baseURL string) string {
	return strings.TrimSuffix(baseURL, "/") + "/bootstrap/node.sh"
}

type envReport struct {
	ConfigPath string
	BinaryDir  string
	LogPath    string
	Advertise  advertise
	Server     config.EdgeServerConf
	Bootstrap  config.EdgeBootstrapConf
	OpenAI     config.EdgeOpenAIConf
	A2A        config.EdgeA2AConf
	Nodes      []config.NodeDefinition
}

func renderEnv(w io.Writer, r envReport) {
	artifactURL := strings.TrimSuffix(r.Bootstrap.ArtifactBaseURL, "/")
	artifactSource := "configured"
	if artifactURL == "" {
		artifactURL = defaultArtifactBaseURL(r.Advertise.Host, r.Bootstrap.Listen)
		artifactSource = "derived"
	}

	fmt.Fprintf(w, "config: %s\n", r.ConfigPath)
	fmt.Fprintf(w, "binary_dir: %s\n", r.BinaryDir)
	fmt.Fprintf(w, "log_path: %s\n", r.LogPath)
	fmt.Fprintf(w, "advertise_host: %s (%s)\n", r.Advertise.Host, r.Advertise.Source)
	fmt.Fprintf(w, "node_transport: %s\n", addressFor(r.Advertise.Host, r.Server.Listen))
	fmt.Fprintf(w, "artifact_dir: %s\n", r.Bootstrap.ArtifactDir)
	fmt.Fprintf(w, "artifact_listen: %s\n", r.Bootstrap.Listen)
	fmt.Fprintf(w, "artifact_base_url: %s (%s)\n", artifactURL, artifactSource)
	fmt.Fprintf(w, "node_bootstrap: %s\n", universalBootstrapScriptURL(artifactURL))
	if r.OpenAI.Enabled {
		fmt.Fprintf(w, "openai_url: http://%s/v1\n", addressFor(r.Advertise.Host, r.OpenAI.Listen))
	} else {
		fmt.Fprintln(w, "openai_url: (disabled)")
	}
	if r.A2A.Enabled {
		path := r.A2A.Path
		if path == "" {
			path = "/"
		}
		fmt.Fprintf(w, "a2a_url: http://%s%s\n", addressFor(r.Advertise.Host, r.A2A.Listen), path)
	} else {
		fmt.Fprintln(w, "a2a_url: (disabled)")
	}
	fmt.Fprintf(w, "configured_nodes: %s\n", summarizeNodes(r.Nodes))
	fmt.Fprintln(w, "connected_nodes: offline (env does not start edge)")
}

func summarizeNodes(nodes []config.NodeDefinition) string {
	if len(nodes) == 0 {
		return "0 (none)"
	}
	labels := make([]string, 0, len(nodes))
	for _, n := range nodes {
		label := n.ID
		if label == "" {
			label = n.Alias
		}
		if label == "" {
			label = "(unnamed)"
		}
		labels = append(labels, label)
	}
	sort.Strings(labels)
	return fmt.Sprintf("%d (%s)", len(nodes), strings.Join(labels, ", "))
}
