// Package main provides the entry point for ja4sentinel
package main

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

	"github.com/coreos/go-systemd/v22/daemon"
	"github.com/antitbone/ja4/sentinel/api"
	"github.com/antitbone/ja4/sentinel/internal/capture"
	"github.com/antitbone/ja4/sentinel/internal/config"
	"github.com/antitbone/ja4/sentinel/internal/fingerprint"
	"github.com/antitbone/ja4/sentinel/internal/logging"
	"github.com/antitbone/ja4/sentinel/internal/output"
	"github.com/antitbone/ja4/sentinel/internal/tlsparse"
)

var (
	// Version information (set via ldflags)
	Version   = "1.1.15"
	BuildTime = "unknown"
	GitCommit = "unknown"
)

func main() {
	// Parse command-line flags
	configPath := flag.String("config", "", "Path to configuration file (YAML)")
	version := flag.Bool("version", false, "Show version information")
	flag.Parse()

	if *version {
		fmt.Printf("ja4sentinel version %s (built %s, commit %s)\n", Version, BuildTime, GitCommit)
		os.Exit(0)
	}

	// Load configuration
	cfgLoader := config.NewLoader(*configPath)
	appConfig, err := cfgLoader.Load()
	if err != nil {
		// Create logger with default level for error reporting
		loggerFactory := &logging.LoggerFactory{}
		appLogger := loggerFactory.NewDefaultLogger()
		appLogger.Error("main", "Failed to load configuration", map[string]string{
			"error": err.Error(),
		})
		os.Exit(1)
	}

	// Create logger factory with configured log level
	loggerFactory := &logging.LoggerFactory{}
	appLogger := loggerFactory.NewLogger(appConfig.Core.LogLevel)

	appLogger.Info("main", "Starting ja4sentinel", map[string]string{
		"version":    Version,
		"build_time": BuildTime,
		"git_commit": GitCommit,
	})

	appLogger.Info("main", "Configuration loaded", map[string]string{
		"interface":    appConfig.Core.Interface,
		"listen_ports": formatPorts(appConfig.Core.ListenPorts),
		"log_level":    appConfig.Core.LogLevel,
	})

	// Create context with cancellation for graceful shutdown
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Signal readiness to systemd
	if _, err := daemon.SdNotify(false, daemon.SdNotifyReady); err != nil {
		appLogger.Warn("main", "Failed to send READY notification to systemd", map[string]string{
			"error": err.Error(),
		})
	}

	// Start watchdog goroutine if enabled
	watchdogInterval, err := daemon.SdWatchdogEnabled(false)
	if err != nil {
		appLogger.Warn("main", "Failed to check watchdog status", map[string]string{
			"error": err.Error(),
		})
	}
	if watchdogInterval > 0 {
		appLogger.Info("main", "systemd watchdog enabled", map[string]string{
			"interval": watchdogInterval.String(),
		})
		go func() {
			ticker := time.NewTicker(watchdogInterval / 2)
			defer ticker.Stop()
			for {
				select {
				case <-ticker.C:
					if _, err := daemon.SdNotify(false, daemon.SdNotifyWatchdog); err != nil {
						appLogger.Warn("main", "Failed to send WATCHDOG notification", map[string]string{
							"error": err.Error(),
						})
					}
				case <-ctx.Done():
					return
				}
			}
		}()
	}

	// Setup signal handling for shutdown and log rotation
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)

	// Create pipeline components
	captureEngine := capture.New()
	parser := tlsparse.NewParserWithTimeoutAndFilter(
		time.Duration(appConfig.Core.FlowTimeoutSec)*time.Second,
		appConfig.Core.ExcludeSourceIPs,
	)
	fingerprintEngine := fingerprint.NewEngine()

	// Log exclusion configuration with debug details
	if len(appConfig.Core.ExcludeSourceIPs) > 0 {
		appLogger.Info("main", "Source IP exclusion enabled", map[string]string{
			"exclude_count": fmt.Sprintf("%d", len(appConfig.Core.ExcludeSourceIPs)),
			"exclude_ips":   strings.Join(appConfig.Core.ExcludeSourceIPs, ", "),
		})
		appLogger.Debug("tlsparse", "IP filter configured", map[string]string{
			"filter_entries": strings.Join(appConfig.Core.ExcludeSourceIPs, ", "),
		})
	} else {
		appLogger.Debug("tlsparse", "IP filter disabled (no exclusions configured)", nil)
	}

	// Log filter stats at startup (debug mode)
	filteredCount, hasFilter := parser.GetFilterStats()
	if hasFilter {
		appLogger.Debug("tlsparse", "IP filter initialized", map[string]string{
			"filtered_packets": fmt.Sprintf("%d", filteredCount),
		})
	}
	
	// Create output builder with error callback for socket connection errors
	outputBuilder := output.NewBuilder().WithErrorCallback(func(socketPath string, err error, attempt int) {
		appLogger.Error("output", "UNIX socket connection failed", map[string]string{
			"socket_path": socketPath,
			"error":       err.Error(),
			"attempt":     fmt.Sprintf("%d", attempt),
		})
	})

	outputWriter, err := outputBuilder.NewFromConfig(appConfig)
	if err != nil {
		appLogger.Error("main", "Failed to create output writer", map[string]string{
			"error": err.Error(),
		})
		os.Exit(1)
	}

	// Create channel for raw packets (configurable buffer size)
	bufferSize := appConfig.Core.PacketBufferSize
	if bufferSize <= 0 {
		bufferSize = 1000 // Default fallback
	}
	packetChan := make(chan api.RawPacket, bufferSize)

	// Start capture goroutine
	captureErrChan := make(chan error, 1)
	go func() {
		appLogger.Info("capture", "Starting packet capture", map[string]string{
			"interface": appConfig.Core.Interface,
		})
		err := captureEngine.Run(appConfig.Core, packetChan)
		close(packetChan) // Close channel to signal packet processor to shut down
		captureErrChan <- err
	}()

	// Log capture diagnostics after a short delay to allow initialization
	go func() {
		time.Sleep(100 * time.Millisecond)
		ifName, localIPs, bpfFilter, linkType := captureEngine.GetDiagnostics()
		appLogger.Debug("capture", "Capture initialized", map[string]string{
			"interface":   ifName,
			"link_type":   fmt.Sprintf("%d", linkType),
			"local_ips":   strings.Join(localIPs, ", "),
			"bpf_filter":  bpfFilter,
		})
	}()

	// Process packets
	go func() {
		for {
			select {
			case <-ctx.Done():
				appLogger.Info("main", "Packet processor shutting down", nil)
				return
			case pkt, ok := <-packetChan:
				if !ok {
					return
				}

				// Parse TLS ClientHello
				clientHello, err := parser.Process(pkt)
				if err != nil {
					appLogger.Warn("tlsparse", "Failed to parse TLS ClientHello", map[string]string{
						"error":        err.Error(),
						"packet_len":   fmt.Sprintf("%d", len(pkt.Data)),
						"link_type":    fmt.Sprintf("%d", pkt.LinkType),
						"timestamp":    fmt.Sprintf("%d", pkt.Timestamp),
					})
					continue
				}
				if clientHello == nil {
					continue // Not a TLS ClientHello packet
				}

				appLogger.Debug("tlsparse", "ClientHello extracted", map[string]string{
					"src_ip":   clientHello.SrcIP,
					"src_port": fmt.Sprintf("%d", clientHello.SrcPort),
					"dst_ip":   clientHello.DstIP,
					"dst_port": fmt.Sprintf("%d", clientHello.DstPort),
				})

				// Generate fingerprints
				fingerprints, err := fingerprintEngine.FromClientHello(*clientHello)
				if err != nil {
					appLogger.Warn("fingerprint", "Failed to generate fingerprints", map[string]string{
						"error":       err.Error(),
						"src_ip":      clientHello.SrcIP,
						"src_port":    fmt.Sprintf("%d", clientHello.SrcPort),
						"dst_ip":      clientHello.DstIP,
						"dst_port":    fmt.Sprintf("%d", clientHello.DstPort),
						"conn_id":     clientHello.ConnID,
						"payload_len": fmt.Sprintf("%d", len(clientHello.Payload)),
						"sni":         clientHello.SNI,
						"tls_version": clientHello.TLSVersion,
						"alpn":        clientHello.ALPN,
					})
					continue
				}

				appLogger.Debug("fingerprint", "Fingerprints generated", map[string]string{
					"src_ip": clientHello.SrcIP,
					"ja4":    fingerprints.JA4,
				})

				// Create log record
				logRecord := api.NewLogRecord(*clientHello, fingerprints)

				// Write output
				if err := outputWriter.Write(logRecord); err != nil {
					appLogger.Error("output", "Failed to write log record", map[string]string{
						"error": err.Error(),
					})
				}
			}
		}
	}()

	// Wait for shutdown signal or capture error
	for {
		select {
		case sig := <-sigChan:
			switch sig {
			case syscall.SIGHUP:
				// Handle log rotation - reopen output files
				appLogger.Info("main", "Received SIGHUP, reopening log files", nil)
				if mw, ok := outputWriter.(api.Reopenable); ok {
					if err := mw.Reopen(); err != nil {
						appLogger.Error("main", "Failed to reopen log files", map[string]string{
							"error": err.Error(),
						})
					} else {
						appLogger.Info("main", "Log files reopened successfully", nil)
					}
				} else {
					appLogger.Warn("main", "Output writer does not support log rotation", nil)
				}
			case syscall.SIGINT, syscall.SIGTERM:
				appLogger.Info("main", "Received shutdown signal", map[string]string{
					"signal": sig.String(),
				})
				goto shutdown
			}
		case err := <-captureErrChan:
			if err != nil {
				appLogger.Error("capture", "Capture engine failed", map[string]string{
					"error": err.Error(),
				})
			}
			goto shutdown
		}
	}

shutdown:
	// Graceful shutdown
	appLogger.Info("main", "Shutting down...", nil)

	// Signal stopping to systemd
	if _, err := daemon.SdNotify(false, daemon.SdNotifyStopping); err != nil {
		appLogger.Warn("main", "Failed to send STOPPING notification to systemd", map[string]string{
			"error": err.Error(),
		})
	}

	cancel()

	// Close components
	if err := captureEngine.Close(); err != nil {
		appLogger.Error("main", "Failed to close capture engine", map[string]string{
			"error": err.Error(),
		})
	}

	if err := parser.Close(); err != nil {
		appLogger.Error("main", "Failed to close parser", map[string]string{
			"error": err.Error(),
		})
	}

	// Log final filter stats
	filteredCount, hasFilter = parser.GetFilterStats()
	if hasFilter {
		appLogger.Info("tlsparse", "IP filter statistics", map[string]string{
			"total_filtered_packets": fmt.Sprintf("%d", filteredCount),
		})
	}

	if mw, ok := outputWriter.(interface{ CloseAll() error }); ok {
		if err := mw.CloseAll(); err != nil {
			appLogger.Error("main", "Failed to close output writers", map[string]string{
				"error": err.Error(),
			})
		}
	} else if closer, ok := outputWriter.(interface{ Close() error }); ok {
		if err := closer.Close(); err != nil {
			appLogger.Error("main", "Failed to close output writer", map[string]string{
				"error": err.Error(),
			})
		}
	}

	appLogger.Info("main", "ja4sentinel stopped", nil)
}

// formatPorts formats a slice of ports as a comma-separated string
func formatPorts(ports []uint16) string {
	if len(ports) == 0 {
		return ""
	}
	result := fmt.Sprintf("%d", ports[0])
	for _, port := range ports[1:] {
		result += fmt.Sprintf(",%d", port)
	}
	return result
}
