package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"log/slog"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"nerd-monitor/internal/stats"
)

func main() {
	var (
		server   = flag.String("server", "", "server URL (required)")
		interval = flag.Duration("interval", 15*time.Second, "reporting interval")
		agentID  = flag.String("id", "", "agent ID (auto-generated if empty)")
	)
	flag.Parse()

	// Set up verbose logging
	slog.SetLogLoggerLevel(slog.LevelDebug)

	if *server == "" {
		slog.Error("Server flag is required")
		os.Exit(1)
	}

	// Normalize server URL (add http:// if missing)
	*server = normalizeURL(*server)

	// Generate or use provided agent ID
	id := *agentID
	if id == "" {
		var err error
		id, err = generateAgentID()
		if err != nil {
			slog.Error("Failed to generate agent ID", "error", err)
			os.Exit(1)
		}
	}

	slog.Info("Starting agent", "id", id)
	slog.Info("Reporting configuration", "server", *server, "interval", *interval)

	// Initialize stats collector
	collector := stats.NewCollector()

	// Setup graceful shutdown
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)

	// Report loop
	ticker := time.NewTicker(*interval)
	defer ticker.Stop()

	// Report immediately on start
	reportStats(id, *server, collector)

	for {
		select {
		case <-ticker.C:
			reportStats(id, *server, collector)
		case <-sigChan:
			slog.Info("Agent shutting down gracefully")
			os.Exit(0)
		}
	}
}

// reportStats collects and reports system statistics to the server.
func reportStats(agentID, serverURL string, collector *stats.Collector) {
	hostname, err := os.Hostname()
	if err != nil {
		hostname = agentID
	}

	stat, err := collector.Collect(hostname)
	if err != nil {
		slog.Error("Error collecting stats", "agentID", agentID, "hostname", hostname, "error", err)
		return
	}

	// Marshal to JSON
	body, err := json.Marshal(stat)
	if err != nil {
		slog.Error("Error marshaling stats", "agentID", agentID, "error", err)
		return
	}

	// Send to server
	reportURL := fmt.Sprintf("%s/api/report?id=%s", serverURL, agentID)
	slog.Debug("Sending stats report", "agentID", agentID, "url", reportURL, "bodySize", len(body))
	resp, err := http.Post(reportURL, "application/json", bytes.NewReader(body))
	if err != nil {
		slog.Error("Error reporting stats", "agentID", agentID, "url", reportURL, "error", err)
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		slog.Error("Server returned non-OK status", "agentID", agentID, "statusCode", resp.StatusCode, "url", reportURL)
		return
	}

	slog.Debug("Stats reported successfully",
		"agentID", agentID,
		"cpu", stat.CPUUsage,
		"ramUsage", formatBytes(stat.RAMUsage),
		"ramTotal", formatBytes(stat.RAMTotal),
		"diskUsage", formatBytes(stat.DiskUsage),
		"diskTotal", formatBytes(stat.DiskTotal),
	)
}

// generateAgentID creates an ID based on hostname.
func generateAgentID() (string, error) {
	hostname, err := os.Hostname()
	if err != nil {
		return "", fmt.Errorf("failed to get hostname: %w", err)
	}
	return hostname, nil
}

// normalizeURL ensures the URL has a valid scheme and port.
func normalizeURL(server string) string {
	// If no scheme, add http://
	if !strings.Contains(server, "://") {
		server = "http://" + server
	}

	// Parse to check if port is specified
	u, err := url.Parse(server)
	if err != nil {
		return server
	}

	// If no port specified, add default port 8080
	if u.Port() == "" {
		return u.Scheme + "://" + u.Hostname() + ":8080"
	}

	return server
}

// formatBytes converts bytes to human-readable format.
func formatBytes(bytes uint64) string {
	const (
		kb = 1024
		mb = kb * 1024
		gb = mb * 1024
	)

	switch {
	case bytes >= gb:
		return fmt.Sprintf("%.1f GB", float64(bytes)/float64(gb))
	case bytes >= mb:
		return fmt.Sprintf("%.1f MB", float64(bytes)/float64(mb))
	case bytes >= kb:
		return fmt.Sprintf("%.1f KB", float64(bytes)/float64(kb))
	default:
		return fmt.Sprintf("%d B", bytes)
	}
}
