package game

import (
	"time"

	"github.com/hajimehoshi/ebiten/v2"
	"github.com/hajimehoshi/ebiten/v2/inpututil"

	"github.com/atridad/LilGuy/internal/maps"
	"github.com/atridad/LilGuy/internal/save"
	"github.com/atridad/LilGuy/internal/screens"
	"github.com/atridad/LilGuy/internal/ui/menu"
)

const (
	ScreenWidth  = 960
	ScreenHeight = 540
	TargetTPS    = 60
	WindowTitle  = "Lil Guy"
)

// Game states
type gameState int

const (
	stateSplash gameState = iota
	stateTitle
	statePlaying
	statePaused
)

// FPS cap options
type FPSCap int

const (
	FPSCap60 FPSCap = iota
	FPSCap120
	FPSCapUncapped
	fpsCapCount
)

func (f FPSCap) TPS() int {
	switch f {
	case FPSCap60:
		return 60
	case FPSCap120:
		return 120
	case FPSCapUncapped:
		return -1
	default:
		return 60
	}
}

func (f FPSCap) String() string {
	switch f {
	case FPSCap60:
		return "60 FPS"
	case FPSCap120:
		return "120 FPS"
	case FPSCapUncapped:
		return "Uncapped"
	default:
		return "60 FPS"
	}
}

func (f *FPSCap) Cycle() {
	*f = (*f + 1) % fpsCapCount
}

// Player input
type controls struct {
	Left   bool
	Right  bool
	Jump   bool
	Sprint bool
	Shoot  bool
}

func readControls() controls {
	return controls{
		Left:   ebiten.IsKeyPressed(ebiten.KeyArrowLeft) || ebiten.IsKeyPressed(ebiten.KeyA),
		Right:  ebiten.IsKeyPressed(ebiten.KeyArrowRight) || ebiten.IsKeyPressed(ebiten.KeyD),
		Jump:   ebiten.IsKeyPressed(ebiten.KeyArrowUp) || ebiten.IsKeyPressed(ebiten.KeyW) || ebiten.IsKeyPressed(ebiten.KeySpace),
		Sprint: ebiten.IsKeyPressed(ebiten.KeyShift),
		Shoot:  inpututil.IsKeyJustPressed(ebiten.KeyK),
	}
}

type Game struct {
	state *state
}

// Main game state
type state struct {
	gameState gameState
	lastTick  time.Time

	splashScreen   *screens.SplashScreen
	titleScreen    *screens.TitleScreen
	gameplayScreen *screens.GameplayScreen
	pauseMenu      *menu.PauseMenu

	fpsEnabled       bool
	fpsCap           FPSCap
	portalVisibility bool
	raycastEnabled   bool
	raycastDebugMode bool
	saveManager      *save.Manager

	lastAutoSave     time.Time
	autoSaveInterval time.Duration
}

func New() *Game {
	return &Game{state: newState()}
}

func newState() *state {
	now := time.Now()
	s := &state{
		gameState:        stateSplash,
		lastTick:         now,
		fpsEnabled:       false,
		fpsCap:           FPSCap60,
		portalVisibility: false,
		raycastEnabled:   true,
		raycastDebugMode: false,
		lastAutoSave:     now,
		autoSaveInterval: 30 * time.Second,
	}

	saveManager, err := save.NewManager()
	if err != nil {
		saveManager = nil
	}
	s.saveManager = saveManager

	if saveManager != nil {
		if settings, err := saveManager.LoadSettings(); err == nil {
			s.fpsEnabled = settings.FPSMonitor
			s.portalVisibility = settings.PortalVisibility
			switch settings.FPSCap {
			case "60":
				s.fpsCap = FPSCap60
			case "120":
				s.fpsCap = FPSCap120
			case "uncapped":
				s.fpsCap = FPSCapUncapped
			default:
				s.fpsCap = FPSCap60
			}
		}
	}

	// Initialize Map Manager
	mapManager := maps.NewManager()
	plains, desert := maps.CreateDefaultMaps(float64(ScreenWidth), float64(ScreenHeight))

	// Bake maps for performance
	plains.Bake()
	desert.Bake()

	mapManager.RegisterMap(plains)
	mapManager.RegisterMap(desert)
	mapManager.SetCurrentMap("plains")

	// Initialize screens
	s.splashScreen = screens.NewSplashScreen()
	s.titleScreen = screens.NewTitleScreen()
	s.gameplayScreen = screens.NewGameplayScreen(ScreenWidth, ScreenHeight, mapManager, &s.fpsEnabled, &s.portalVisibility, &s.raycastEnabled, &s.raycastDebugMode)
	s.pauseMenu = menu.NewPauseMenu()

	// Wire up settings references
	s.titleScreen.SetFPSMonitor(&s.fpsEnabled)
	s.titleScreen.SetFPSCap(&s.fpsCap)
	s.titleScreen.SetPortalVisibility(&s.portalVisibility)
	s.titleScreen.SetRaycastSettings(&s.raycastEnabled, &s.raycastDebugMode)
	s.pauseMenu.SetFPSMonitor(&s.fpsEnabled)
	s.pauseMenu.SetFPSCap(&s.fpsCap)
	s.pauseMenu.SetPortalVisibility(&s.portalVisibility)
	s.pauseMenu.SetRaycastSettings(&s.raycastEnabled, &s.raycastDebugMode)

	if saveManager != nil {
		s.titleScreen.SetHasSaveGame(saveManager.HasSavedGame())
	}

	ebiten.SetTPS(s.fpsCap.TPS())

	// Create initial save file
	if saveManager != nil {
		settings := &save.Settings{
			FPSMonitor:       s.fpsEnabled,
			FPSCap:           s.fpCapToStringHelper(s.fpsCap),
			PortalVisibility: s.portalVisibility,
		}
		saveManager.SaveSettings(settings)
	}

	return s
}

func (s *state) fpCapToStringHelper(cap FPSCap) string {
	switch cap {
	case FPSCap60:
		return "60"
	case FPSCap120:
		return "120"
	case FPSCapUncapped:
		return "uncapped"
	default:
		return "60"
	}
}

func (g *Game) Update() error {
	prevFPSEnabled := g.state.fpsEnabled
	prevFPSCap := g.state.fpsCap
	prevPortalVisibility := g.state.portalVisibility

	currentTPS := g.state.fpsCap.TPS()
	if currentTPS < 0 {
		ebiten.SetTPS(ebiten.SyncWithFPS)
	} else {
		ebiten.SetTPS(currentTPS)
	}

	// Update current screen
	var err error
	switch g.state.gameState {
	case stateSplash:
		err = g.updateSplash()
	case stateTitle:
		err = g.updateTitle()
	case statePlaying:
		err = g.updatePlaying()
	case statePaused:
		err = g.updatePaused()
	}

	if prevFPSEnabled != g.state.fpsEnabled || prevFPSCap != g.state.fpsCap || prevPortalVisibility != g.state.portalVisibility {
		g.saveSettings()
	}

	return err
}

// Screen update handlers

func (g *Game) updateSplash() error {
	if g.state.splashScreen.Update() {
		g.state.gameState = stateTitle
	}
	return nil
}

func (g *Game) updateTitle() error {
	if selectedOption := g.state.titleScreen.Update(); selectedOption != nil {
		switch *selectedOption {
		case screens.TitleOptionContinue:
			if g.state.saveManager != nil {
				if gameState, err := g.state.saveManager.LoadGameState(); err == nil && gameState != nil {
					g.state.gameplayScreen.LoadState(gameState)
					g.state.gameState = statePlaying
					g.state.lastTick = time.Now()
				}
			}
		case screens.TitleOptionNewGame:
			g.state.gameState = statePlaying
			g.state.gameplayScreen.Reset()
			g.state.lastTick = time.Now()
			if g.state.saveManager != nil {
				g.state.saveManager.DeleteGameState()
			}
		case screens.TitleOptionSettings:
		case screens.TitleOptionQuit:
			return ebiten.Termination
		}
	}
	return nil
}

func (g *Game) updatePlaying() error {
	if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
		g.state.gameState = statePaused
		g.state.pauseMenu.Reset()
		return nil
	}

	// Toggle raycasting with L key
	if inpututil.IsKeyJustPressed(ebiten.KeyL) {
		g.state.gameplayScreen.ToggleRaycast()
	}

	// Toggle raycasting debug mode with B key
	if inpututil.IsKeyJustPressed(ebiten.KeyB) {
		g.state.gameplayScreen.ToggleRaycastDebug()
	}

	now := time.Now()
	delta := now.Sub(g.state.lastTick)
	g.state.lastTick = now

	maxDelta := 100 * time.Millisecond
	if delta > maxDelta {
		delta = maxDelta
	}

	minDelta := time.Microsecond
	if delta < minDelta {
		delta = minDelta
	}

	input := readControls()
	g.state.gameplayScreen.Update(screens.GameplayInput{
		Left:   input.Left,
		Right:  input.Right,
		Jump:   input.Jump,
		Sprint: input.Sprint,
		Shoot:  input.Shoot,
	}, delta)

	if now.Sub(g.state.lastAutoSave) >= g.state.autoSaveInterval {
		g.saveGame()
		g.state.lastAutoSave = now
	}

	return nil
}

func (g *Game) updatePaused() error {
	if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
		g.state.gameState = statePlaying
		g.state.lastTick = time.Now()
		return nil
	}

	if selectedOption := g.state.pauseMenu.Update(); selectedOption != nil {
		switch *selectedOption {
		case menu.OptionResume:
			g.state.gameState = statePlaying
			g.state.lastTick = time.Now()
		case menu.OptionSave:
			g.saveGame()
			g.state.gameplayScreen.ShowSaveNotification()
			g.state.gameState = statePlaying
			g.state.lastTick = time.Now()
		case menu.OptionMainMenu:
			g.saveGame()
			g.state.gameState = stateTitle
			if g.state.saveManager != nil {
				g.state.titleScreen.SetHasSaveGame(g.state.saveManager.HasSavedGame())
			}
			g.state.titleScreen.Reset()
		case menu.OptionQuit:
			g.saveGame()
			return ebiten.Termination
		}
	}

	return nil
}

// Save/load operations

func (g *Game) saveGame() {
	if g.state.saveManager == nil {
		return
	}

	if g.state.gameState == statePlaying || g.state.gameState == statePaused {
		gameState := g.state.gameplayScreen.SaveState()
		g.state.saveManager.SaveGameState(gameState)
	}

	g.saveSettings()
}

func (g *Game) saveSettings() {
	if g.state.saveManager == nil {
		return
	}

	settings := &save.Settings{
		FPSMonitor:       g.state.fpsEnabled,
		FPSCap:           g.fpCapToString(g.state.fpsCap),
		PortalVisibility: g.state.portalVisibility,
	}
	g.state.saveManager.SaveSettings(settings)
}

func (g *Game) fpCapToString(cap FPSCap) string {
	switch cap {
	case FPSCap60:
		return "60"
	case FPSCap120:
		return "120"
	case FPSCapUncapped:
		return "uncapped"
	default:
		return "60"
	}
}

// Rendering

func (g *Game) Draw(screen *ebiten.Image) {
	switch g.state.gameState {
	case stateSplash:
		g.state.splashScreen.Draw(screen, ScreenWidth, ScreenHeight)
	case stateTitle:
		g.state.titleScreen.Draw(screen, ScreenWidth, ScreenHeight)
	case statePlaying:
		g.state.gameplayScreen.Draw(screen)
	case statePaused:
		g.state.gameplayScreen.Draw(screen)
		g.state.pauseMenu.Draw(screen, ScreenWidth, ScreenHeight)
	}
}

func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
	return ScreenWidth, ScreenHeight
}
