package main

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

	"github.com/rcourtman/pulse-go-rewrite/internal/agentupdate"
	"github.com/rcourtman/pulse-go-rewrite/internal/hostagent"
	"github.com/rcourtman/pulse-go-rewrite/internal/utils"
	"github.com/rs/zerolog"
	"golang.org/x/sync/errgroup"
)

var (
	// Version is the semantic version of the agent, set at build time via ldflags
	Version = "dev"
)

// Config holds the configuration for the standalone host agent
type Config struct {
	HostConfig        hostagent.Config
	DisableAutoUpdate bool
}

type multiValue []string

func (m *multiValue) String() string {
	return strings.Join(*m, ",")
}

func (m *multiValue) Set(value string) error {
	*m = append(*m, value)
	return nil
}

func main() {
	cfg := loadConfig()
	hostCfg := cfg.HostConfig

	zerolog.SetGlobalLevel(hostCfg.LogLevel)

	logger := zerolog.New(os.Stdout).Level(hostCfg.LogLevel).With().Timestamp().Logger()
	hostCfg.Logger = &logger

	// Check if we should run as a Windows service
	if err := runAsWindowsService(cfg, logger); err != nil {
		logger.Fatal().Err(err).Msg("Windows service failed")
	}

	// If runAsWindowsService returns nil without error, we're not running as a service
	// or we're on a non-Windows platform, so run normally

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

	g, ctx := errgroup.WithContext(ctx)

	logger.Info().
		Str("version", Version).
		Str("pulse_url", hostCfg.PulseURL).
		Str("agent_id", hostCfg.AgentID).
		Dur("interval", hostCfg.Interval).
		Bool("auto_update", !cfg.DisableAutoUpdate).
		Msg("Starting Pulse host agent")

	// Start Auto-Updater
	updater := agentupdate.New(agentupdate.Config{
		PulseURL:           hostCfg.PulseURL,
		APIToken:           hostCfg.APIToken,
		AgentName:          "pulse-host-agent",
		CurrentVersion:     Version,
		CheckInterval:      1 * time.Hour,
		InsecureSkipVerify: hostCfg.InsecureSkipVerify,
		Logger:             &logger,
		Disabled:           cfg.DisableAutoUpdate,
	})

	g.Go(func() error {
		updater.RunLoop(ctx)
		return nil
	})

	// Start the host agent
	agent, err := hostagent.New(hostCfg)
	if err != nil {
		logger.Fatal().Err(err).Msg("failed to initialise host agent")
	}

	g.Go(func() error {
		return agent.Run(ctx)
	})

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

	logger.Info().Msg("Host agent stopped")
}

func loadConfig() 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")
	envTags := utils.GetenvTrim("PULSE_TAGS")
	envRunOnce := utils.GetenvTrim("PULSE_ONCE")
	envLogLevel := utils.GetenvTrim("LOG_LEVEL")
	envNoAutoUpdate := utils.GetenvTrim("PULSE_NO_AUTO_UPDATE")

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

	urlFlag := flag.String("url", envURL, "Pulse server URL (e.g. https://pulse.example.com)")
	tokenFlag := flag.String("token", envToken, "Pulse API token (required)")
	intervalFlag := flag.Duration("interval", defaultInterval, "Reporting interval (e.g. 30s, 1m)")
	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")
	runOnceFlag := flag.Bool("once", utils.ParseBool(envRunOnce), "Collect and send a single report, then exit")
	noAutoUpdateFlag := flag.Bool("no-auto-update", utils.ParseBool(envNoAutoUpdate), "Disable automatic updates")
	showVersion := flag.Bool("version", false, "Print the agent version and exit")
	logLevelFlag := flag.String("log-level", defaultLogLevel(envLogLevel), "Log level: debug, info, warn, error")

	var tagFlags multiValue
	flag.Var(&tagFlags, "tag", "Tag to apply to this host (repeatable)")

	flag.Parse()

	if *showVersion {
		fmt.Println(Version)
		os.Exit(0)
	}

	pulseURL := strings.TrimSpace(*urlFlag)
	if pulseURL == "" {
		pulseURL = "http://localhost:7655"
	}

	token := strings.TrimSpace(*tokenFlag)
	if token == "" {
		fmt.Fprintln(os.Stderr, "error: Pulse API token is required (via --token or PULSE_TOKEN)")
		os.Exit(1)
	}

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

	logLevel, err := parseLogLevel(*logLevelFlag)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(1)
	}

	tags := gatherTags(envTags, tagFlags)

	return Config{
		HostConfig: hostagent.Config{
			PulseURL:           pulseURL,
			APIToken:           token,
			Interval:           interval,
			HostnameOverride:   strings.TrimSpace(*hostnameFlag),
			AgentID:            strings.TrimSpace(*agentIDFlag),
			Tags:               tags,
			InsecureSkipVerify: *insecureFlag,
			RunOnce:            *runOnceFlag,
			LogLevel:           logLevel,
		},
		DisableAutoUpdate: *noAutoUpdateFlag,
	}
}

func gatherTags(env string, flags []string) []string {
	tags := make([]string, 0)
	if env != "" {
		for _, tag := range strings.Split(env, ",") {
			tag = strings.TrimSpace(tag)
			if tag != "" {
				tags = append(tags, tag)
			}
		}
	}
	for _, tag := range flags {
		tag = strings.TrimSpace(tag)
		if tag != "" {
			tags = append(tags, tag)
		}
	}
	return tags
}

func parseLogLevel(value string) (zerolog.Level, error) {
	normalized := strings.ToLower(strings.TrimSpace(value))
	if normalized == "" {
		return zerolog.InfoLevel, nil
	}

	level, err := zerolog.ParseLevel(normalized)
	if err != nil {
		return zerolog.InfoLevel, fmt.Errorf("invalid log level %q: must be debug, info, warn, or error", value)
	}
	if level < zerolog.DebugLevel || level > zerolog.ErrorLevel {
		return zerolog.InfoLevel, fmt.Errorf("invalid log level %q: must be debug, info, warn, or error", value)
	}

	return level, nil
}

func defaultLogLevel(envValue string) string {
	if strings.TrimSpace(envValue) == "" {
		return "info"
	}
	return envValue
}
