package main

import (
	"context"
	"flag"
	"fmt"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/rcourtman/pulse-go-rewrite/internal/dockeragent"
	"github.com/rcourtman/pulse-go-rewrite/internal/utils"
	"github.com/rs/zerolog"
)

type stringFlagList []string

func (l *stringFlagList) String() string {
	return strings.Join(*l, ",")
}

func (l *stringFlagList) Set(value string) error {
	*l = append(*l, value)
	return nil
}

func (l stringFlagList) Values() []string {
	if len(l) == 0 {
		return nil
	}
	return append([]string(nil), l...)
}

func main() {
	cfg := loadConfig()

	logger := zerolog.New(os.Stdout).With().Timestamp().Logger()
	cfg.Logger = &logger

	agent, err := dockeragent.New(cfg)
	if err != nil {
		logger.Fatal().Err(err).Msg("Failed to create docker agent")
	}
	defer agent.Close()

	ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
	defer cancel()

	logger.Info().Str("pulse_url", cfg.PulseURL).Dur("interval", cfg.Interval).Msg("Starting Pulse Docker agent")

	if err := agent.Run(ctx); err != nil && err != context.Canceled {
		logger.Fatal().Err(err).Msg("Agent terminated with error")
	}

	logger.Info().Msg("Agent stopped")
}

func loadConfig() dockeragent.Config {
	envURL := utils.GetenvTrim("PULSE_URL")
	envToken := utils.GetenvTrim("PULSE_TOKEN")
	envInterval := utils.GetenvTrim("PULSE_INTERVAL")
	envHostname := utils.GetenvTrim("PULSE_HOSTNAME")
	envAgentID := utils.GetenvTrim("PULSE_AGENT_ID")
	envInsecure := utils.GetenvTrim("PULSE_INSECURE_SKIP_VERIFY")
	envNoAutoUpdate := utils.GetenvTrim("PULSE_NO_AUTO_UPDATE")
	envTargets := utils.GetenvTrim("PULSE_TARGETS")
	envRuntime := utils.GetenvTrim("PULSE_RUNTIME")
	envContainerStates := utils.GetenvTrim("PULSE_CONTAINER_STATES")
	envSwarmScope := utils.GetenvTrim("PULSE_SWARM_SCOPE")
	envSwarmServices := utils.GetenvTrim("PULSE_SWARM_SERVICES")
	envSwarmTasks := utils.GetenvTrim("PULSE_SWARM_TASKS")
	envIncludeContainers := utils.GetenvTrim("PULSE_INCLUDE_CONTAINERS")
	envCollectDisk := utils.GetenvTrim("PULSE_COLLECT_DISK")

	defaultInterval := 30 * time.Second
	if envInterval != "" {
		if parsed, err := time.ParseDuration(envInterval); err == nil {
			defaultInterval = parsed
		}
	}

	swarmScopeDefault := envSwarmScope
	if swarmScopeDefault == "" {
		swarmScopeDefault = "node"
	}

	includeServicesDefault := true
	if envSwarmServices != "" {
		includeServicesDefault = utils.ParseBool(envSwarmServices)
	}

	includeTasksDefault := true
	if envSwarmTasks != "" {
		includeTasksDefault = utils.ParseBool(envSwarmTasks)
	}

	includeContainersDefault := true
	if envIncludeContainers != "" {
		includeContainersDefault = utils.ParseBool(envIncludeContainers)
	}

	collectDiskDefault := true
	if envCollectDisk != "" {
		collectDiskDefault = utils.ParseBool(envCollectDisk)
	}

	urlFlag := flag.String("url", envURL, "Pulse server URL (e.g. http://pulse:7655)")
	tokenFlag := flag.String("token", envToken, "Pulse API token (required)")
	intervalFlag := flag.Duration("interval", defaultInterval, "Reporting interval (e.g. 30s)")
	hostnameFlag := flag.String("hostname", envHostname, "Override hostname reported to Pulse")
	agentIDFlag := flag.String("agent-id", envAgentID, "Override agent identifier")
	insecureFlag := flag.Bool("insecure", utils.ParseBool(envInsecure), "Skip TLS certificate verification")
	noAutoUpdateFlag := flag.Bool("no-auto-update", utils.ParseBool(envNoAutoUpdate), "Disable automatic agent updates")
	runtimeFlag := flag.String("runtime", envRuntime, "Container runtime to expect (auto, docker, podman)")
	var targetFlags stringFlagList
	flag.Var(&targetFlags, "target", "Pulse target in url|token[|insecure] format. Repeat to send to multiple Pulse instances")
	var containerStateFlags stringFlagList
	flag.Var(&containerStateFlags, "container-state", "Only include containers whose status matches this value (repeat to allow multiple). Allowed values: created,running,restarting,removing,paused,exited,dead.")
	swarmScopeFlag := flag.String("swarm-scope", strings.ToLower(strings.TrimSpace(swarmScopeDefault)), "Swarm data scope to collect: node, cluster, or auto")
	includeServicesFlag := flag.Bool("swarm-services", includeServicesDefault, "Include Swarm service summaries in reports")
	includeTasksFlag := flag.Bool("swarm-tasks", includeTasksDefault, "Include Swarm tasks in reports")
	includeContainersFlag := flag.Bool("include-containers", includeContainersDefault, "Include per-container metrics in reports")
	collectDiskFlag := flag.Bool("collect-disk", collectDiskDefault, "Collect per-container disk usage, block IO, and mount details in reports")

	flag.Parse()

	// Check for common mistakes with unparsed arguments
	unparsedArgs := flag.Args()
	if len(unparsedArgs) > 0 {
		// Check if any look like flags with single dash
		for _, arg := range unparsedArgs {
			if strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") {
				fmt.Fprintf(os.Stderr, "error: unrecognized argument %q\n", arg)
				fmt.Fprintln(os.Stderr, "note: flags must use double dashes (e.g., --token, not -token)")
				fmt.Fprintln(os.Stderr, "\nUsage:")
				flag.Usage()
				os.Exit(1)
			}
		}
		fmt.Fprintf(os.Stderr, "error: unexpected arguments: %v\n", unparsedArgs)
		flag.Usage()
		os.Exit(1)
	}

	pulseURL := *urlFlag
	urlFromEnvOrFlag := envURL != "" || *urlFlag != envURL
	if pulseURL == "" {
		pulseURL = "http://localhost:7655"
	}

	targets := make([]dockeragent.TargetConfig, 0)

	if len(targetFlags) > 0 {
		parsedTargets, err := parseTargetSpecs(targetFlags.Values())
		if err != nil {
			fmt.Fprintf(os.Stderr, "error: %v\n", err)
			os.Exit(1)
		}
		targets = append(targets, parsedTargets...)
	}

	if envTargets != "" {
		envTargetSpecs := splitTargetSpecs(envTargets)
		if len(envTargetSpecs) > 0 {
			parsedTargets, err := parseTargetSpecs(envTargetSpecs)
			if err != nil {
				fmt.Fprintf(os.Stderr, "error: %v\n", err)
				os.Exit(1)
			}
			targets = append(targets, parsedTargets...)
		}
	}

	token := strings.TrimSpace(*tokenFlag)
	if token == "" && len(targets) == 0 {
		fmt.Fprintln(os.Stderr, "error: PULSE_TOKEN, --token, or at least one --target/PULSE_TARGETS entry must be provided")
		fmt.Fprintln(os.Stderr, "\nExample usage:")
		fmt.Fprintln(os.Stderr, "  pulse-docker-agent --url http://pulse.example.com:7655 --token <your-token>")
		fmt.Fprintln(os.Stderr, "\nOr set environment variables:")
		fmt.Fprintln(os.Stderr, "  export PULSE_URL=http://pulse.example.com:7655")
		fmt.Fprintln(os.Stderr, "  export PULSE_TOKEN=<your-token>")
		fmt.Fprintln(os.Stderr, "  pulse-docker-agent")
		os.Exit(1)
	}

	// Warn if using default localhost URL without explicit configuration
	if !urlFromEnvOrFlag && len(targets) == 0 && token != "" {
		fmt.Fprintln(os.Stderr, "warning: no --url or PULSE_URL provided, defaulting to http://localhost:7655")
		fmt.Fprintln(os.Stderr, "note: if your Pulse server is not on localhost, specify --url http://your-pulse-server:7655")
		fmt.Fprintln(os.Stderr, "")
	}

	interval := *intervalFlag
	if interval <= 0 {
		interval = 30 * time.Second
	}

	containerStates := make([]string, 0)
	if len(containerStateFlags) > 0 {
		containerStates = append(containerStates, containerStateFlags.Values()...)
	}
	if envContainerStates != "" {
		containerStates = append(containerStates, splitStringList(envContainerStates)...)
	}

	return dockeragent.Config{
		PulseURL:           pulseURL,
		APIToken:           token,
		Interval:           interval,
		HostnameOverride:   strings.TrimSpace(*hostnameFlag),
		AgentID:            strings.TrimSpace(*agentIDFlag),
		InsecureSkipVerify: *insecureFlag,
		DisableAutoUpdate:  *noAutoUpdateFlag,
		Targets:            targets,
		ContainerStates:    containerStates,
		SwarmScope:         strings.ToLower(strings.TrimSpace(*swarmScopeFlag)),
		Runtime:            strings.ToLower(strings.TrimSpace(*runtimeFlag)),
		IncludeServices:    *includeServicesFlag,
		IncludeTasks:       *includeTasksFlag,
		IncludeContainers:  *includeContainersFlag,
		CollectDiskMetrics: *collectDiskFlag,
	}
}

func parseTargetSpecs(specs []string) ([]dockeragent.TargetConfig, error) {
	targets := make([]dockeragent.TargetConfig, 0, len(specs))
	for _, spec := range specs {
		spec = strings.TrimSpace(spec)
		if spec == "" {
			continue
		}
		target, err := parseTargetSpec(spec)
		if err != nil {
			return nil, err
		}
		targets = append(targets, target)
	}
	return targets, nil
}

func parseTargetSpec(spec string) (dockeragent.TargetConfig, error) {
	parts := strings.Split(spec, "|")
	if len(parts) < 2 {
		return dockeragent.TargetConfig{}, fmt.Errorf("invalid target %q: expected format url|token[|insecure]", spec)
	}

	url := strings.TrimSpace(parts[0])
	token := strings.TrimSpace(parts[1])
	if url == "" {
		return dockeragent.TargetConfig{}, fmt.Errorf("invalid target %q: URL is required", spec)
	}
	if token == "" {
		return dockeragent.TargetConfig{}, fmt.Errorf("invalid target %q: token is required", spec)
	}

	insecure := false
	if len(parts) >= 3 {
		switch strings.ToLower(strings.TrimSpace(parts[2])) {
		case "1", "true", "yes", "y", "on":
			insecure = true
		case "", "0", "false", "no", "n", "off":
			insecure = false
		default:
			return dockeragent.TargetConfig{}, fmt.Errorf("invalid target %q: insecure flag must be true/false", spec)
		}
	}

	return dockeragent.TargetConfig{
		URL:                url,
		Token:              token,
		InsecureSkipVerify: insecure,
	}, nil
}

func splitTargetSpecs(value string) []string {
	if value == "" {
		return nil
	}

	normalized := strings.ReplaceAll(value, "\n", ";")
	raw := strings.Split(normalized, ";")
	result := make([]string, 0, len(raw))
	for _, item := range raw {
		if trimmed := strings.TrimSpace(item); trimmed != "" {
			result = append(result, trimmed)
		}
	}
	return result
}

func splitStringList(value string) []string {
	if value == "" {
		return nil
	}

	items := strings.FieldsFunc(value, func(r rune) bool {
		switch r {
		case ',', ';', '\n', '\r':
			return true
		default:
			return false
		}
	})

	result := make([]string, 0, len(items))
	for _, item := range items {
		if trimmed := strings.TrimSpace(item); trimmed != "" {
			result = append(result, trimmed)
		}
	}

	return result
}
