// Package main initialise et démarre le service logcorrelator.
package main

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

	"github.com/antitbone/ja4/correlator/internal/adapters/inbound/unixsocket"
	"github.com/antitbone/ja4/correlator/internal/adapters/outbound/clickhouse"
	"github.com/antitbone/ja4/correlator/internal/adapters/outbound/file"
	"github.com/antitbone/ja4/correlator/internal/adapters/outbound/multi"
	"github.com/antitbone/ja4/correlator/internal/adapters/outbound/stdout"
	"github.com/antitbone/ja4/correlator/internal/app"
	"github.com/antitbone/ja4/correlator/internal/config"
	"github.com/antitbone/ja4/correlator/internal/domain"
	"github.com/antitbone/ja4/correlator/internal/observability"
	"github.com/antitbone/ja4/correlator/internal/ports"
)

var Version = "dev"

// main configure les sources, les puits et le service de corrélation, puis démarre l'orchestrateur.
func main() {
	configPath := flag.String("config", "config.yml", "path to configuration file")
	version := flag.Bool("version", false, "print version and exit")
	flag.Parse()

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

	// Load configuration
	cfg, err := config.Load(*configPath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error loading configuration: %v\n", err)
		os.Exit(1)
	}

	// Initialize logger with configured level
	logger := observability.NewLoggerWithLevel("logcorrelator", cfg.Log.GetLevel())

	logger.Info(fmt.Sprintf("Starting logcorrelator version %s (log_level=%s)", Version, cfg.Log.GetLevel()))

	// Create sources
	sources := make([]ports.EventSource, 0, len(cfg.Inputs.UnixSockets))
	for _, inputCfg := range cfg.Inputs.UnixSockets {
		source := unixsocket.NewUnixSocketSource(unixsocket.Config{
			Name:              inputCfg.Name,
			Path:              inputCfg.Path,
			SourceType:        inputCfg.SourceType,
			SocketPermissions: inputCfg.GetSocketPermissions(),
		})
		// Set logger for debug logging
		source.SetLogger(logger)
		sources = append(sources, source)
		logger.Info(fmt.Sprintf("Configured input source: name=%s, path=%s, permissions=%o", inputCfg.Name, inputCfg.Path, inputCfg.GetSocketPermissions()))
	}

	// Create sinks
	sinks := make([]ports.CorrelatedLogSink, 0)

	if cfg.Outputs.File.Enabled && cfg.Outputs.File.Path != "" {
		fileSink, err := file.NewFileSink(file.Config{
			Path: cfg.Outputs.File.Path,
		})
		if err != nil {
			logger.Error("Failed to create file sink", err)
			os.Exit(1)
		}
		sinks = append(sinks, fileSink)
		logger.Info(fmt.Sprintf("Configured file sink: path=%s", cfg.Outputs.File.Path))
	}

	if cfg.Outputs.ClickHouse.Enabled {
		clickHouseSink, err := clickhouse.NewClickHouseSink(clickhouse.Config{
			DSN:             cfg.Outputs.ClickHouse.DSN,
			Table:           cfg.Outputs.ClickHouse.Table,
			BatchSize:       cfg.Outputs.ClickHouse.BatchSize,
			FlushIntervalMs: cfg.Outputs.ClickHouse.FlushIntervalMs,
			MaxBufferSize:   cfg.Outputs.ClickHouse.MaxBufferSize,
			DropOnOverflow:  cfg.Outputs.ClickHouse.DropOnOverflow,
			AsyncInsert:     cfg.Outputs.ClickHouse.AsyncInsert,
			TimeoutMs:       cfg.Outputs.ClickHouse.TimeoutMs,
		})
		if err != nil {
			logger.Error("Failed to create ClickHouse sink", err)
			os.Exit(1)
		}
		clickHouseSink.SetLogger(logger)
		sinks = append(sinks, clickHouseSink)
		logger.Info(fmt.Sprintf("Configured ClickHouse sink: table=%s", cfg.Outputs.ClickHouse.Table))
	}

	if cfg.Outputs.Stdout.Enabled {
		stdoutSink := stdout.NewStdoutSink(stdout.Config{Enabled: true})
		sinks = append(sinks, stdoutSink)
		logger.Info("Configured stdout sink (operational logs on stderr)")
	}

	// Create multi-sink wrapper
	multiSink := multi.NewMultiSink(sinks...)

	// Create correlation service
	correlationSvc := domain.NewCorrelationService(domain.CorrelationConfig{
		TimeWindow:          cfg.Correlation.GetTimeWindow(),
		ApacheAlwaysEmit:    cfg.Correlation.GetApacheAlwaysEmit(),
		ApacheEmitDelayMs:   cfg.Correlation.GetApacheEmitDelayMs(),
		NetworkEmit:         false,
		MaxHTTPBufferSize:   cfg.Correlation.GetMaxHTTPBufferSize(),
		MaxNetworkBufferSize: cfg.Correlation.GetMaxNetworkBufferSize(),
		NetworkTTLS:         cfg.Correlation.GetNetworkTTLS(),
		MatchingMode:        cfg.Correlation.GetMatchingMode(),
		ExcludeSourceIPs:    cfg.Correlation.GetExcludeSourceIPs(),
		IncludeDestPorts:    cfg.Correlation.GetIncludeDestPorts(),
	}, &domain.RealTimeProvider{})

	// Set logger for correlation service
	correlationSvc.SetLogger(logger.WithFields(map[string]any{"component": "correlation"}))

	logger.Info(fmt.Sprintf("Correlation service initialized: time_window=%s, emit_orphans=%v, emit_delay_ms=%d",
		cfg.Correlation.GetTimeWindow().String(),
		cfg.Correlation.GetApacheAlwaysEmit(),
		cfg.Correlation.GetApacheEmitDelayMs()))

	// Start metrics server if enabled
	var metricsServer *observability.MetricsServer
	if cfg.Metrics.Enabled {
		addr := cfg.Metrics.Addr
		if addr == "" {
			addr = ":8080" // Default address
		}
		var err error
		metricsServer, err = observability.NewMetricsServer(addr, correlationSvc.GetMetricsSnapshot)
		if err != nil {
			logger.Error("Failed to create metrics server", err)
			os.Exit(1)
		}
		if err := metricsServer.Start(); err != nil {
			logger.Error("Failed to start metrics server", err)
			os.Exit(1)
		}
		logger.Info(fmt.Sprintf("Metrics server started: addr=%s", metricsServer.Addr()))
		logger.Info("Metrics endpoints: /metrics (JSON), /health")
	}

	// Create orchestrator
	orchestrator := app.NewOrchestrator(app.OrchestratorConfig{
		Sources: sources,
		Sink:    multiSink,
	}, correlationSvc)

	// Start the application
	if err := orchestrator.Start(); err != nil {
		logger.Error("Failed to start orchestrator", err)
		os.Exit(1)
	}

	logger.Info("logcorrelator started successfully")

	// Wait for shutdown signal
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)

	for {
		sig := <-sigChan

		if sig == syscall.SIGHUP {
			// Reopen file sinks for log rotation
			logger.Info("SIGHUP received, reopening file sinks...")
			if err := multiSink.Reopen(); err != nil {
				logger.Error("Error reopening file sinks", err)
			} else {
				logger.Info("File sinks reopened successfully")
			}
			continue
		}

		// Shutdown signal received
		logger.Info(fmt.Sprintf("Shutdown signal received: %v", sig))
		break
	}

	// Graceful shutdown
	if err := orchestrator.Stop(); err != nil {
		logger.Error("Error during shutdown", err)
	}

	// Stop metrics server
	if metricsServer != nil {
		shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		if err := metricsServer.Stop(shutdownCtx); err != nil {
			logger.Error("Error stopping metrics server", err)
		}
	}

	logger.Info("logcorrelator stopped")
}
