package cli

import (
	"bytes"
	"context"
	"log/slog"
	"strings"
	"testing"
)

func TestDaemonPprofFlag(t *testing.T) {
	f := daemonCmd.Flags().Lookup("pprof")
	if f == nil {
		t.Fatal("--pprof flag not registered on daemonCmd")
	}
	if f.DefValue != "" {
		t.Errorf("--pprof default = %q, want empty", f.DefValue)
	}
}

// captureSlog swaps slog.Default() for a text handler writing to buf,
// returning a buffer and a restore func. Tests use this to observe
// warnDeprecated output (which uses the package-level slog.Default).
func captureSlog(t *testing.T) (*bytes.Buffer, func()) {
	t.Helper()
	var buf bytes.Buffer
	prev := slog.Default()
	logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelWarn}))
	slog.SetDefault(logger)
	return &buf, func() { slog.SetDefault(prev) }
}

// runDaemonHermetic invokes daemonCmd.RunE with a context that is
// already cancelled and an unbindable --addr, so the long-running
// server start short-circuits and RunE returns quickly without
// touching the network. It returns whatever RunE returned and the
// captured slog buffer.
func runDaemonHermetic(t *testing.T, suppressWarnings bool) (string, error) {
	t.Helper()
	_, cleanup := initTestEnv(t)
	defer cleanup()
	resetRootFlags(t)

	buf, restore := captureSlog(t)
	defer restore()

	if suppressWarnings {
		_ = rootCmd.PersistentFlags().Set("no-deprecation-warnings", "true")
	}

	daemonAddr = "127.0.0.1:99999" // unbindable: port outside uint16 range → ListenAndServe fails fast

	ctx, cancel := context.WithCancel(context.Background())
	cancel() // already-done context: the select returns via <-ctx.Done() immediately

	cmd := daemonCmd
	cmd.SetOut(&bytes.Buffer{})
	cmd.SetErr(&bytes.Buffer{})
	cmd.SetArgs(nil)
	cmd.SetContext(ctx)

	err := cmd.RunE(cmd, nil)
	return buf.String(), err
}

// TestDaemonEmitsDeprecationWarning verifies REQ-068: `orca daemon`
// emits a slog.Warn deprecation banner on every run.
func TestDaemonEmitsDeprecationWarning(t *testing.T) {
	out, _ := runDaemonHermetic(t, false)
	if !strings.Contains(out, "orca daemon is deprecated in v0.9") {
		t.Errorf("expected deprecation warning in slog output, got:\n%s", out)
	}
	if !strings.Contains(out, "R-001") {
		t.Errorf("deprecation warning should reference R-001, got:\n%s", out)
	}
}

// TestDaemonDeprecationWarningSuppressed verifies that
// --no-deprecation-warnings suppresses the deprecation banner (for
// `orca upgrade` migrations).
func TestDaemonDeprecationWarningSuppressed(t *testing.T) {
	out, _ := runDaemonHermetic(t, true)
	if strings.Contains(out, "deprecated in v0.9") {
		t.Errorf("--no-deprecation-warnings should suppress the deprecation warning, got:\n%s", out)
	}
}

// TestDaemonStillRuns verifies deprecation ≠ removal: the daemon
// command's RunE is still wired and callable. We don't assert on the
// error value (the hermetic short-circuit may return nil or a
// shutdown-related error), only that the command did not fail *because*
// of the deprecation notice.
func TestDaemonStillRuns(t *testing.T) {
	_, err := runDaemonHermetic(t, false)
	if err != nil && strings.Contains(err.Error(), "deprecated") {
		t.Errorf("daemon must not error due to deprecation, got: %v", err)
	}
}

// TestWarnDeprecatedGate verifies the package-level helper that gates
// deprecation warnings on the --no-deprecation-warnings flag.
func TestWarnDeprecatedGate(t *testing.T) {
	t.Run("emits by default", func(t *testing.T) {
		buf, restore := captureSlog(t)
		defer restore()
		noDeprecationWarnings = false
		warnDeprecated("test-deprecation-marker")
		if !strings.Contains(buf.String(), "test-deprecation-marker") {
			t.Errorf("expected warning emitted, got: %s", buf.String())
		}
	})
	t.Run("suppressed when flag set", func(t *testing.T) {
		buf, restore := captureSlog(t)
		defer restore()
		noDeprecationWarnings = true
		defer func() { noDeprecationWarnings = false }()
		warnDeprecated("should-not-appear")
		if strings.Contains(buf.String(), "should-not-appear") {
			t.Errorf("expected no warning when --no-deprecation-warnings set, got: %s", buf.String())
		}
	})
}

// TestNoDeprecationWarningsFlagRegistered verifies the
// --no-deprecation-warnings persistent flag exists on rootCmd.
func TestNoDeprecationWarningsFlagRegistered(t *testing.T) {
	f := rootCmd.PersistentFlags().Lookup("no-deprecation-warnings")
	if f == nil {
		t.Fatal("--no-deprecation-warnings persistent flag not registered on rootCmd")
	}
	if f.DefValue != "false" {
		t.Errorf("--no-deprecation-warnings default = %q, want false", f.DefValue)
	}
}

// TestCertEmitsDeprecationWarning verifies REQ-068: deprecated
// `orca cert ca-init` subcommand emits a deprecation banner.
func TestCertEmitsDeprecationWarning(t *testing.T) {
	_, cleanup := initTestEnv(t)
	defer cleanup()
	resetRootFlags(t)

	buf, restore := captureSlog(t)
	defer restore()

	var out bytes.Buffer
	rootCmd.SetOut(&out)
	rootCmd.SetErr(&out)
	rootCmd.SetArgs([]string{"cert", "ca-init", "--cn", "test-ca"})
	_ = rootCmd.Execute()

	logged := buf.String()
	if !strings.Contains(logged, "orca cert ca-init is deprecated") {
		t.Errorf("expected cert ca-init deprecation warning, got:\n%s", logged)
	}
	if !strings.Contains(logged, "step-ca") {
		t.Errorf("deprecation warning should mention step-ca, got:\n%s", logged)
	}
}

// TestCertDeprecationWarningSuppressed verifies --no-deprecation-warnings
// suppresses the cert deprecation banner.
func TestCertDeprecationWarningSuppressed(t *testing.T) {
	_, cleanup := initTestEnv(t)
	defer cleanup()
	resetRootFlags(t)
	_ = rootCmd.PersistentFlags().Set("no-deprecation-warnings", "true")

	buf, restore := captureSlog(t)
	defer restore()

	var out bytes.Buffer
	rootCmd.SetOut(&out)
	rootCmd.SetErr(&out)
	rootCmd.SetArgs([]string{"cert", "ca-init", "--cn", "test-ca"})
	_ = rootCmd.Execute()

	if strings.Contains(buf.String(), "orca cert ca-init is deprecated") {
		t.Errorf("--no-deprecation-warnings should suppress cert warning, got:\n%s", buf.String())
	}
}

// TestNodeJoinMTLSEmitsDeprecationWarning verifies REQ-068: the mTLS
// join path (`orca node join` without --type proxmox) warns that the
// mTLS join path is deprecated.
func TestNodeJoinMTLSEmitsDeprecationWarning(t *testing.T) {
	_, cleanup := initTestEnv(t)
	defer cleanup()
	resetRootFlags(t)

	buf, restore := captureSlog(t)
	defer restore()

	var out bytes.Buffer
	rootCmd.SetOut(&out)
	rootCmd.SetErr(&out)
	rootCmd.SetArgs([]string{"node", "join", "--name", "dep-warning", "--addr", "10.0.0.55:8443"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("node join: %v", err)
	}

	logged := buf.String()
	if !strings.Contains(logged, "mTLS join path is deprecated") {
		t.Errorf("expected mTLS join deprecation warning, got:\n%s", logged)
	}
	if !strings.Contains(logged, "R-001") {
		t.Errorf("deprecation warning should reference R-001, got:\n%s", logged)
	}
}

// TestNodeJoinProxmoxNoMTLSDeprecationWarning verifies the deprecation
// warning does NOT fire for the proxmox SSH path (that path is the
// v0.9 replacement, not the deprecated mTLS path).
func TestNodeJoinProxmoxNoMTLSDeprecationWarning(t *testing.T) {
	_, cleanup := initTestEnv(t)
	defer cleanup()
	resetRootFlags(t)

	buf, restore := captureSlog(t)
	defer restore()

	var out bytes.Buffer
	rootCmd.SetOut(&out)
	rootCmd.SetErr(&out)
	// proxmox path errors on missing --host before reaching the warning,
	// and never calls joinLocal, so no mTLS deprecation warning fires.
	rootCmd.SetArgs([]string{"node", "join", "--type", "proxmox", "--ssh-key", "/tmp/nonexistent-key"})
	_ = rootCmd.Execute()

	if strings.Contains(buf.String(), "mTLS join path is deprecated") {
		t.Errorf("proxmox path must not emit mTLS deprecation warning, got:\n%s", buf.String())
	}
}
