package cmd

import (
	"fmt"
	"path/filepath"
	"runtime"
	"strings"
	"testing"

	"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/profile"
)

func TestLinuxServiceCommandsExposeUserFlag(t *testing.T) {
	if runtime.GOOS != "linux" {
		t.Skip("Linux-only service flag")
	}

	require.NotNil(t, serviceInstallCmd.Flags().Lookup("user"))
	require.NotNil(t, serviceUninstallCmd.Flags().Lookup("user"))
	require.NotNil(t, serviceRestartCmd.Flags().Lookup("user"))
	require.NotNil(t, serviceReinstallCmd.Flags().Lookup("user"))
	require.NotNil(t, statusCmd.Flags().Lookup("user"))
	require.NotNil(t, serviceStartCmd.Flags().Lookup("user"))
	require.NotNil(t, serviceStopCmd.Flags().Lookup("user"))
}

func TestServiceCommandHintsRespectScope(t *testing.T) {
	if runtime.GOOS != "linux" {
		t.Skip("Linux-only user service hints")
	}

	require.Equal(t, "agentteam service install", installCommandHint(daemon.ServiceScopeSystem))
	require.Equal(t, "agentteam service install --user", installCommandHint(daemon.ServiceScopeUser))
	require.Equal(t, "agentteam service start", startCommandHint(daemon.ServiceScopeSystem))
	require.Equal(t, "agentteam service start --user", startCommandHint(daemon.ServiceScopeUser))
	require.Equal(t, "agentteam status", statusCommandHint(daemon.ServiceScopeSystem))
	require.Equal(t, "agentteam status --user", statusCommandHint(daemon.ServiceScopeUser))
}

func TestValidateLinuxUserServiceInvocationRejectsSudo(t *testing.T) {
	if runtime.GOOS != "linux" {
		t.Skip("Linux-only sudo guard")
	}

	err := validateLinuxUserServiceInvocation(0, "alice")
	require.Error(t, err)
	require.Contains(t, err.Error(), "run it without sudo")
}

func TestValidateLinuxUserServiceInvocationAllowsDirectUserSession(t *testing.T) {
	require.NoError(t, validateLinuxUserServiceInvocation(1000, ""))
	require.NoError(t, validateLinuxUserServiceInvocation(0, ""))
}

func TestServiceConfigPathForSudoSystemServiceUsesOriginalUserHome(t *testing.T) {
	got := serviceConfigPathFor(
		"linux",
		daemon.ServiceScopeSystem,
		0,
		"alice",
		"",
		"/root/.agentteam/config.yaml",
		func(username string) (string, bool) {
			require.Equal(t, "alice", username)
			return "/home/alice", true
		},
	)

	require.Equal(t, filepath.Join("/home/alice", ".agentteam", "config.yaml"), got)
}

func TestServiceConfigPathForHonorsExplicitConfigDir(t *testing.T) {
	got := serviceConfigPathFor(
		"linux",
		daemon.ServiceScopeSystem,
		0,
		"alice",
		"/srv/agentteam-profile",
		"/root/.agentteam/config.yaml",
		func(string) (string, bool) {
			t.Fatal("lookup should not be called when AGENTTEAM_CONFIG_DIR is set")
			return "", false
		},
	)

	require.Equal(t, filepath.Join("/srv/agentteam-profile", "config.yaml"), got)
}

func TestServiceConfigPathForBareRootKeepsRootProfile(t *testing.T) {
	got := serviceConfigPathFor(
		"linux",
		daemon.ServiceScopeSystem,
		0,
		"",
		"",
		"/root/.agentteam/config.yaml",
		func(string) (string, bool) {
			t.Fatal("lookup should not be called without SUDO_USER")
			return "", false
		},
	)

	require.Equal(t, "/root/.agentteam/config.yaml", got)
}

func TestServiceCommandHelpMentionsTopLevelStatus(t *testing.T) {
	require.Contains(t, serviceCmd.Long, "agentteam status")
	require.NotContains(t, serviceCmd.Long, "agentteam service status")
	require.True(t, strings.Contains(serviceCmd.Long, "starts with the user's systemd manager"))
}

func TestStatusScopesDefaultToBothLinuxManagers(t *testing.T) {
	require.Equal(t, []daemon.ServiceScope{daemon.ServiceScopeSystem, daemon.ServiceScopeUser}, statusScopes("linux", false))
	require.Equal(t, []daemon.ServiceScope{daemon.ServiceScopeUser}, statusScopes("linux", true))
	require.Equal(t, []daemon.ServiceScope{daemon.ServiceScopeUser}, statusScopes("darwin", false))
	require.Equal(t, []daemon.ServiceScope{daemon.ServiceScopeUser}, statusScopes("darwin", true))
	require.Equal(t, []daemon.ServiceScope{daemon.ServiceScopeSystem}, statusScopes("windows", false))
}

func TestInstallStartsServiceOnlyWhenPlatformRequiresIt(t *testing.T) {
	require.True(t, installStartsService("darwin"))
	require.False(t, installStartsService("linux"))
	require.False(t, installStartsService("windows"))
}

func TestReinstallStartsServiceWhenRunningOrMacOSFirstInstall(t *testing.T) {
	require.True(t, reinstallStartsService("linux", true, true))
	require.False(t, reinstallStartsService("linux", true, false))
	require.True(t, reinstallStartsService("darwin", false, false))
	require.False(t, reinstallStartsService("darwin", true, false))
}

func TestCollectServiceStatusReportIncludesInstalledServiceStatuses(t *testing.T) {
	originalInstallerFactory := newServiceInstaller
	originalDaemonStatus := currentDaemonStatus
	defer func() {
		newServiceInstaller = originalInstallerFactory
		currentDaemonStatus = originalDaemonStatus
	}()

	var requestedScopes []daemon.ServiceScope
	newServiceInstaller = func(binPath, configPath string, scope daemon.ServiceScope) (daemon.ServiceInstaller, error) {
		requestedScopes = append(requestedScopes, scope)
		switch scope {
		case daemon.ServiceScopeSystem:
			return fakeServiceInstaller{name: "systemd", installed: true, status: "inactive"}, nil
		case daemon.ServiceScopeUser:
			return fakeServiceInstaller{name: "systemd", installed: true, status: "active"}, nil
		default:
			return nil, fmt.Errorf("unexpected scope %s", scope)
		}
	}
	currentDaemonStatus = func() *daemon.Status {
		return &daemon.Status{
			Running: true, Healthy: true, PID: 1234,
			BaseURL: "http://127.0.0.1:43123", InstanceID: "instance-a",
			DesktopAPIVersion: 1, OwnerKind: daemoncontrol.OwnerKindService,
		}
	}

	report, err := collectServiceStatusReport("linux", false, "/tmp/agentteam", "")
	require.NoError(t, err)
	require.Equal(t, []daemon.ServiceScope{daemon.ServiceScopeSystem, daemon.ServiceScopeUser}, requestedScopes)
	require.Len(t, report.Services, 2)
	require.Equal(t, "inactive", report.Services[0].Status)
	require.Zero(t, report.Services[0].PID)
	require.Equal(t, "active", report.Services[1].Status)
	require.Equal(t, 1234, report.Services[1].PID)
	require.True(t, report.Services[1].Healthy)
	require.Equal(t, "http://127.0.0.1:43123", report.Services[1].BaseURL)
	require.Equal(t, "instance-a", report.Services[1].InstanceID)
	require.Equal(t, 1, report.Services[1].APIVersion)
	require.Equal(t, "service", report.Services[1].OwnerKind)
}

func TestCollectServiceStatusReportHonorsExplicitUserScope(t *testing.T) {
	originalInstallerFactory := newServiceInstaller
	originalDaemonStatus := currentDaemonStatus
	defer func() {
		newServiceInstaller = originalInstallerFactory
		currentDaemonStatus = originalDaemonStatus
	}()

	var requestedScopes []daemon.ServiceScope
	newServiceInstaller = func(binPath, configPath string, scope daemon.ServiceScope) (daemon.ServiceInstaller, error) {
		requestedScopes = append(requestedScopes, scope)
		return fakeServiceInstaller{name: "systemd", installed: true, status: "active"}, nil
	}
	currentDaemonStatus = func() *daemon.Status {
		return &daemon.Status{}
	}

	report, err := collectServiceStatusReport("linux", true, "/tmp/agentteam", "")
	require.NoError(t, err)
	require.Len(t, report.Services, 1)
	require.Equal(t, []daemon.ServiceScope{daemon.ServiceScopeUser}, requestedScopes)
	require.Equal(t, "user", report.Services[0].Scope)
	require.Empty(t, report.Services[0].BaseURL)
}

func TestCollectServiceStatusReportIncludesInactiveServices(t *testing.T) {
	originalInstallerFactory := newServiceInstaller
	originalDaemonStatus := currentDaemonStatus
	defer func() {
		newServiceInstaller = originalInstallerFactory
		currentDaemonStatus = originalDaemonStatus
	}()

	newServiceInstaller = func(binPath, configPath string, scope daemon.ServiceScope) (daemon.ServiceInstaller, error) {
		return fakeServiceInstaller{name: "systemd", installed: true, status: "inactive"}, nil
	}
	currentDaemonStatus = func() *daemon.Status {
		return &daemon.Status{}
	}

	report, err := collectServiceStatusReport("linux", false, "/tmp/agentteam", "")
	require.NoError(t, err)
	require.Len(t, report.Services, 2)
	require.Equal(t, "inactive", report.Services[0].Status)
	require.Equal(t, "inactive", report.Services[1].Status)
	require.Zero(t, report.Services[0].PID)
	require.Zero(t, report.Services[1].PID)
}

func TestCollectServiceStatusReportIncludesForegroundDaemonWithoutCredentials(t *testing.T) {
	originalInstallerFactory := newServiceInstaller
	originalDaemonStatus := currentDaemonStatus
	defer func() {
		newServiceInstaller = originalInstallerFactory
		currentDaemonStatus = originalDaemonStatus
	}()
	newServiceInstaller = func(binPath, configPath string, scope daemon.ServiceScope) (daemon.ServiceInstaller, error) {
		return fakeServiceInstaller{name: "systemd", installed: false}, nil
	}
	currentDaemonStatus = func() *daemon.Status {
		return &daemon.Status{
			Running: true, Healthy: true, PID: 4321,
			BaseURL: "http://127.0.0.1:8084", InstanceID: "instance-local",
			DesktopAPIVersion: 1, OwnerKind: daemoncontrol.OwnerKindCLI,
		}
	}
	report, err := collectServiceStatusReport("linux", false, "/tmp/agentteam", "")
	require.NoError(t, err)
	require.Len(t, report.Services, 1)
	info := report.Services[0]
	require.Equal(t, "agentteam-daemon", info.Service)
	require.Equal(t, 4321, info.PID)
	require.Equal(t, "http://127.0.0.1:8084", info.BaseURL)
	require.Equal(t, "instance-local", info.InstanceID)
	require.NotContains(t, fmt.Sprintf("%+v", info), "temp-key")
}

func TestServiceStatusDoesNotRewriteManagedBinary(t *testing.T) {
	originalInstallerFactory := newServiceInstaller
	originalManagedStatusBinaryPath := managedStatusBinaryPath
	originalDaemonStatus := currentDaemonStatus
	originalFormat := serviceOpts.format
	defer func() {
		newServiceInstaller = originalInstallerFactory
		managedStatusBinaryPath = originalManagedStatusBinaryPath
		currentDaemonStatus = originalDaemonStatus
		serviceOpts.format = originalFormat
	}()

	serviceOpts.format = "table"
	managedStatusBinaryPath = func() string {
		return "/tmp/existing-managed-agentteam"
	}
	newServiceInstaller = func(binPath, configPath string, scope daemon.ServiceScope) (daemon.ServiceInstaller, error) {
		require.Equal(t, "/tmp/existing-managed-agentteam", binPath)
		return fakeServiceInstaller{name: "service", installed: false}, nil
	}
	currentDaemonStatus = func() *daemon.Status {
		return &daemon.Status{}
	}

	require.NoError(t, runServiceStatus(statusCmd, nil))
}

func TestServiceRestartUsesInstallerRestartWithoutReinstall(t *testing.T) {
	originalInstallerFactory := newServiceInstaller
	originalEnsureManaged := ensureServiceManagedBinary
	originalUser := serviceOpts.user
	defer func() {
		newServiceInstaller = originalInstallerFactory
		ensureServiceManagedBinary = originalEnsureManaged
		serviceOpts.user = originalUser
	}()

	// Avoid the Linux sudo path when this package is tested on Linux.
	serviceOpts.user = runtime.GOOS == "linux"

	ensureServiceManagedBinary = func(sourcePath string) (string, error) {
		require.NotEmpty(t, sourcePath)
		return "/tmp/managed-agentteam", nil
	}

	installer := &recordingServiceInstaller{
		name:      "launchd",
		installed: true,
	}
	newServiceInstaller = func(binPath, configPath string, scope daemon.ServiceScope) (daemon.ServiceInstaller, error) {
		require.Equal(t, "/tmp/managed-agentteam", binPath)
		if runtime.GOOS == "linux" {
			require.Equal(t, daemon.ServiceScopeUser, scope)
		}
		return installer, nil
	}

	require.NoError(t, serviceRestartCmd.RunE(serviceRestartCmd, nil))
	require.Equal(t, []string{"restart"}, installer.calls)
}

func TestServiceReinstallRestartsPreviouslyRunningService(t *testing.T) {
	originalInstallerFactory := newServiceInstaller
	originalUser := serviceOpts.user
	defer func() {
		newServiceInstaller = originalInstallerFactory
		serviceOpts.user = originalUser
	}()

	t.Setenv(profile.ConfigDirEnvKey, t.TempDir())
	serviceOpts.user = runtime.GOOS == "linux"

	installer := &recordingServiceInstaller{
		name:      "systemd",
		installed: true,
		status:    "active",
	}
	newServiceInstaller = func(binPath, configPath string, scope daemon.ServiceScope) (daemon.ServiceInstaller, error) {
		require.NotEmpty(t, binPath)
		require.NotEmpty(t, configPath)
		return installer, nil
	}

	require.NoError(t, serviceReinstallCmd.RunE(serviceReinstallCmd, nil))
	require.Equal(t, []string{"uninstall", "install"}, installer.calls)
	require.Len(t, installer.installOptions, 1)
	require.True(t, installer.installOptions[0].Start)
}

func TestServiceReinstallLeavesInactiveServiceStopped(t *testing.T) {
	originalInstallerFactory := newServiceInstaller
	originalUser := serviceOpts.user
	defer func() {
		newServiceInstaller = originalInstallerFactory
		serviceOpts.user = originalUser
	}()

	t.Setenv(profile.ConfigDirEnvKey, t.TempDir())
	serviceOpts.user = runtime.GOOS == "linux"

	installer := &recordingServiceInstaller{
		name:      "systemd",
		installed: true,
		status:    "inactive",
	}
	newServiceInstaller = func(binPath, configPath string, scope daemon.ServiceScope) (daemon.ServiceInstaller, error) {
		require.NotEmpty(t, binPath)
		require.NotEmpty(t, configPath)
		return installer, nil
	}

	require.NoError(t, serviceReinstallCmd.RunE(serviceReinstallCmd, nil))
	require.Equal(t, []string{"uninstall", "install"}, installer.calls)
	require.Len(t, installer.installOptions, 1)
	require.False(t, installer.installOptions[0].Start)
}

type fakeServiceInstaller struct {
	name      string
	installed bool
	status    string
}

func (f fakeServiceInstaller) Name() string { return f.name }

func (f fakeServiceInstaller) Install(daemon.InstallOptions) error { return nil }

func (f fakeServiceInstaller) Uninstall() error { return nil }

func (f fakeServiceInstaller) Start() error { return nil }

func (f fakeServiceInstaller) Stop() error { return nil }

func (f fakeServiceInstaller) Restart() error { return nil }

func (f fakeServiceInstaller) IsInstalled() bool { return f.installed }

func (f fakeServiceInstaller) Status() (string, error) { return f.status, nil }

type recordingServiceInstaller struct {
	name           string
	installed      bool
	status         string
	calls          []string
	installOptions []daemon.InstallOptions
}

func (r *recordingServiceInstaller) Name() string { return r.name }

func (r *recordingServiceInstaller) Install(opts daemon.InstallOptions) error {
	r.calls = append(r.calls, "install")
	r.installOptions = append(r.installOptions, opts)
	return nil
}

func (r *recordingServiceInstaller) Uninstall() error {
	r.calls = append(r.calls, "uninstall")
	return nil
}

func (r *recordingServiceInstaller) Start() error {
	r.calls = append(r.calls, "start")
	return nil
}

func (r *recordingServiceInstaller) Stop() error {
	r.calls = append(r.calls, "stop")
	return nil
}

func (r *recordingServiceInstaller) Restart() error {
	r.calls = append(r.calls, "restart")
	return nil
}

func (r *recordingServiceInstaller) IsInstalled() bool { return r.installed }

func (r *recordingServiceInstaller) Status() (string, error) { return r.status, nil }
