package main

import (
	"bytes"
	"path/filepath"
	"strings"
	"testing"

	"iop/apps/node/internal/workspace"
	"iop/packages/go/version"
)

func TestNodeMainCommandShimRoutingIsClosed(t *testing.T) {
	t.Setenv("IOP_WORKSPACE_COMMAND_SHIM", "")
	if handled, _ := workspace.RunCommandShim([]string{"node", "__iop_workspace_command_shim"}); handled {
		t.Fatal("shim argument without control marker was handled")
	}
	t.Setenv("IOP_WORKSPACE_COMMAND_SHIM", "malformed")
	if handled, _ := workspace.RunCommandShim([]string{"node", "__iop_workspace_command_shim"}); handled {
		t.Fatal("malformed shim marker was handled")
	}
	t.Setenv("IOP_WORKSPACE_COMMAND_SHIM", "1")
	if handled, _ := workspace.RunCommandShim([]string{"node", "version"}); handled {
		t.Fatal("normal command was handled as a shim")
	}
}

func TestRootCmdIncludesOperationalCommands(t *testing.T) {
	root := rootCmd()
	want := map[string]bool{
		"serve":   false,
		"version": false,
		"config":  false,
		"setup":   false,
	}
	for _, c := range root.Commands() {
		if _, ok := want[c.Name()]; ok {
			want[c.Name()] = true
		}
	}
	for name, ok := range want {
		if !ok {
			t.Errorf("root command missing %q", name)
		}
	}

	cfg, _, err := root.Find([]string{"config"})
	if err != nil {
		t.Fatalf("find config: %v", err)
	}
	subs := map[string]bool{"print": false, "check": false}
	for _, c := range cfg.Commands() {
		if _, ok := subs[c.Name()]; ok {
			subs[c.Name()] = true
		}
	}
	for name, ok := range subs {
		if !ok {
			t.Errorf("config subcommand missing %q", name)
		}
	}
}

func TestRootCmdUsesProviderOnlyDescription(t *testing.T) {
	short := rootCmd().Short
	lower := strings.ToLower(short)
	if !strings.Contains(lower, "provider") {
		t.Errorf("root command Short must describe provider execution; got %q", short)
	}
	if strings.Contains(lower, "agent") {
		t.Errorf("root command Short must not claim Agent ownership; got %q", short)
	}
}

func TestVersionCmdPrintsVersion(t *testing.T) {
	root := rootCmd()
	var out bytes.Buffer
	root.SetOut(&out)
	root.SetErr(&out)
	root.SetArgs([]string{"version"})
	if err := root.Execute(); err != nil {
		t.Fatalf("execute: %v", err)
	}
	got := strings.TrimSpace(out.String())
	if got != version.Version {
		t.Fatalf("version output = %q, want %q", got, version.Version)
	}
}

func TestConfigCheckCmdLoadsNodeConfig(t *testing.T) {
	root := rootCmd()
	var out bytes.Buffer
	root.SetOut(&out)
	root.SetErr(&out)
	root.SetArgs([]string{"config", "check", "--config", "../../../../configs/node.yaml"})
	if err := root.Execute(); err != nil {
		t.Fatalf("execute: %v\n%s", err, out.String())
	}
	if !strings.Contains(out.String(), "OK") {
		t.Fatalf("expected OK in output, got %q", out.String())
	}
}

func TestConfigPrintCmdPrintsResolvedConfig(t *testing.T) {
	root := rootCmd()
	var out bytes.Buffer
	root.SetOut(&out)
	root.SetErr(&out)
	root.SetArgs([]string{"config", "print", "--config", "../../../../configs/node.yaml"})
	if err := root.Execute(); err != nil {
		t.Fatalf("execute: %v\n%s", err, out.String())
	}
	if out.Len() == 0 {
		t.Fatal("expected non-empty YAML output")
	}
	for _, want := range []string{"reconnect:", "interval_sec: 10", "max_attempts: 10"} {
		if !strings.Contains(out.String(), want) {
			t.Errorf("config print output missing %q\n---\n%s", want, out.String())
		}
	}
}

func TestSetupDryRunUsesNodeDefaults(t *testing.T) {
	cfgFile = "configs/node.yaml" // reset to root default
	root := rootCmd()
	var out bytes.Buffer
	root.SetOut(&out)
	root.SetErr(&out)
	root.SetArgs([]string{"setup", "--dry-run", "--binary", "/usr/local/bin/iop-node"})
	if err := root.Execute(); err != nil {
		t.Fatalf("execute: %v\n%s", err, out.String())
	}
	s := out.String()
	for _, want := range []string{
		"/etc/iop/node.yaml",
		"/var/lib/iop/node",
		"/etc/systemd/system/iop-node.service",
		"/usr/local/bin/iop-node serve --config /etc/iop/node.yaml",
		"--- unit preview ---",
		"--- config preview ---",
	} {
		if !strings.Contains(s, want) {
			t.Errorf("dry-run output missing %q\n---\n%s", want, s)
		}
	}
	if strings.Contains(s, "configs/node.yaml") {
		t.Errorf("setup default leaked root dev config path: %s", s)
	}
}

func TestSetupDryRunAcceptsExplicitConfig(t *testing.T) {
	cfgFile = "configs/node.yaml" // reset
	root := rootCmd()
	var out bytes.Buffer
	root.SetOut(&out)
	root.SetErr(&out)
	custom := filepath.Join(t.TempDir(), "node.yaml")
	root.SetArgs([]string{"--config", custom, "setup", "--dry-run", "--binary", "/usr/local/bin/iop-node"})
	if err := root.Execute(); err != nil {
		t.Fatalf("execute: %v\n%s", err, out.String())
	}
	if !strings.Contains(out.String(), custom) {
		t.Errorf("explicit config not used; output:\n%s", out.String())
	}
}

func TestSetupDryRunConfigSubcmdPosition(t *testing.T) {
	cfgFile = "configs/node.yaml" // reset
	root := rootCmd()
	var out bytes.Buffer
	root.SetOut(&out)
	root.SetErr(&out)
	custom := filepath.Join(t.TempDir(), "node.yaml")
	root.SetArgs([]string{"setup", "--config", custom, "--dry-run", "--binary", "/usr/local/bin/iop-node"})
	if err := root.Execute(); err != nil {
		t.Fatalf("execute: %v\n%s", err, out.String())
	}
	if !strings.Contains(out.String(), custom) {
		t.Errorf("subcommand-position --config not used; output:\n%s", out.String())
	}
}

func TestSetupHelpShowsNodeDefault(t *testing.T) {
	cfgFile = "configs/node.yaml" // reset
	root := rootCmd()
	var out bytes.Buffer
	root.SetOut(&out)
	root.SetErr(&out)
	root.SetArgs([]string{"setup", "--help"})
	_ = root.Execute()
	s := out.String()
	if strings.Contains(s, `"configs/node.yaml"`) {
		t.Errorf("setup --help must not show dev default configs/node.yaml; output:\n%s", s)
	}
	if !strings.Contains(s, "/etc/iop/node.yaml") {
		t.Errorf("setup --help must show /etc/iop/node.yaml; output:\n%s", s)
	}
}
