// p4lf - P4 Log Feeder
//
// Tails the Perforce server log (P4LOG) and writes compressed chunk files to
// LogChunksDir for consumption by external automation (e.g. Splunk forwarder).
//
// Usage:
//
//	p4lf -config /p4/common/site/log_feeder/p4lf.cfg
//
// Signals:
//
//	SIGTERM / SIGINT  — flush current chunk, save state, exit cleanly
//	SIGHUP            — reload config file + rotate p4lf's own log
package main

import (
	"flag"
	"fmt"
	"os"
	"os/signal"
	"sync/atomic"
	"syscall"
	"time"

	"workshop.perforce.com/p4lf/internal/chunker"
	"workshop.perforce.com/p4lf/internal/config"
	"workshop.perforce.com/p4lf/internal/logger"
	"workshop.perforce.com/p4lf/internal/tailer"
	"workshop.perforce.com/p4lf/internal/version"
)

func main() {
	configPath := flag.String("config", "/p4/common/site/log_feeder/p4lf.cfg", "path to config file")
	showVersion := flag.Bool("version", false, "print version and exit")
	flag.Parse()

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

	// Bootstrap: we need a logger before the config is fully loaded, so use
	// stderr until the config is ready.
	logsDir := os.Getenv("LOGS")
	if logsDir == "" {
		logsDir = "/tmp"
	}
	logPath := logsDir + "/p4lf.log"

	cfg, err := config.Load(*configPath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "p4lf: failed to load config %q: %v\n", *configPath, err)
		os.Exit(1)
	}

	log, err := logger.New(logPath, cfg.MaxLogSize, cfg.Debug)
	if err != nil {
		fmt.Fprintf(os.Stderr, "p4lf: failed to open log %q: %v\n", logPath, err)
		os.Exit(1)
	}
	defer log.Close()

	log.Infof("%s starting (config: %s)", version.String(), *configPath)
	log.Infof("P4LogFile: %s", cfg.P4LogFile)
	log.Infof("LogChunksDir: %s", cfg.LogChunksDir)
	log.Infof("LogTailDelay: %s", cfg.LogTailDelay)
	log.Infof("StateFile: %s", cfg.StateFile)

	// Wait for P4LOG to exist before proceeding.
	done := make(chan struct{})
	if err := tailer.WaitForFile(cfg.P4LogFile, 10*time.Second, log, done); err != nil {
		log.Errorf("Shutdown before P4LOG appeared: %v", err)
		return
	}

	t, err := tailer.New(cfg, log)
	if err != nil {
		log.Errorf("Failed to create tailer: %v", err)
		os.Exit(1)
	}
	defer func() {
		t.Close()
		if err := t.SaveState(); err != nil {
			log.Errorf("Failed to save state on exit: %v", err)
		}
	}()

	c, err := chunker.New(cfg, log)
	if err != nil {
		log.Errorf("Failed to create chunker: %v", err)
		os.Exit(1)
	}

	// configModTime tracks the config file's mtime for hot-reload detection.
	var configModTime atomic.Value
	if info, err := os.Stat(*configPath); err == nil {
		configModTime.Store(info.ModTime())
	}

	// Signal handling.
	sigCh := make(chan os.Signal, 4)
	signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP)

	ticker := time.NewTicker(cfg.LogTailDelay)
	defer ticker.Stop()

	log.Infof("p4lf running (tick interval: %s)", cfg.LogTailDelay)

	for {
		select {
		case <-ticker.C:
			// Check if config file has changed.
			if info, err := os.Stat(*configPath); err == nil {
				saved, _ := configModTime.Load().(time.Time)
				if info.ModTime().After(saved) {
					log.Infof("Config file changed; reloading")
					if newCfg, err := config.Load(*configPath); err != nil {
						log.Warnf("Failed to reload config: %v (keeping previous config)", err)
					} else {
						cfg = newCfg
						ticker.Reset(cfg.LogTailDelay)
						log.Infof("Config reloaded (LogTailDelay now %s)", cfg.LogTailDelay)
					}
					configModTime.Store(info.ModTime())
				}
			}

			// Check guards; if not met, wait before flushing.
			c.WaitUntilGuardsPassed(10*time.Second, done)

			// Flush accumulated bytes from the tailer.
			chunk, err := t.Flush()
			if err != nil {
				log.Errorf("Flush error: %v", err)
				continue
			}
			if chunk == nil {
				log.Debugf("No new data this tick")
				continue
			}

			if _, err := c.Write(chunk); err != nil {
				log.Errorf("Failed to write chunk: %v", err)
				continue
			}

			if err := t.SaveState(); err != nil {
				log.Warnf("Failed to save state: %v", err)
			}

		case sig := <-sigCh:
			switch sig {
			case syscall.SIGHUP:
				log.Infof("SIGHUP received: reloading config and rotating log")
				if newCfg, err := config.Load(*configPath); err != nil {
					log.Warnf("Failed to reload config on SIGHUP: %v", err)
				} else {
					cfg = newCfg
					ticker.Reset(cfg.LogTailDelay)
					log.Infof("Config reloaded")
				}
				if err := log.Rotate(); err != nil {
					log.Errorf("Log rotation failed: %v", err)
				}

			case syscall.SIGTERM, syscall.SIGINT:
				log.Infof("Signal %v received: flushing and shutting down", sig)
				close(done)

				// Final flush.
				if chunk, err := t.Flush(); err != nil {
					log.Errorf("Final flush error: %v", err)
				} else if chunk != nil {
					if _, err := c.Write(chunk); err != nil {
						log.Errorf("Failed to write final chunk: %v", err)
					}
				}
				log.Infof("p4lf stopped")
				return
			}
		}
	}
}
