package cli

import (
	"fmt"
	"os"
	"runtime"
	"strings"

	"github.com/kardianos/service"
	"github.com/spf13/cobra"
)

// filterEmptyStrings removes empty strings from a slice
// This is used to clean up command line arguments and configuration values
func filterEmptyStrings(slice []string) []string {
	var result []string
	for _, s := range slice {
		if s != "" {
			result = append(result, s)
		}
	}
	return result
}

// ServiceCommand handles service-related operations
// This encapsulates all service management functionality for the CLI
type ServiceCommand struct {
	serviceManager *ServiceManager
}

// initializeServiceManager creates a service manager with default configuration
// This sets up the basic service infrastructure needed for all service operations
func (sc *ServiceCommand) initializeServiceManager() (service.Service, *prog, error) {
	svcConfig := sc.createServiceConfig()
	return sc.initializeServiceManagerWithServiceConfig(svcConfig)
}

// initializeServiceManagerWithServiceConfig creates a service manager with the given configuration
// This allows for custom service configuration while maintaining the same initialization pattern
func (sc *ServiceCommand) initializeServiceManagerWithServiceConfig(svcConfig *service.Config) (service.Service, *prog, error) {
	p := &prog{}

	s, err := sc.newService(p, svcConfig)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to create service: %w", err)
	}

	sc.serviceManager = &ServiceManager{prog: p, svc: s}
	return s, p, nil
}

// newService creates a new service instance using the provided program and configuration.
// This abstracts the service creation process for different operating systems
func (sc *ServiceCommand) newService(p *prog, svcConfig *service.Config) (service.Service, error) {
	s, err := newService(p, svcConfig)
	if err != nil {
		return nil, fmt.Errorf("failed to create service: %w", err)
	}
	return s, nil
}

// NewServiceCommand creates a new service command handler
// This provides a clean factory method for creating service command instances
func NewServiceCommand() *ServiceCommand {
	return &ServiceCommand{}
}

// createServiceConfig creates a properly initialized service configuration
// This ensures consistent service naming and description across all platforms
func (sc *ServiceCommand) createServiceConfig() *service.Config {
	return &service.Config{
		Name:        ctrldServiceName,
		DisplayName: "Control-D Helper Service",
		Description: "A highly configurable, multi-protocol DNS forwarding proxy",
		Option:      service.KeyValue{},
	}
}

// InitServiceCmd creates the service command with proper logic and aliases
// This sets up all service-related subcommands with appropriate permissions and flags
func InitServiceCmd(rootCmd *cobra.Command) *cobra.Command {
	// Create service command handlers
	sc := NewServiceCommand()

	startCmd, startCmdAlias := createStartCommands(sc)
	rootCmd.AddCommand(startCmdAlias)

	// Stop command
	stopCmd := &cobra.Command{
		Use:   "stop",
		Short: "Stop the ctrld service",
		Args:  cobra.NoArgs,
		PreRun: func(cmd *cobra.Command, args []string) {
			checkHasElevatedPrivilege()
		},
		RunE: sc.Stop,
	}
	stopCmd.Flags().StringVarP(&iface, "iface", "", "", `Reset DNS setting for iface, "auto" means the default interface gateway`)
	stopCmd.Flags().Int64VarP(&deactivationPin, "pin", "", defaultDeactivationPin, `Pin code for stopping ctrld`)
	_ = stopCmd.Flags().MarkHidden("pin")

	// Restart command
	restartCmd := &cobra.Command{
		Use:   "restart",
		Short: "Restart the ctrld service",
		Args:  cobra.NoArgs,
		PreRun: func(cmd *cobra.Command, args []string) {
			checkHasElevatedPrivilege()
		},
		RunE: sc.Restart,
	}

	// Status command
	statusCmd := &cobra.Command{
		Use:   "status",
		Short: "Show status of the ctrld service",
		Args:  cobra.NoArgs,
		RunE:  sc.Status,
	}
	if runtime.GOOS == "darwin" {
		// On darwin, running status command without privileges may return wrong information.
		statusCmd.PreRun = func(cmd *cobra.Command, args []string) {
			checkHasElevatedPrivilege()
		}
	}

	// Reload command
	reloadCmd := &cobra.Command{
		Use:   "reload",
		Short: "Reload the ctrld service",
		Args:  cobra.NoArgs,
		PreRun: func(cmd *cobra.Command, args []string) {
			checkHasElevatedPrivilege()
		},
		RunE: sc.Reload,
	}

	// Uninstall command
	uninstallCmd := &cobra.Command{
		Use:   "uninstall",
		Short: "Stop and uninstall the ctrld service",
		Long: `Stop and uninstall the ctrld service.

NOTE: Uninstalling will set DNS to values provided by DHCP.`,
		Args: cobra.NoArgs,
		PreRun: func(cmd *cobra.Command, args []string) {
			checkHasElevatedPrivilege()
		},
		RunE: sc.Uninstall,
	}
	uninstallCmd.Flags().StringVarP(&iface, "iface", "", "", `Reset DNS setting for iface, "auto" means the default interface gateway`)
	uninstallCmd.Flags().Int64VarP(&deactivationPin, "pin", "", defaultDeactivationPin, `Pin code for stopping ctrld`)
	_ = uninstallCmd.Flags().MarkHidden("pin")
	uninstallCmd.Flags().BoolVarP(&cleanup, "cleanup", "", false, `Removing ctrld binary and config files`)

	// Interfaces command - use the existing InitInterfacesCmd function
	interfacesCmd := InitInterfacesCmd(rootCmd)

	stopCmdAlias := &cobra.Command{
		PreRun: func(cmd *cobra.Command, args []string) {
			checkHasElevatedPrivilege()
		},
		Use:   "stop",
		Short: "Quick stop service and remove DNS from interface",
		RunE: func(cmd *cobra.Command, args []string) error {
			if !cmd.Flags().Changed("iface") {
				os.Args = append(os.Args, "--iface="+ifaceStartStop)
			}
			iface = ifaceStartStop
			return stopCmd.RunE(cmd, args)
		},
	}
	stopCmdAlias.Flags().StringVarP(&ifaceStartStop, "iface", "", "auto", `Reset DNS setting for iface, "auto" means the default interface gateway`)
	stopCmdAlias.Flags().AddFlagSet(stopCmd.Flags())
	rootCmd.AddCommand(stopCmdAlias)

	// Create aliases for other service commands
	restartCmdAlias := &cobra.Command{
		PreRun: func(cmd *cobra.Command, args []string) {
			checkHasElevatedPrivilege()
		},
		Use:   "restart",
		Short: "Restart the ctrld service",
		RunE: func(cmd *cobra.Command, args []string) error {
			return restartCmd.RunE(cmd, args)
		},
	}
	rootCmd.AddCommand(restartCmdAlias)

	reloadCmdAlias := &cobra.Command{
		PreRun: func(cmd *cobra.Command, args []string) {
			checkHasElevatedPrivilege()
		},
		Use:   "reload",
		Short: "Reload the ctrld service",
		RunE: func(cmd *cobra.Command, args []string) error {
			return reloadCmd.RunE(cmd, args)
		},
	}
	rootCmd.AddCommand(reloadCmdAlias)

	statusCmdAlias := &cobra.Command{
		Use:   "status",
		Short: "Show status of the ctrld service",
		Args:  cobra.NoArgs,
		RunE:  statusCmd.RunE,
	}
	rootCmd.AddCommand(statusCmdAlias)

	uninstallCmdAlias := &cobra.Command{
		PreRun: func(cmd *cobra.Command, args []string) {
			checkHasElevatedPrivilege()
		},
		Use:   "uninstall",
		Short: "Stop and uninstall the ctrld service",
		Long: `Stop and uninstall the ctrld service.

NOTE: Uninstalling will set DNS to values provided by DHCP.`,
		RunE: func(cmd *cobra.Command, args []string) error {
			if !cmd.Flags().Changed("iface") {
				os.Args = append(os.Args, "--iface="+ifaceStartStop)
			}
			iface = ifaceStartStop
			return uninstallCmd.RunE(cmd, args)
		},
	}
	uninstallCmdAlias.Flags().StringVarP(&ifaceStartStop, "iface", "", "auto", `Reset DNS setting for iface, "auto" means the default interface gateway`)
	uninstallCmdAlias.Flags().AddFlagSet(uninstallCmd.Flags())
	rootCmd.AddCommand(uninstallCmdAlias)

	// Create service command
	serviceCmd := &cobra.Command{
		Use:   "service",
		Short: "Manage ctrld service",
		Args:  cobra.OnlyValidArgs,
	}
	serviceCmd.ValidArgs = make([]string, 7)
	serviceCmd.ValidArgs[0] = startCmd.Use
	serviceCmd.ValidArgs[1] = stopCmd.Use
	serviceCmd.ValidArgs[2] = restartCmd.Use
	serviceCmd.ValidArgs[3] = reloadCmd.Use
	serviceCmd.ValidArgs[4] = statusCmd.Use
	serviceCmd.ValidArgs[5] = uninstallCmd.Use
	serviceCmd.ValidArgs[6] = interfacesCmd.Use

	serviceCmd.AddCommand(startCmd)
	serviceCmd.AddCommand(stopCmd)
	serviceCmd.AddCommand(restartCmd)
	serviceCmd.AddCommand(reloadCmd)
	serviceCmd.AddCommand(statusCmd)
	serviceCmd.AddCommand(uninstallCmd)
	serviceCmd.AddCommand(interfacesCmd)

	rootCmd.AddCommand(serviceCmd)

	return serviceCmd
}

// validInterceptMode reports whether the given value is a recognized --intercept-mode.
// This is the single source of truth for mode validation — used by the early start
// command check, the runtime validation in prog.go, and onlyInterceptFlags below.
// Add new modes here to have them recognized everywhere.
func validInterceptMode(mode string) bool {
	switch mode {
	case "off", "dns", "hard":
		return true
	}
	return false
}

// validFirewallMode reports whether the given value is a recognized --firewall-mode.
func validFirewallMode(mode string) bool {
	switch mode {
	case "off", "on":
		return true
	}
	return false
}

// onlyInterceptFlags reports whether args contain only intercept mode
// flags (--intercept-mode <value>) and flags that are auto-added by the
// start command alias (--iface). This is used to detect "ctrld start --intercept-mode dns"
// (or "off" to disable) on an existing installation, where the intent is to modify the
// intercept flag on the existing service without replacing other arguments.
//
// Note: the startCmdAlias appends "--iface=auto" to os.Args when --iface isn't
// explicitly provided, so we must allow it here.
func onlyInterceptFlags(args []string) bool {
	hasIntercept := false
	for i := 0; i < len(args); i++ {
		arg := args[i]
		switch {
		case arg == "--intercept-mode":
			// Next arg must be a valid mode value.
			if i+1 < len(args) && validInterceptMode(args[i+1]) {
				hasIntercept = true
				i++ // skip the value
			} else {
				return false
			}
		case strings.HasPrefix(arg, "--intercept-mode="):
			val := strings.TrimPrefix(arg, "--intercept-mode=")
			if validInterceptMode(val) {
				hasIntercept = true
			} else {
				return false
			}
		case arg == "--iface="+autoIface || arg == "--iface" || arg == autoIface:
			// Auto-added by startCmdAlias or its value; safe to ignore.
			continue
		default:
			return false
		}
	}
	return hasIntercept
}
