// Package app contains the main application logic for hdmistat
package app

import (
	"context"
	"log/slog"
	"sync"
	"time"

	"git.eeqj.de/sneak/hdmistat/internal/config"
	"git.eeqj.de/sneak/hdmistat/internal/display"
	"git.eeqj.de/sneak/hdmistat/internal/renderer"
	"git.eeqj.de/sneak/hdmistat/internal/statcollector"
	"go.uber.org/fx"
)

// App is the main application
type App struct {
	display   display.Display
	collector statcollector.Collector
	renderer  *renderer.Renderer
	screens   []renderer.Screen
	log       *slog.Logger

	ctx    context.Context
	cancel context.CancelFunc
	wg     sync.WaitGroup

	currentScreen    int
	rotationInterval time.Duration
	updateInterval   time.Duration
}

// Options contains all dependencies for the App
type Options struct {
	fx.In

	Lifecycle fx.Lifecycle
	Display   display.Display
	Collector statcollector.Collector
	Renderer  *renderer.Renderer
	Logger    *slog.Logger
	Context   context.Context
	Config    *config.Config
}

// NewApp creates a new application instance
func NewApp(opts Options) *App {
	app := &App{
		display:          opts.Display,
		collector:        opts.Collector,
		renderer:         opts.Renderer,
		log:              opts.Logger,
		currentScreen:    0,
		rotationInterval: opts.Config.GetRotationDuration(),
		updateInterval:   opts.Config.GetUpdateDuration(),
	}

	// Initialize screens
	app.screens = []renderer.Screen{
		renderer.NewStatusScreen(),   // New status screen
		renderer.NewOverviewScreen(), // Old overview screen
		renderer.NewProcessScreenCPU(),
		renderer.NewProcessScreenMemory(),
	}

	// Use the injected context, not the lifecycle context
	app.ctx, app.cancel = context.WithCancel(opts.Context)

	opts.Lifecycle.Append(fx.Hook{
		OnStart: func(_ context.Context) error {
			app.Start()
			return nil
		},
		OnStop: func(_ context.Context) error {
			return app.Stop()
		},
	})

	return app
}

// Start begins the application main loop
func (a *App) Start() {
	a.log.Info("starting hdmistat app",
		"screens", len(a.screens),
		"rotation_interval", a.rotationInterval,
		"update_interval", a.updateInterval)

	// Start update loop
	a.wg.Add(1)
	go a.updateLoop()

	// Start rotation loop
	a.wg.Add(1)
	go a.rotationLoop()
}

// Stop stops the application
func (a *App) Stop() error {
	a.log.Info("stopping hdmistat app")

	a.cancel()
	a.wg.Wait()

	// Clear display
	if err := a.display.Clear(); err != nil {
		a.log.Error("clearing display", "error", err)
	}

	// Close display
	if err := a.display.Close(); err != nil {
		a.log.Error("closing display", "error", err)
	}

	return nil
}

// updateLoop continuously updates the current screen
func (a *App) updateLoop() {
	defer a.wg.Done()
	defer func() {
		a.log.Info("updateLoop exiting")
		if r := recover(); r != nil {
			a.log.Error("updateLoop panic", "error", r)
		}
	}()

	a.log.Debug("updateLoop started")

	// DISABLED FOR DEBUGGING - Only render once on screen switch
	// ticker := time.NewTicker(a.updateInterval)
	// defer ticker.Stop()

	// Initial render
	a.renderCurrentScreen()

	// Just wait for context cancellation
	a.log.Debug("updateLoop waiting for context cancellation")
	<-a.ctx.Done()
	a.log.Debug("updateLoop context cancelled")

	/* COMMENTED OUT FOR DEBUGGING
	for {
		select {
		case <-a.ctx.Done():
			return
		case <-ticker.C:
			a.renderCurrentScreen()
		}
	}
	*/
}

// rotationLoop rotates through screens
func (a *App) rotationLoop() {
	defer a.wg.Done()
	defer func() {
		a.log.Info("rotationLoop exiting")
		if r := recover(); r != nil {
			a.log.Error("rotationLoop panic", "error", r)
		}
	}()

	a.log.Debug("rotationLoop started", "interval", a.rotationInterval)

	ticker := time.NewTicker(a.rotationInterval)
	defer ticker.Stop()

	for {
		select {
		case <-a.ctx.Done():
			a.log.Debug("rotationLoop context cancelled")
			return
		case <-ticker.C:
			a.log.Debug("rotationLoop ticker fired")
			a.nextScreen()
		}
	}
}

// renderCurrentScreen renders and displays the current screen
func (a *App) renderCurrentScreen() {
	if len(a.screens) == 0 {
		return
	}

	// Get current screen
	screen := a.screens[a.currentScreen]
	a.log.Debug("rendering screen",
		"index", a.currentScreen,
		"name", screen.Name())

	// Collect system info
	info, err := a.collector.Collect()
	if err != nil {
		a.log.Error("collecting system info", "error", err)
		return
	}

	// Render screen
	img, err := a.renderer.RenderScreen(screen, info)
	if err != nil {
		a.log.Error("rendering screen",
			"screen", screen.Name(),
			"error", err)
		return
	}

	// Display image
	if err := a.display.Show(img); err != nil {
		a.log.Error("displaying image", "error", err)
	}
}

// nextScreen advances to the next screen
func (a *App) nextScreen() {
	if len(a.screens) == 0 {
		return
	}

	a.currentScreen = (a.currentScreen + 1) % len(a.screens)
	a.log.Info("switching screen",
		"index", a.currentScreen,
		"name", a.screens[a.currentScreen].Name())

	// Render the new screen immediately
	a.renderCurrentScreen()
}
