package scenes

import (
	"fmt"
	"image"
	"image/color"
	_ "image/jpeg"
	_ "image/png"
	"log"
	"sort"

	"mountain/internal/entities"
	"mountain/internal/input"

	"github.com/go-gl/mathgl/mgl64"
	"github.com/hajimehoshi/ebiten/v2"
	"github.com/hajimehoshi/ebiten/v2/ebitenutil"
	"github.com/hajimehoshi/ebiten/v2/inpututil"
)

const (
	Gravity   = 0.5
	JumpForce = -8.0
	MoveSpeed = 4.0
	ViewSize  = 380 // 800 width, we have 2 viewports (380 each with 20 padding)
)

type PlayState int

const (
	StatePlaying PlayState = iota
	StatePaused
	StateSettings
)

type Player struct {
	Pos mgl64.Vec3 // 3D Kordinater: X (vänster/höger), Y (upp/ner), Z (djup)
	Vel mgl64.Vec3 // Hastighet

	Width, Height float64 // För (AABB)
	IsGrounded    bool

	// Sprites
	SpriteRun        *ebiten.Image
	SpriteJump       *ebiten.Image
	SpriteIdle       *ebiten.Image
	SpriteTop        *ebiten.Image
	AnimationFrame   int
	AnimationCounter int

	FacingRight bool
}

type PlayScene struct {
	world     *entities.World
	player    *Player
	leftView  *ebiten.Image
	rightView *ebiten.Image
	state     PlayState

	// Settings UI state
	sortedActions []string
	selectedIndex int
}

func NewPlayScene() *PlayScene {
	w, err := entities.LoadWorldFromTiled("assets/maps/room1.tmj")
	if err != nil {
		log.Println("Kunde inte ladda room1:", err)
		w = entities.NewWorld(100, 10, 100) // Fallback tom värd om JSON saknas
	}

	pRun, _, err := ebitenutil.NewImageFromFile("assets/images/Warrior_1/Run.png")
	if err != nil {
		log.Println("Run sprite error:", err)
		pRun = ebiten.NewImage(96, 96)
	}
	pIdle, _, _ := ebitenutil.NewImageFromFile("assets/images/Warrior_1/Idle.png")
	if pIdle == nil {
		pIdle = pRun
	}
	pJump, _, _ := ebitenutil.NewImageFromFile("assets/images/Warrior_1/Jump.png")
	if pJump == nil {
		pJump = pRun
	}

	pTop := ebiten.NewImage(96, 96)
	pTop.Fill(color.RGBA{255, 255, 0, 255})

	player := &Player{
		Pos:         mgl64.Vec3{50.0 * entities.TileSize, 6.0 * entities.TileSize, 50.0 * entities.TileSize},
		Width:       32,
		Height:      32,
		SpriteRun:   pRun,
		SpriteIdle:  pIdle,
		SpriteJump:  pJump,
		SpriteTop:   pTop,
		FacingRight: true,
	}

	return &PlayScene{
		world:         w,
		player:        player,
		leftView:      ebiten.NewImage(ViewSize, ViewSize),
		rightView:     ebiten.NewImage(ViewSize, ViewSize),
		state:         StatePlaying,
		sortedActions: make([]string, 0),
		selectedIndex: 0,
	}
}

func (s *PlayScene) Update() error {
	switch s.state {
	case StatePlaying:
		return s.updatePlaying()
	case StatePaused:
		return s.updatePaused()
	case StateSettings:
		return s.updateSettings()
	}
	return nil
}

func (s *PlayScene) updatePaused() error {
	if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
		s.state = StatePlaying
	}
	// Menu logic for pausing
	if inpututil.IsKeyJustPressed(ebiten.KeyEnter) {
		if s.selectedIndex == 0 {
			s.state = StatePlaying
		} else if s.selectedIndex == 1 {
			s.state = StateSettings
			s.initSettings()
		}
	}
	if inpututil.IsKeyJustPressed(ebiten.KeyUp) || inpututil.IsKeyJustPressed(ebiten.KeyW) {
		s.selectedIndex--
		if s.selectedIndex < 0 {
			s.selectedIndex = 1
		}
	}
	if inpututil.IsKeyJustPressed(ebiten.KeyDown) || inpututil.IsKeyJustPressed(ebiten.KeyS) {
		s.selectedIndex++
		if s.selectedIndex > 1 {
			s.selectedIndex = 0
		}
	}

	return nil
}

func (s *PlayScene) initSettings() {
	s.sortedActions = make([]string, 0, len(input.Manager.Actions))
	for k := range input.Manager.Actions {
		s.sortedActions = append(s.sortedActions, k)
	}
	sort.Strings(s.sortedActions)
	s.selectedIndex = 0
}

func (s *PlayScene) updateSettings() error {
	if input.Manager.IsRecording() {
		if input.Manager.UpdateRecording() {
			// Recording finished
		}
		return nil
	}

	if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
		s.state = StatePaused
		s.selectedIndex = 1 // back to settings button
		return nil
	}

	if inpututil.IsKeyJustPressed(ebiten.KeyUp) || inpututil.IsKeyJustPressed(ebiten.KeyW) {
		s.selectedIndex--
		if s.selectedIndex < 0 {
			s.selectedIndex = len(s.sortedActions) - 1
		}
	}
	if inpututil.IsKeyJustPressed(ebiten.KeyDown) || inpututil.IsKeyJustPressed(ebiten.KeyS) {
		s.selectedIndex++
		if s.selectedIndex >= len(s.sortedActions) {
			s.selectedIndex = 0
		}
	}

	if inpututil.IsKeyJustPressed(ebiten.KeyEnter) {
		action := s.sortedActions[s.selectedIndex]
		input.Manager.StartRecording(action)
	}

	return nil
}

func (s *PlayScene) updatePlaying() error {
	if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
		s.state = StatePaused
		s.selectedIndex = 0
		return nil
	}

	p := s.player

	currentMoveSpeed := MoveSpeed
	if input.Manager.IsActionPressed("Run") {
		currentMoveSpeed = MoveSpeed * 1.8 // Lite snabbare när man springer
	}

	p.Vel[0] = 0
	p.Vel[2] = 0
	isMoving := false

	// Kolla om spelaren är i ett klättringsbart objekt
	currentGridX := int((p.Pos[0] + p.Width/2) / entities.TileSize)
	currentGridY := int((p.Pos[1] + p.Height/2) / entities.TileSize)
	currentGridZ := int((p.Pos[2] + p.Width/2) / entities.TileSize)
	entIn := s.world.GetEntityAt(currentGridX, currentGridY, currentGridZ)

	isClimbing := false
	if entIn != nil && entIn.IsClimbable() && input.Manager.IsActionPressed("ClimbMode") {
		isClimbing = true
	}

	if isClimbing {
		p.Vel[1] = 0 // Ingen gravitation när man klättrar

		if input.Manager.IsActionPressed("ClimbLeft") {
			p.Vel[0] = -currentMoveSpeed
			p.FacingRight = false
			isMoving = true
		}
		if input.Manager.IsActionPressed("ClimbRight") {
			p.Vel[0] = currentMoveSpeed
			p.FacingRight = true
			isMoving = true
		}
		if input.Manager.IsActionPressed("ClimbUp") {
			p.Vel[1] = -currentMoveSpeed // Uppåt
			isMoving = true
		}
		if input.Manager.IsActionPressed("ClimbDown") {
			p.Vel[1] = currentMoveSpeed // Nedåt
			isMoving = true
		}
		if input.Manager.IsActionPressed("ClimbIn") {
			p.Vel[2] = -currentMoveSpeed // Inåt (mot djupet Z-)
			isMoving = true
		}
		if input.Manager.IsActionPressed("ClimbOut") {
			p.Vel[2] = currentMoveSpeed // Utåt (från djupet Z+)
			isMoving = true
		}
	} else {
		if input.Manager.IsActionPressed("MoveLeft") {
			p.Vel[0] = -currentMoveSpeed
			p.FacingRight = false
			isMoving = true
		}
		if input.Manager.IsActionPressed("MoveRight") {
			p.Vel[0] = currentMoveSpeed
			p.FacingRight = true
			isMoving = true
		}
		if input.Manager.IsActionPressed("MoveUp") {
			p.Vel[2] = -currentMoveSpeed
			isMoving = true
		}
		if input.Manager.IsActionPressed("MoveDown") {
			p.Vel[2] = currentMoveSpeed
			isMoving = true
		}

		if input.Manager.IsActionPressed("Jump") && p.IsGrounded {
			p.Vel[1] = JumpForce
			p.IsGrounded = false
		}

		if !p.IsGrounded {
			p.Vel[1] += Gravity
		}
	}

	// === FYSIK OCH KOLLISION ===

	// Få tag på det nuvarande objektet i spelarens mitt-punkt för att veta om vi står i något klättringsbart
	currGridX := int((p.Pos[0] + p.Width/2) / entities.TileSize)
	currGridY := int((p.Pos[1] + p.Height/2) / entities.TileSize)
	currGridZ := int((p.Pos[2] + p.Width/2) / entities.TileSize)

	currentEnt := s.world.GetEntityAt(currGridX, currGridY, currGridZ)
	isCurrentClimbable := currentEnt != nil && currentEnt.IsClimbable()

	// 1. Hantera kollision för X-axeln (vänster/höger)
	nextX := p.Pos[0] + p.Vel[0]
	if p.Vel[0] != 0 {
		targetGridX := int((nextX + p.Width/2) / entities.TileSize)
		entX := s.world.GetEntityAt(targetGridX, currGridY, currGridZ)

		if entX != nil && entX.IsBlocking() {
			isTargetClimbable := entX.IsClimbable()

			// Auto-klättra om målet är klättringsbart ELLER om vi redan står i något klättringsbart (t.ex framför en vägg på en stege)
			if isTargetClimbable || isCurrentClimbable {
				nextX = p.Pos[0] // Avbryt rörelse framåt
				p.Vel[0] = 0
				p.Pos[1] -= MoveSpeed * 0.6 // Långsam rörelse uppåt ("klättra upp")
				p.IsGrounded = false
			} else {
				// Vanlig vägg - stoppa rörelse helt
				nextX = p.Pos[0]
				p.Vel[0] = 0
			}
		}
	}
	p.Pos[0] = nextX

	// 2. Hantera kollision för Z-axeln (djup in/ut)
	nextZ := p.Pos[2] + p.Vel[2]
	if p.Vel[2] != 0 {
		// Uppdatera currGridX eftersom vi kan ha rört oss
		currGridX = int((p.Pos[0] + p.Width/2) / entities.TileSize)
		targetGridZ := int((nextZ + p.Width/2) / entities.TileSize)
		entZ := s.world.GetEntityAt(currGridX, currGridY, targetGridZ)

		if entZ != nil && entZ.IsBlocking() {
			isTargetClimbable := entZ.IsClimbable()

			if isTargetClimbable || isCurrentClimbable {
				nextZ = p.Pos[2] // Avbryt rörelse framåt
				p.Vel[2] = 0
				p.Pos[1] -= MoveSpeed * 0.6 // Långsam rörelse uppåt ("klättra upp")
				p.IsGrounded = false
			} else {
				// Vanlig vägg - stoppa rörelse helt
				nextZ = p.Pos[2]
				p.Vel[2] = 0
			}
		}
	}
	p.Pos[2] = nextZ

	// 3. Hantera kollision för Y-axeln (upp/ner + gravitation)
	nextY := p.Pos[1] + p.Vel[1]

	gridX := int((p.Pos[0] + p.Width/2) / entities.TileSize)
	gridZ := int((p.Pos[2] + p.Width/2) / entities.TileSize)

	if p.Vel[1] >= 0 {
		// Faller (eller står still) - kolla kollision med marken (fötterna)
		gridYFeet := int((nextY + p.Height) / entities.TileSize)
		entY := s.world.GetEntityAt(gridX, gridYFeet, gridZ)

		if entY != nil && entY.IsBlocking() {
			nextY = float64(gridYFeet)*entities.TileSize - p.Height - 0.1
			p.Vel[1] = 0
			p.IsGrounded = true
		} else {
			p.IsGrounded = false
		}
	} else {
		// Hoppar eller klättrar uppåt - kolla kollision med taket (huvudet)
		p.IsGrounded = false
		gridYHead := int(nextY / entities.TileSize)
		entY := s.world.GetEntityAt(gridX, gridYHead, gridZ)

		// Vi studsar i taket om det är solid OCH inte är klättringsbart
		if entY != nil && entY.IsBlocking() && !entY.IsClimbable() {
			nextY = float64(gridYHead+1)*entities.TileSize + 0.1
			p.Vel[1] = 0
		}
	}
	p.Pos[1] = nextY

	// Kolla ifall vi står på en portal (mittpunkten)
	standEnt := s.world.GetEntityAt(gridX, int((p.Pos[1]+p.Height/2)/entities.TileSize), gridZ)
	if prt, ok := standEnt.(*entities.Portal); ok {
		log.Println("Byter rum till: ", prt.TargetMap)
		newW, err := entities.LoadWorldFromTiled("assets/maps/" + prt.TargetMap + ".tmj")
		if err == nil {
			s.world = newW
			// Reset player position for new map
			if prt.TargetMap == "room1" {
				p.Pos = mgl64.Vec3{80.0 * entities.TileSize, 6.0 * entities.TileSize, 10.0 * entities.TileSize}
			} else {
				p.Pos = mgl64.Vec3{40.0 * entities.TileSize, 6.0 * entities.TileSize, 65.0 * entities.TileSize}
			}
		}
	}

	if isMoving {
		p.AnimationCounter++
		if p.AnimationCounter > 5 {
			p.AnimationFrame = (p.AnimationFrame + 1) % 6
			p.AnimationCounter = 0
		}
	} else {
		p.AnimationFrame = 0
	}

	return nil
}

func (s *PlayScene) Draw(screen *ebiten.Image) {
	screen.Fill(color.RGBA{20, 20, 20, 255})

	s.leftView.Fill(color.RGBA{135, 206, 235, 255})
	s.rightView.Fill(color.RGBA{50, 150, 50, 255})

	playerGridZ := int((s.player.Pos[2] + s.player.Width/2) / entities.TileSize)
	playerGridY := int((s.player.Pos[1] + s.player.Height/2) / entities.TileSize)

	camX := s.player.Pos[0] - float64(ViewSize)/2.0
	camY := s.player.Pos[1] - float64(ViewSize)/2.0
	camZ := s.player.Pos[2] - float64(ViewSize)/2.0

	// Vänster vy: Sido-vy (renderar objekt vid spelarens Z, max -3 till +3 djup)
	leftDepthStart := playerGridZ - 3
	if leftDepthStart < 0 {
		leftDepthStart = 0
	}
	leftDepthEnd := playerGridZ + 3
	if leftDepthEnd >= s.world.Depth {
		leftDepthEnd = s.world.Depth - 1
	}

	for zLevel := leftDepthStart; zLevel <= leftDepthEnd; zLevel++ {
		alpha := float32(1.0)
		diff := float32(zLevel - playerGridZ)

		if diff < 0 {
			// Bakom spelaren: självande alpha
			alpha = 1.0 + (diff * 0.2) // diff är negativt, så detta minskar alpha
			if alpha < 0.2 {
				alpha = 0.2
			}
		} else if diff > 0 {
			// Framför spelaren: självande alpha
			alpha = 1.0 - (diff * 0.3)
			if alpha < 0.1 {
				alpha = 0.1
			}
		}

		// Rendera spelaren på rätt Z-lager (vid diff == 0)
		if zLevel == playerGridZ {
			for x := 0; x < s.world.Width; x++ {
				for y := 0; y < s.world.Height; y++ {
					ent := s.world.GetEntityAt(x, y, zLevel)
					if ent != nil {
						px := float64(x)*entities.TileSize - camX
						py := float64(y)*entities.TileSize - camY
						ent.DrawSide(s.leftView, px, py, 1.0)
					}
				}
			}
			s.drawPlayerSide(s.leftView, camX, camY)
		} else {
			for x := 0; x < s.world.Width; x++ {
				for y := 0; y < s.world.Height; y++ {
					ent := s.world.GetEntityAt(x, y, zLevel)
					if ent != nil {
						px := float64(x)*entities.TileSize - camX
						py := float64(y)*entities.TileSize - camY
						ent.DrawSide(s.leftView, px, py, alpha)
					}
				}
			}
		}
	}

	// Höger vy: Ovan-vy (renderar Y-lager runt spelaren)
	// Positivt 'diff' i Y-axel betyder djupare in i marken (Längre bort = mer transparent)
	// Negativt 'diff' i Y-axel betyder högre upp mot kameran (Närmare = mer transparent så man ser igenom)
	for diff := 3; diff >= -3; diff-- {
		yLevel := playerGridY + diff
		if yLevel >= 0 && yLevel < s.world.Height {
			alpha := float32(1.0)

			if diff > 0 {
				// Under marknivån: sjunkande alpha
				alpha = 1.0 - (float32(diff) * 0.2)
				if alpha < 0.2 {
					alpha = 0.2
				}
			} else if diff < 0 {
				// Över marknivån: sjunkande alpha
				alpha = 1.0 - (float32(-diff) * 0.3)
				if alpha < 0.1 {
					alpha = 0.1
				}
			}

			if diff == 0 {
				for x := 0; x < s.world.Width; x++ {
					for z := 0; z < s.world.Depth; z++ {
						ent := s.world.GetEntityAt(x, yLevel, z)
						if ent != nil {
							px := float64(x)*entities.TileSize - camX
							pz := float64(z)*entities.TileSize - camZ
							ent.DrawTop(s.rightView, px, pz, 1.0)
						}
					}
				}
				s.drawPlayerTop(s.rightView, camX, camZ)
			} else {
				for x := 0; x < s.world.Width; x++ {
					for z := 0; z < s.world.Depth; z++ {
						ent := s.world.GetEntityAt(x, yLevel, z)
						if ent != nil {
							px := float64(x)*entities.TileSize - camX
							pz := float64(z)*entities.TileSize - camZ
							ent.DrawTop(s.rightView, px, pz, alpha)
						}
					}
				}
			}
		}
	}

	opL := &ebiten.DrawImageOptions{}
	opL.GeoM.Translate(10, 100)
	screen.DrawImage(s.leftView, opL)

	opR := &ebiten.DrawImageOptions{}
	opR.GeoM.Translate(410, 100)
	screen.DrawImage(s.rightView, opR)

	ebitenutil.DebugPrint(screen, "VÄNSTER (Sido vy)        HÖGER (Top vy: Slice av din Hojd)")

	// Rita UI ovanpå allt, beroende på state
	if s.state == StatePaused {
		s.drawPauseUI(screen)
	} else if s.state == StateSettings {
		s.drawSettingsUI(screen)
	}
}

func (s *PlayScene) drawPlayerSide(screen *ebiten.Image, camX, camY float64) {
	p := s.player

	sx := p.AnimationFrame * 96
	srcRect := image.Rect(sx, 0, sx+96, 96)
	var activeSprite *ebiten.Image

	if p.Vel[0] != 0 || p.Vel[2] != 0 { // Is moving X or Z
		activeSprite = p.SpriteRun
	} else {
		activeSprite = p.SpriteIdle
	}

	subImg := activeSprite.SubImage(srcRect).(*ebiten.Image)

	op := &ebiten.DrawImageOptions{}
	if !p.FacingRight {
		op.GeoM.Scale(-1, 1)
		op.GeoM.Translate(96, 0)
	}

	drawX := p.Pos[0] - camX - 32
	drawY := p.Pos[1] - camY - 64

	op.GeoM.Translate(drawX, drawY)
	screen.DrawImage(subImg, op)
}

func (s *PlayScene) drawPlayerTop(screen *ebiten.Image, camX, camZ float64) {
	p := s.player

	sx := p.AnimationFrame * 96
	srcRect := image.Rect(sx, 0, sx+96, 96)
	var activeSprite *ebiten.Image

	if p.Vel[0] != 0 || p.Vel[2] != 0 || p.Vel[1] != 0 {
		activeSprite = p.SpriteRun
	} else {
		activeSprite = p.SpriteIdle
	}

	subImg := activeSprite.SubImage(srcRect).(*ebiten.Image)

	op := &ebiten.DrawImageOptions{}
	if !p.FacingRight {
		op.GeoM.Scale(-1, 1)
		op.GeoM.Translate(96, 0)
	}

	drawX := p.Pos[0] - camX - 32
	drawZ := p.Pos[2] - camZ - 64
	op.GeoM.Translate(drawX, drawZ)

	// Rita med lite tint i toppen kanske? Vi ritar bara ut imagen
	screen.DrawImage(subImg, op)
}

func (s *PlayScene) drawPauseUI(screen *ebiten.Image) {
	// Mörka ner bakgrunden något
	overlay := ebiten.NewImage(800, 600)
	overlay.Fill(color.RGBA{0, 0, 0, 150})
	screen.DrawImage(overlay, &ebiten.DrawImageOptions{})

	ebitenutil.DebugPrintAt(screen, "--- PAUSAD ---", 350, 200)

	options := []string{"Fortsatt", "Installningar"}
	for i, opt := range options {
		prefix := "  "
		if i == s.selectedIndex {
			prefix = "> "
		}
		ebitenutil.DebugPrintAt(screen, prefix+opt, 350, 250+(i*30))
	}
}

func (s *PlayScene) drawSettingsUI(screen *ebiten.Image) {
	overlay := ebiten.NewImage(800, 600)
	overlay.Fill(color.RGBA{0, 0, 0, 200})
	screen.DrawImage(overlay, &ebiten.DrawImageOptions{})

	ebitenutil.DebugPrintAt(screen, "--- INSTALLNINGAR ---", 300, 30)

	if input.Manager.IsRecording() {
		ebitenutil.DebugPrintAt(screen, ">> SPELAR IN: HÅLL IN TANGENTER OCH TRYCK ENTER FOR ATT SPARA <<", 180, 60)
		ebitenutil.DebugPrintAt(screen, "ESC for att avbryta", 300, 80)
	} else {
		ebitenutil.DebugPrintAt(screen, "Tryck Enter for att ga in i redigeringslage, ESC for att abryta", 180, 60)
	}

	startY := 120
	for i, action := range s.sortedActions {
		prefix := "  "
		if i == s.selectedIndex {
			prefix = "> "
		}

		// Bygg en sträng för att visa nuvarande tangenter
		bindingStr := ""
		bindings := input.Manager.Actions[action]
		if input.Manager.IsRecording() && input.Manager.GetRecordingAction() == action {
			// Visar vilka knappar som spelas in just nu
			recKeys := input.Manager.GetRecordingKeys()
			if len(recKeys) == 0 {
				bindingStr = "[Vantar pa dig...]"
			} else {
				names := []string{}
				for _, rk := range recKeys {
					names = append(names, input.KeyName(rk))
				}
				bindingStr = fmt.Sprintf("[TRYCK ENTER FOR ATT SPARA: %v]", names)
			}
		} else {
			if len(bindings) > 0 {
				for bi, b := range bindings {
					if bi > 0 {
						bindingStr += " ELLER "
					}
					names := []string{}
					for _, k := range b {
						names = append(names, input.KeyName(k))
					}
					bindingStr += fmt.Sprintf("%v", names)
				}
			} else {
				bindingStr = "[Ingen]"
			}
		}

		line := fmt.Sprintf("%s%-15s : %s", prefix, action, bindingStr)
		ebitenutil.DebugPrintAt(screen, line, 100, startY+(i*25))
	}
}
