package hdmistat

import (
	"context"
	"fmt"
	"log/slog"
	"os"
	"os/signal"
	"runtime"
	"syscall"

	"git.eeqj.de/sneak/hdmistat/internal/app"
	"git.eeqj.de/sneak/hdmistat/internal/config"
	"git.eeqj.de/sneak/hdmistat/internal/display"
	"git.eeqj.de/sneak/hdmistat/internal/font"
	"git.eeqj.de/sneak/hdmistat/internal/renderer"
	"git.eeqj.de/sneak/hdmistat/internal/statcollector"
	"github.com/golang/freetype/truetype"
	"github.com/spf13/cobra"
	"go.uber.org/fx"
)

// newDaemonCmd creates the daemon command
func (c *CLI) newDaemonCmd() *cobra.Command {
	var framebufferDevice string
	var configFile string

	cmd := &cobra.Command{
		Use:   "daemon",
		Short: "Run hdmistat as a daemon",
		Long:  `Run hdmistat as a daemon that displays system statistics on the framebuffer.`,
		Run: func(cmd *cobra.Command, _ []string) {
			c.runDaemon(cmd, framebufferDevice, configFile)
		},
	}

	cmd.Flags().StringVarP(&framebufferDevice, "framebuffer", "f", "/dev/fb0", "Framebuffer device to use")
	cmd.Flags().StringVarP(&configFile, "config", "c", "/etc/hdmistat/config.yaml", "Configuration file path")

	return cmd
}

func (c *CLI) runDaemon(cmd *cobra.Command, framebufferDevice, _ string) {
	// Set up signal handling
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

	go func() {
		<-sigChan
		c.log.Info("received shutdown signal")
		cancel()
	}()

	// Set up debug signal handler for SIGUSR1
	debugChan := make(chan os.Signal, 1)
	signal.Notify(debugChan, syscall.SIGUSR1)

	go func() {
		for range debugChan {
			c.log.Info("received SIGUSR1, dumping goroutines")
			// Dump all goroutine stack traces to stderr
			const megabyte = 1 << 20
			buf := make([]byte, megabyte) // 1MB buffer
			stackSize := runtime.Stack(buf, true)
			fmt.Fprintf(os.Stderr, "\n=== GOROUTINE DUMP ===\n%s\n=== END GOROUTINE DUMP ===\n", buf[:stackSize])
		}
	}()

	// Load configuration
	cfg, err := config.Load(ctx)
	if err != nil {
		c.log.Error("loading config", "error", err)
		os.Exit(1)
	}

	// Override with command line flags if provided
	if cmd.Flags().Changed("framebuffer") {
		cfg.FramebufferDevice = framebufferDevice
	}

	// Update logger level - use stderr for serial console visibility
	c.log = slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
		Level: cfg.GetLogLevel(),
	}))

	c.log.Info("hdmistat daemon starting", "log_level", cfg.LogLevel, "framebuffer", cfg.FramebufferDevice)

	// Create fx application
	fxApp := fx.New(
		fx.Provide(
			func() *slog.Logger { return c.log },
			func() context.Context { return ctx },
			func() *config.Config { return cfg },

			// Provide font
			func() (*truetype.Font, error) {
				return font.LoadIBMPlexMono()
			},

			// Provide display
			func(cfg *config.Config, logger *slog.Logger) (display.Display, error) {
				return display.NewFramebufferDisplay(cfg.FramebufferDevice, logger)
			},

			// Provide collector
			func(lc fx.Lifecycle, logger *slog.Logger) statcollector.Collector {
				collector := statcollector.NewSystemCollector(logger)

				lc.Append(fx.Hook{
					OnStop: func(_ context.Context) error {
						collector.Stop()
						return nil
					},
				})

				return collector
			},

			// Provide renderer
			func(font *truetype.Font, logger *slog.Logger, disp display.Display) *renderer.Renderer {
				r := renderer.NewRenderer(font, logger)
				// Get actual framebuffer resolution
				if fbDisp, ok := disp.(*display.FramebufferDisplay); ok && fbDisp != nil {
					width := int(fbDisp.GetWidth())
					height := int(fbDisp.GetHeight())
					logger.Info("using framebuffer resolution", "width", width, "height", height)
					r.SetResolution(width, height)
				}

				return r
			},

			// Provide app
			app.NewApp,
		),

		fx.Invoke(func(_ *app.App) {
			// App will be started by fx lifecycle
		}),
	)

	// Start the application
	fxApp.Run()
}
