package screens

import (
	"fmt"
	"image/color"
	"math"
	"time"

	"github.com/hajimehoshi/ebiten/v2"
	"github.com/hajimehoshi/ebiten/v2/ebitenutil"
	"github.com/hajimehoshi/ebiten/v2/vector"

	"github.com/atridad/LilGuy/internal/config"
	"github.com/atridad/LilGuy/internal/hero"
	"github.com/atridad/LilGuy/internal/maps"
	"github.com/atridad/LilGuy/internal/portal"
	"github.com/atridad/LilGuy/internal/projectile"
	"github.com/atridad/LilGuy/internal/raycast"
	"github.com/atridad/LilGuy/internal/save"
	"github.com/atridad/LilGuy/internal/status"
	"github.com/atridad/LilGuy/internal/ui/hud"
	"github.com/atridad/LilGuy/internal/world"
)

type GameplayInput struct {
	Left   bool
	Right  bool
	Jump   bool
	Sprint bool
	Shoot  bool
}

type GameplayScreen struct {
	hero           *hero.Hero
	hud            hud.Overlay
	world          *world.World
	projectiles    *projectile.Manager
	portals        *portal.Manager
	mapManager     *maps.Manager
	bounds         hero.Bounds
	lastTick       time.Time
	gameStartTime  time.Time
	totalPlayTime  time.Duration
	fpsEnabled     *bool
	fpsFrames      int
	fpsAccumulator time.Duration
	fpsValue       float64

	saveNotificationTimer time.Duration
	showSaveNotification  bool

	portalVisibility *bool

	// Raycasting system
	raycastSystem    *raycast.System
	raycastEnabled   *bool
	raycastDebugMode *bool
}

func NewGameplayScreen(screenWidth, screenHeight int, mapManager *maps.Manager, fpsEnabled *bool, portalVisibility *bool, raycastEnabled *bool, raycastDebugMode *bool) *GameplayScreen {
	cfg := config.Default()

	// Ensure we have a current map
	if mapManager.CurrentMap() == nil {
		// Fallback or error later
	}
	currentMap := mapManager.CurrentMap()

	portalMgr := portal.NewManager()
	for _, p := range currentMap.Portals {
		portalMgr.AddPortal(p)
	}

	if portalVisibility != nil && *portalVisibility {
		for _, p := range portalMgr.Portals {
			p.Visible = true
		}
	}

	gs := &GameplayScreen{
		hero: hero.New(hero.Config{
			StartX:       cfg.Hero.StartX,
			StartY:       cfg.Hero.StartY,
			Radius:       cfg.Hero.Radius,
			Speed:        cfg.Hero.Speed,
			Color:        cfg.Hero.Color,
			MaxStamina:   cfg.Hero.MaxStamina,
			StaminaDrain: cfg.Hero.StaminaDrain,
			StaminaRegen: cfg.Hero.StaminaRegen,
		}),
		hud: hud.Overlay{
			X:          cfg.HUD.X,
			Y:          cfg.HUD.Y,
			Color:      color.White,
			ScreenName: currentMap.DisplayName,
		},
		world:       currentMap.World,
		projectiles: projectile.NewManager(),
		portals:     portalMgr,
		mapManager:  mapManager,
		bounds: hero.Bounds{
			Width:  float64(screenWidth),
			Height: float64(screenHeight),
			Ground: float64(screenHeight) - config.GroundHeight,
		},
		lastTick:         time.Now(),
		gameStartTime:    time.Now(),
		fpsEnabled:       fpsEnabled,
		portalVisibility: portalVisibility,
		raycastSystem:    raycast.NewSystem(screenWidth, screenHeight),
		raycastEnabled:   raycastEnabled,
		raycastDebugMode: raycastDebugMode,
	}

	gs.portals.OnTransition = gs.handlePortalTransition

	return gs
}

func (g *GameplayScreen) Update(input GameplayInput, delta time.Duration) {
	if delta > 100*time.Millisecond {
		delta = 100 * time.Millisecond
	}

	dt := delta.Seconds()

	g.hero.Update(hero.Input{
		Left:   input.Left,
		Right:  input.Right,
		Jump:   input.Jump,
		Sprint: input.Sprint,
	}, dt, g.bounds, g.world)

	if input.Shoot {
		direction := 1.0
		if g.hero.GetDirection() == hero.DirLeft {
			direction = -1.0
		}
		g.projectiles.Shoot(g.hero.X, g.hero.Y-20, direction, g.hero.ProjectileConfig)
	}

	g.projectiles.Update(dt, g.bounds.Width, g.bounds.Height)
	g.portals.Update(dt)

	// Update raycasting lights based on current map
	g.updateRaycastLights()

	// check for portal collisions
	g.checkPortalCollision()

	g.totalPlayTime += delta

	g.trackFPS(delta)

	// update save notification timer
	if g.showSaveNotification {
		g.saveNotificationTimer -= delta
		if g.saveNotificationTimer <= 0 {
			g.showSaveNotification = false
		}
	}
}

func (g *GameplayScreen) updateRaycastLights() {
	g.raycastSystem.ClearLights()

	currentMap := g.mapManager.CurrentMap()
	if currentMap == nil {
		return
	}

	for _, lightSrc := range currentMap.Lighting.Sources {
		g.raycastSystem.SetShadowIntensity(lightSrc.ShadowDarkness)
		g.raycastSystem.AddLight(&raycast.LightSource{
			X:         lightSrc.X,
			Y:         lightSrc.Y,
			Radius:    lightSrc.Radius,
			Color:     lightSrc.Color,
			Intensity: lightSrc.Intensity,
		})
	}
}

// portal collision detection
func (g *GameplayScreen) checkPortalCollision() {
	heroRadius := g.hero.Radius
	heroX := g.hero.X - heroRadius
	heroY := g.hero.Y - heroRadius
	heroWidth := heroRadius * 2
	heroHeight := heroRadius * 2

	if p := g.portals.CheckCollision(heroX, heroY, heroWidth, heroHeight); p != nil {
		g.portals.TriggerTransition(p, g.hero.X, g.hero.Y)
	}
}

func (g *GameplayScreen) handlePortalTransition(event portal.TransitionEvent) {
	destMap, destPortal := g.mapManager.ResolveTransition(event)
	if destMap == nil {
		return
	}

	// Switch to destination map
	g.mapManager.SetCurrentMap(destMap.ID)
	g.world = destMap.World

	// Clear and reload portals for new map
	g.portals.Clear()
	for _, p := range destMap.Portals {
		g.portals.AddPortal(p)
	}

	// set portal visibility
	if g.portalVisibility != nil {
		visible := *g.portalVisibility
		for _, p := range g.portals.Portals {
			p.Visible = visible
		}
	}

	// Find destination portal and position hero
	if destPortal != nil {
		// Position hero based on which side they're entering from
		switch destPortal.Side {
		case portal.SideLeft:
			g.hero.X = destPortal.X + destPortal.Width + g.hero.Radius + 10
			g.hero.Y = event.HeroY
		case portal.SideRight:
			g.hero.X = destPortal.X - g.hero.Radius - 10
			g.hero.Y = event.HeroY
		case portal.SideTop:
			g.hero.X = event.HeroX
			g.hero.Y = destPortal.Y + destPortal.Height + g.hero.Radius + 10
		case portal.SideBottom:
			g.hero.X = event.HeroX
			g.hero.Y = destPortal.Y - g.hero.Radius - 10
		}
	}
}

// FPS tracking
func (g *GameplayScreen) trackFPS(delta time.Duration) {
	if g.fpsEnabled == nil || !*g.fpsEnabled {
		return
	}

	g.fpsAccumulator += delta
	g.fpsFrames++

	if g.fpsAccumulator >= config.FPSSampleWindow {
		g.fpsValue = float64(g.fpsFrames) / g.fpsAccumulator.Seconds()
		g.fpsAccumulator = 0
		g.fpsFrames = 0
	}
}

// Rendering
func (g *GameplayScreen) Draw(screen *ebiten.Image) {
	cfg := config.Default()

	currentMap := g.mapManager.CurrentMap()
	if currentMap != nil {
		currentMap.Draw(screen)
		g.hud.ScreenName = currentMap.DisplayName

		g.drawAmbientOverlay(screen, currentMap)

		// Draw raycasting shadows/lighting
		if g.raycastEnabled != nil && *g.raycastEnabled {
			g.raycastSystem.Draw(screen, g.world)

			if g.raycastDebugMode != nil && *g.raycastDebugMode {
				g.raycastSystem.DrawDebug(screen, g.world)
			}
		}

		// Draw sun or moon circle in top-right
		g.drawCelestialBody(screen, currentMap)

		// Update hero lighting state
		g.updateHeroLighting(currentMap)
	} else {
		screen.Fill(cfg.Visual.BackgroundColor)
	}

	g.projectiles.Draw(screen)
	g.hero.Draw(screen)

	staminaColor := cfg.Visual.StaminaNormalColor
	if g.hero.Stamina < g.hero.MaxStamina*config.StaminaLowThreshold {
		staminaColor = cfg.Visual.StaminaLowColor
	}

	staminaMeter := status.Meter{
		Label: "Stamina",
		Base:  g.hero.MaxStamina,
		Level: g.hero.Stamina,
		Color: staminaColor,
	}

	meters := []status.Meter{staminaMeter}

	if g.fpsEnabled != nil && *g.fpsEnabled {
		ratio := g.fpsValue / float64(config.TargetTPS)
		fpsColor := cfg.Visual.FPSGoodColor
		switch {
		case ratio < config.FPSPoorThreshold:
			fpsColor = cfg.Visual.FPSPoorColor
		case ratio < config.FPSWarnThreshold:
			fpsColor = cfg.Visual.FPSWarnColor
		}

		fpsMeter := status.Meter{
			Label: fmt.Sprintf("Framerate: %3.0f FPS", g.fpsValue),
			Base:  -1,
			Level: 0,
			Color: fpsColor,
		}
		meters = append(meters, fpsMeter)
	}

	g.hud.Draw(screen, meters)

	if g.showSaveNotification {
		g.drawSaveNotification(screen)
	}
}

func (g *GameplayScreen) drawSaveNotification(screen *ebiten.Image) {
	centerX := float32(g.bounds.Width / 2)
	centerY := float32(30)

	boxWidth := float32(140)
	boxHeight := float32(40)

	cfg := config.Default()
	vector.FillRect(screen,
		centerX-boxWidth/2,
		centerY-boxHeight/2,
		boxWidth,
		boxHeight,
		color.NRGBA{R: 0, G: 0, B: 0, A: 180},
		false)

	vector.StrokeRect(screen,
		centerX-boxWidth/2,
		centerY-boxHeight/2,
		boxWidth,
		boxHeight,
		2,
		cfg.Visual.SaveNotificationColor,
		false)

	msg := "Game Saved!"
	textX := centerX - 45
	textY := centerY - 5
	ebitenutil.DebugPrintAt(screen, msg, int(textX), int(textY))
}

func (g *GameplayScreen) updateHeroLighting(currentMap *maps.Map) {
	if g.raycastEnabled == nil || !*g.raycastEnabled {
		g.hero.Brightness = 1.0
		return
	}

	ambient := currentMap.Lighting.AmbientBrightness
	maxDirectLight := 0.0

	for _, light := range currentMap.Lighting.Sources {
		inShadow := g.raycastSystem.IsPointInShadow(g.hero.X, g.hero.Y, g.world)

		if !inShadow {
			maxDirectLight = math.Max(maxDirectLight, light.Intensity)
		} else {
			indirect := light.Intensity * (1.0 - light.ShadowDarkness)
			maxDirectLight = math.Max(maxDirectLight, indirect)
		}
	}

	g.hero.Brightness = math.Min(1.0, ambient+maxDirectLight)
}

func (g *GameplayScreen) drawAmbientOverlay(screen *ebiten.Image, currentMap *maps.Map) {
	if currentMap.Lighting.AmbientBrightness >= 1.0 {
		return
	}

	darkness := 1.0 - currentMap.Lighting.AmbientBrightness
	alpha := uint8(darkness * 200)

	overlay := ebiten.NewImage(int(g.bounds.Width), int(g.bounds.Height))
	overlay.Fill(color.RGBA{R: 0, G: 0, B: 0, A: alpha})
	screen.DrawImage(overlay, &ebiten.DrawImageOptions{})
}

func (g *GameplayScreen) drawCelestialBody(screen *ebiten.Image, currentMap *maps.Map) {
	if currentMap == nil {
		return
	}

	for _, lightSrc := range currentMap.Lighting.Sources {
		cx := float32(lightSrc.X)
		cy := float32(lightSrc.Y)
		radius := float32(25)

		vector.DrawFilledCircle(screen, cx, cy, radius, lightSrc.Color, true)
		glowColor := lightSrc.Color
		glowColor.A = 80
		vector.DrawFilledCircle(screen, cx, cy, radius+3, glowColor, true)
	}
}

func (g *GameplayScreen) ShowSaveNotification() {
	g.showSaveNotification = true
	g.saveNotificationTimer = config.SaveNotificationDuration
}

// State management
func (g *GameplayScreen) Reset() {
	cfg := config.Default()
	screenWidth := int(g.bounds.Width)
	screenHeight := int(g.bounds.Height)

	g.mapManager.Reset()
	g.mapManager.SetCurrentMap("plains")
	currentMap := g.mapManager.CurrentMap()

	g.hero = hero.New(hero.Config{
		StartX:       cfg.Hero.StartX,
		StartY:       cfg.Hero.StartY,
		Radius:       cfg.Hero.Radius,
		Speed:        cfg.Hero.Speed,
		Color:        cfg.Hero.Color,
		MaxStamina:   cfg.Hero.MaxStamina,
		StaminaDrain: cfg.Hero.StaminaDrain,
		StaminaRegen: cfg.Hero.StaminaRegen,
	})
	g.world = currentMap.World
	g.projectiles = projectile.NewManager()
	g.portals = portal.NewManager()
	for _, p := range currentMap.Portals {
		g.portals.AddPortal(p)
	}
	g.portals.OnTransition = g.handlePortalTransition

	// set portal visibility
	if g.portalVisibility != nil {
		visible := *g.portalVisibility
		for _, p := range g.portals.Portals {
			p.Visible = visible
		}
	}

	g.bounds = hero.Bounds{
		Width:  float64(screenWidth),
		Height: float64(screenHeight),
		Ground: float64(screenHeight) - config.GroundHeight,
	}
	g.lastTick = time.Now()
	g.gameStartTime = time.Now()
	g.totalPlayTime = 0
	g.fpsFrames = 0
	g.fpsAccumulator = 0
	g.fpsValue = 0
}

func (g *GameplayScreen) SaveState() *save.GameState {
	currentMapID := "plains"
	if g.mapManager.CurrentMap() != nil {
		currentMapID = g.mapManager.CurrentMap().ID
	}
	return &save.GameState{
		CurrentMap:  currentMapID,
		HeroX:       g.hero.X,
		HeroY:       g.hero.Y,
		HeroStamina: g.hero.Stamina,
		PlayTimeMS:  g.totalPlayTime.Milliseconds(),
	}
}

func (g *GameplayScreen) LoadState(state *save.GameState) {
	cfg := config.Default()
	screenWidth := int(g.bounds.Width)
	screenHeight := int(g.bounds.Height)

	g.mapManager.Reset()

	mapID := state.CurrentMap
	if mapID == "" {
		mapID = "plains"
	}

	if err := g.mapManager.SetCurrentMap(mapID); err != nil {
		g.mapManager.SetCurrentMap("plains")
	}
	currentMap := g.mapManager.CurrentMap()

	g.hero = hero.New(hero.Config{
		StartX:       state.HeroX,
		StartY:       state.HeroY,
		Radius:       cfg.Hero.Radius,
		Speed:        cfg.Hero.Speed,
		Color:        cfg.Hero.Color,
		MaxStamina:   cfg.Hero.MaxStamina,
		StaminaDrain: cfg.Hero.StaminaDrain,
		StaminaRegen: cfg.Hero.StaminaRegen,
	})
	g.hero.Stamina = state.HeroStamina
	g.world = currentMap.World
	g.projectiles = projectile.NewManager()
	g.portals = portal.NewManager()
	for _, p := range currentMap.Portals {
		g.portals.AddPortal(p)
	}
	g.portals.OnTransition = g.handlePortalTransition

	// set portal visibility
	if g.portalVisibility != nil {
		visible := *g.portalVisibility
		for _, p := range g.portals.Portals {
			p.Visible = visible
		}
	}

	g.bounds = hero.Bounds{
		Width:  float64(screenWidth),
		Height: float64(screenHeight),
		Ground: float64(screenHeight) - config.GroundHeight,
	}
	g.totalPlayTime = time.Duration(state.PlayTimeMS) * time.Millisecond
	g.lastTick = time.Now()
	g.gameStartTime = time.Now()
	g.fpsFrames = 0
	g.fpsAccumulator = 0
	g.fpsValue = 0
}

// ToggleRaycast toggles the raycasting lighting system on/off
func (g *GameplayScreen) ToggleRaycast() {
	if g.raycastEnabled != nil {
		*g.raycastEnabled = !*g.raycastEnabled
	}
}

// ToggleRaycastDebug toggles the raycasting debug visualization
func (g *GameplayScreen) ToggleRaycastDebug() {
	if g.raycastDebugMode != nil {
		*g.raycastDebugMode = !*g.raycastDebugMode
	}
}

// IsRaycastEnabled returns whether raycasting is enabled
func (g *GameplayScreen) IsRaycastEnabled() bool {
	if g.raycastEnabled == nil {
		return false
	}
	return *g.raycastEnabled
}

// IsRaycastDebugMode returns whether raycasting debug mode is enabled
func (g *GameplayScreen) IsRaycastDebugMode() bool {
	if g.raycastDebugMode == nil {
		return false
	}
	return *g.raycastDebugMode
}
