package cmd

import (
	"io"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/desktop/internal/daemon"
	"github.com/tidbcloud/AgentTeam/desktop/internal/daemoncontrol"
	"github.com/tidbcloud/AgentTeam/desktop/internal/operator"
	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
)

func TestDaemonCommandExposesAgentTeamConnectFlags(t *testing.T) {
	require.NotNil(t, daemonCmd.Flags().Lookup("host"))
	require.NotNil(t, daemonCmd.Flags().Lookup("port"))
	require.NotNil(t, daemonCmd.Flags().Lookup("connect"))
	require.NotNil(t, daemonCmd.Flags().Lookup("server-url"))
	require.NotNil(t, daemonCmd.Flags().Lookup("api-key"))
	require.NotNil(t, daemonCmd.Flags().Lookup("agentteam.server-url"))
	require.NotNil(t, daemonCmd.Flags().Lookup("agentteam.api-key"))
}

func TestDaemonCommandIsAlwaysHeadless(t *testing.T) {
	require.Nil(t, daemonCmd.Flags().Lookup("headless"))
	require.NotContains(t, strings.ToLower(daemonCmd.Long), "dashboard")
	require.Contains(t, strings.ToLower(daemonCmd.Long), "authenticated local api")
}

func TestDaemonOwnerBootstrapFlagsAreHiddenAndContainNoSecret(t *testing.T) {
	for _, name := range []string{"owner-kind", "owner-id", "owner-pid"} {
		flag := daemonCmd.Flags().Lookup(name)
		require.NotNil(t, flag)
		require.True(t, flag.Hidden, "%s must remain an internal Electron bootstrap flag", name)
	}
	for _, forbidden := range []string{"control-token", "session-token"} {
		require.Nil(t, daemonCmd.Flags().Lookup(forbidden))
	}
}

func TestChannelCommandDoesNotExposeConnectSubcommand(t *testing.T) {
	for _, subcmd := range channelCmd.Commands() {
		require.NotEqual(t, "connect", subcmd.Name())
	}
}

func TestRunDaemonCommandRejectsUnsupportedConnectType(t *testing.T) {
	originalOpts := daemonOpts
	t.Cleanup(func() {
		daemonOpts = originalOpts
	})
	resetDaemonCommandFlagsForTest()

	daemonOpts.connectType = "external"

	err := runDaemonCommand()
	require.EqualError(t, err, "daemon connect currently supports only --connect agentteam")
}

func TestRunDaemonCommandRequiresAgentTeamAPIKey(t *testing.T) {
	originalRunDaemonForeground := runDaemonForeground
	originalGetOperator := getOperator
	originalOpts := daemonOpts
	t.Cleanup(func() {
		runDaemonForeground = originalRunDaemonForeground
		getOperator = originalGetOperator
		daemonOpts = originalOpts
	})
	resetDaemonCommandFlagsForTest()

	called := false
	runDaemonForeground = func() error {
		called = true
		return nil
	}
	daemonOpts.connectType = "agentteam"

	err := runDaemonCommand()
	require.NoError(t, err)
	require.True(t, called)
}

func TestRunDaemonCommandRequiresAPIKeyWhenDaemonConnectFlagsAreSet(t *testing.T) {
	originalGetOperator := getOperator
	originalOpts := daemonOpts
	t.Cleanup(func() {
		getOperator = originalGetOperator
		daemonOpts = originalOpts
	})
	resetDaemonCommandFlagsForTest()

	getOperator = func() (operator.Operator, error) {
		return fakeOperator{servers: &fakeChannelOperator{}}, nil
	}

	require.NoError(t, daemonCmd.Flags().Set("server-url", "https://agentteam.example.com"))
	daemonOpts.connectType = ""
	daemonOpts.agentteamAPIKey = ""

	err := runDaemonCommand()
	require.EqualError(t, err, "agentteam daemon requires --api-key when saving AgentTeam machine settings")
}

func TestRunDaemonCommandReportsMachineSettingsSaved(t *testing.T) {
	originalGetOperator := getOperator
	originalDaemonStatus := daemonStatus
	originalOpts := daemonOpts
	t.Cleanup(func() {
		getOperator = originalGetOperator
		daemonStatus = originalDaemonStatus
		daemonOpts = originalOpts
	})
	resetDaemonCommandFlagsForTest()

	tempConfigDir := t.TempDir()
	t.Setenv(profile.ConfigDirEnvKey, tempConfigDir)
	require.NoError(t, profile.Save(profile.DefaultConfig(), profile.Path()))

	getOperator = func() (operator.Operator, error) {
		return fakeOperator{servers: &fakeChannelOperator{}}, nil
	}
	daemonStatus = func() *daemon.Status {
		return &daemon.Status{Running: true, Healthy: true, PID: 1234}
	}

	daemonOpts.connectType = "agentteam"
	daemonOpts.agentteamServerURL = "https://agentteam.example.com"
	daemonOpts.agentteamAPIKey = "key123"

	var err error
	output := captureStdout(t, func() {
		err = runDaemonCommand()
	})

	require.NoError(t, err)
	require.Equal(t, "AgentTeam auth ready.\nID:        chn_1\nServer:    https://agentteam.example.com\nWorkspace: wsp_1\n\n", output)
}

func TestRunDaemonCommandDefaultsToAgentTeamConnectWhenDaemonFlagsAreSet(t *testing.T) {
	originalGetOperator := getOperator
	originalDaemonStatus := daemonStatus
	originalOpts := daemonOpts
	t.Cleanup(func() {
		getOperator = originalGetOperator
		daemonStatus = originalDaemonStatus
		daemonOpts = originalOpts
	})
	resetDaemonCommandFlagsForTest()

	getOperator = func() (operator.Operator, error) {
		return fakeOperator{servers: &fakeChannelOperator{}}, nil
	}
	daemonStatus = func() *daemon.Status {
		return &daemon.Status{Running: true, Healthy: true, PID: 1234}
	}

	require.NoError(t, daemonCmd.Flags().Set("server-url", "https://agentteam.example.com"))
	require.NoError(t, daemonCmd.Flags().Set("api-key", "key123"))
	daemonOpts.connectType = ""

	var err error
	output := captureStdout(t, func() {
		err = runDaemonCommand()
	})

	require.NoError(t, err)
	require.Equal(t, "AgentTeam auth ready.\nID:        chn_1\nServer:    https://agentteam.example.com\nWorkspace: wsp_1\n\n", output)
}

func TestRunDaemonCommandSupportsLegacyAgentTeamPrefixedFlags(t *testing.T) {
	originalGetOperator := getOperator
	originalDaemonStatus := daemonStatus
	originalOpts := daemonOpts
	t.Cleanup(func() {
		getOperator = originalGetOperator
		daemonStatus = originalDaemonStatus
		daemonOpts = originalOpts
	})
	resetDaemonCommandFlagsForTest()

	getOperator = func() (operator.Operator, error) {
		return fakeOperator{servers: &fakeChannelOperator{}}, nil
	}
	daemonStatus = func() *daemon.Status {
		return &daemon.Status{Running: true, Healthy: true, PID: 1234}
	}

	require.NoError(t, daemonCmd.Flags().Set("agentteam.server-url", "https://agentteam.example.com"))
	require.NoError(t, daemonCmd.Flags().Set("agentteam.api-key", "key123"))
	daemonOpts.connectType = ""

	var err error
	output := captureStdout(t, func() {
		err = runDaemonCommand()
	})

	require.NoError(t, err)
	require.Equal(t, "AgentTeam auth ready.\nID:        chn_1\nServer:    https://agentteam.example.com\nWorkspace: wsp_1\n\n", output)
}

func TestRunDaemonCommandIgnoresExplicitAgentTeamConnectWhenAgentTeamFlagsAreUnset(t *testing.T) {
	originalRunDaemonForeground := runDaemonForeground
	originalOpts := daemonOpts
	t.Cleanup(func() {
		runDaemonForeground = originalRunDaemonForeground
		daemonOpts = originalOpts
	})
	resetDaemonCommandFlagsForTest()

	called := false
	runDaemonForeground = func() error {
		called = true
		return nil
	}

	require.NoError(t, daemonCmd.Flags().Set("connect", "agentteam"))
	daemonOpts.agentteamAPIKey = ""

	err := runDaemonCommand()
	require.NoError(t, err)
	require.True(t, called)
}

func TestRunDaemonForegroundStartsCurrentBinaryWithoutManagedReexec(t *testing.T) {
	originalDaemonStatus := daemonStatus
	originalStopUnhealthyProcess := stopUnhealthyProcess
	originalRunForegroundDaemon := runForegroundDaemon
	t.Cleanup(func() {
		daemonStatus = originalDaemonStatus
		stopUnhealthyProcess = originalStopUnhealthyProcess
		runForegroundDaemon = originalRunForegroundDaemon
	})

	daemonStatus = func() *daemon.Status {
		return &daemon.Status{Running: false}
	}

	started := false
	runForegroundDaemon = func(options daemon.RunOptions) error {
		started = true
		require.Equal(t, daemoncontrol.OwnerKindCLI, options.OwnerBootstrap.Kind)
		return nil
	}

	err := runDaemonForeground()
	require.NoError(t, err)
	require.True(t, started)
}

func TestRunDaemonForegroundStopsUnhealthyDaemonBeforeRestart(t *testing.T) {
	originalDaemonStatus := daemonStatus
	originalStopUnhealthyProcess := stopUnhealthyProcess
	originalRunForegroundDaemon := runForegroundDaemon
	t.Cleanup(func() {
		daemonStatus = originalDaemonStatus
		stopUnhealthyProcess = originalStopUnhealthyProcess
		runForegroundDaemon = originalRunForegroundDaemon
	})
	stopped := false
	daemonStatus = func() *daemon.Status {
		if !stopped {
			return &daemon.Status{Running: true, Healthy: false, PID: 4321}
		}
		return &daemon.Status{Running: false}
	}

	stopUnhealthyProcess = func(pid int, timeout time.Duration) error {
		stopped = true
		require.Equal(t, 4321, pid)
		return nil
	}

	started := false
	runForegroundDaemon = func(options daemon.RunOptions) error {
		started = true
		return nil
	}

	err := runDaemonForegroundImpl()
	require.NoError(t, err)
	require.True(t, stopped)
	require.True(t, started)
}

func TestRunDaemonCommandRestartsWhenDaemonProcessIsUnhealthy(t *testing.T) {
	originalGetOperator := getOperator
	originalDaemonStatus := daemonStatus
	originalRunDaemonForeground := runDaemonForeground
	originalOpts := daemonOpts
	t.Cleanup(func() {
		getOperator = originalGetOperator
		daemonStatus = originalDaemonStatus
		runDaemonForeground = originalRunDaemonForeground
		daemonOpts = originalOpts
	})
	resetDaemonCommandFlagsForTest()

	getOperator = func() (operator.Operator, error) {
		return fakeOperator{servers: &fakeChannelOperator{}}, nil
	}
	daemonStatus = func() *daemon.Status {
		return &daemon.Status{Running: true, Healthy: false, PID: 1234}
	}

	restarted := false
	runDaemonForeground = func() error {
		restarted = true
		return nil
	}

	daemonOpts.connectType = "agentteam"
	daemonOpts.agentteamServerURL = "https://agentteam.example.com"
	daemonOpts.agentteamAPIKey = "key123"

	err := runDaemonCommand()
	require.NoError(t, err)
	require.True(t, restarted)
}

func TestRunDaemonCommandStartsHeadlessForAgentTeamConnect(t *testing.T) {
	originalGetOperator := getOperator
	originalDaemonStatus := daemonStatus
	originalRunDaemonForeground := runDaemonForeground
	originalOpts := daemonOpts
	t.Cleanup(func() {
		getOperator = originalGetOperator
		daemonStatus = originalDaemonStatus
		runDaemonForeground = originalRunDaemonForeground
		daemonOpts = originalOpts
	})
	resetDaemonCommandFlagsForTest()

	getOperator = func() (operator.Operator, error) {
		return fakeOperator{servers: &fakeChannelOperator{}}, nil
	}
	daemonStatus = func() *daemon.Status {
		return &daemon.Status{Running: false}
	}

	runDaemonForeground = func() error {
		return nil
	}

	daemonOpts.connectType = "agentteam"
	daemonOpts.agentteamServerURL = "https://agentteam.example.com"
	daemonOpts.agentteamAPIKey = "key123"

	require.NoError(t, runDaemonCommand())
}

func resetDaemonCommandFlagsForTest() {
	resetDaemonCommandFlagForTest("connect", "")
	resetDaemonCommandFlagForTest("server-url", "https://agentteam.pingcap.com")
	resetDaemonCommandFlagForTest("api-key", "")
	resetDaemonCommandFlagForTest("agentteam.server-url", "https://agentteam.pingcap.com")
	resetDaemonCommandFlagForTest("agentteam.api-key", "")
}

func resetDaemonCommandFlagForTest(name, value string) {
	flag := daemonCmd.Flags().Lookup(name)
	if flag == nil {
		return
	}
	if err := flag.Value.Set(value); err != nil {
		panic(err)
	}
	flag.DefValue = value
	flag.Changed = false
}

func captureStdout(t *testing.T, fn func()) string {
	t.Helper()

	originalStdout := os.Stdout
	reader, writer, err := os.Pipe()
	require.NoError(t, err)

	os.Stdout = writer
	defer func() {
		os.Stdout = originalStdout
	}()

	fn()

	require.NoError(t, writer.Close())
	output, err := io.ReadAll(reader)
	require.NoError(t, err)
	require.NoError(t, reader.Close())
	return string(output)
}
