package se.urmo.game.state;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import se.urmo.game.collision.CollisionChecker;
import se.urmo.game.collision.GhostCollisionChecker;
import se.urmo.game.entities.ghost.Ghost;
import se.urmo.game.entities.ghost.mode.GhostMode;
import se.urmo.game.entities.pacman.PacMan;
import se.urmo.game.main.AnimationManager;
import se.urmo.game.main.FruitManager;
import se.urmo.game.main.GameStateManager;
import se.urmo.game.main.GhostManager;
import se.urmo.game.main.LevelManager;
import se.urmo.game.main.ScorePopupManager;
import se.urmo.game.map.GameMap;
import se.urmo.game.map.MapTile;
import se.urmo.game.map.TileType;
import se.urmo.game.sound.SoundEffect;
import se.urmo.game.sound.SoundManager;
import se.urmo.game.util.Direction;
import se.urmo.game.util.GameFonts;
import se.urmo.game.util.GameStateType;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.KeyEvent;

@Slf4j
public class PlayingState implements GameState {
    private final GameStateManager gameStateManager;
    private final GameOverState gameOverState;

    // Core components
    private final GhostManager ghostManager;
    private final FruitManager fruitManager;
    private final LevelManager levelManager;
    private final AnimationManager animationManager;
    private final PacMan pacman;
    @Getter
    private final GameMap map;

    // Durations (tune to taste)
    private static final int READY_MS = 3000;
    private static final int LEVEL_COMPLETE_MS = 1500;
    private static final int LIFE_LOST_MS = 2000;

    // Score/Lives
    private int score = 0;
    private int lives = 3;
    private int dotsEaten = 0;

    // Phase + timers
    private RoundPhase phase = RoundPhase.READY;
    private long phaseStartMs = System.currentTimeMillis();
    private boolean deathInProgress;

    private final ScorePopupManager scorePopups = new ScorePopupManager();
    private final Font scorePopupFont = GameFonts.arcade(16F);

    private final SoundManager sound = new SoundManager();

    public PlayingState(GameStateManager gameStateManager, GameOverState gameOverState) {
        this.gameStateManager = gameStateManager;
        this.gameOverState = gameOverState;
        this.map = new GameMap("maps/map1.csv");
        this.animationManager = new AnimationManager();
        this.levelManager = new LevelManager();
        this.pacman = new PacMan(new CollisionChecker(map), levelManager);
        this.animationManager.register(pacman);
        this.ghostManager = new GhostManager(new GhostCollisionChecker(map), animationManager, levelManager);
        this.fruitManager = new FruitManager(levelManager);
        sound.play(SoundEffect.START);
    }

    @Override
    public void update() {
        switch (phase) {
            case READY -> {
                // Freeze everything during READY
                if (phaseElapsed() >= READY_MS) {
                    ghostManager.setMode(GhostMode.CHASE);
                    setPhase(RoundPhase.PLAYING);
                }
            }
            case PLAYING -> {
                sound.loop(SoundEffect.SIREN);
                animationManager.updateAll();
                pacman.update();
                ghostManager.update(pacman, map);
                fruitManager.update(pacman, this);
                checkCollisions();
                handleDots();
            }
            case LEVEL_COMPLETE -> {
                // Freeze, then advance level
                if (phaseElapsed() >= LEVEL_COMPLETE_MS) {
                    advanceLevel();
                    setPhase(RoundPhase.READY);
                }
            }
            case LIFE_LOST -> {
                // Freeze, then reset round (keep dot state)
                if (phaseElapsed() >= LIFE_LOST_MS) {
                    pacman.setState(PacMan.PacmanState.ALIVE);
                    deathInProgress = false;
                    ghostManager.getGhosts().forEach(ghost -> ghost.requestModeChange(GhostMode.CHASE));
                    setPhase(RoundPhase.READY);
                    if (lives <= 0) {
                        endGame();
                    }
                }
                pacman.update();
            }
        }
        scorePopups.update();
    }

    private void advanceLevel() {
        levelManager.nextLevel();
        map.reset();
        ghostManager.reset();
        fruitManager.reset();
        pacman.setState(PacMan.PacmanState.ALIVE);
        dotsEaten = 0;
    }

    private void setPhase(RoundPhase next) {
        phase = next;
        phaseStartMs = System.currentTimeMillis();
    }

    private long phaseElapsed() {
        return System.currentTimeMillis() - phaseStartMs;
    }

    private void handleDots() {
        Point pacmanScreenPos = pacman.getPosition();
        MapTile tile = map.getTile(pacmanScreenPos);
        boolean wasRemoved = map.removeTileImage(pacmanScreenPos);
        if (wasRemoved && tile.getTileType() == TileType.LARGE_PELLET) {
            ghostManager.setFrightMode();
        }
        if (wasRemoved) {
            sound.playMunch();
            dotsEaten++;
            fruitManager.dotEaten(dotsEaten);
            score += tile.getTileType().getScore();
            if (dotsEaten == map.numberOfDots()) {
                setPhase(RoundPhase.LEVEL_COMPLETE);
            }
        }
    }

    @Override
    public void render(Graphics2D g) {
        map.draw(g);
        ghostManager.draw(g);
        pacman.draw(g);
        fruitManager.draw(g);
        drawUI(g);
        scorePopups.draw(g, GameMap.OFFSET_X, GameMap.OFFSET_Y, scorePopupFont);

        // Phase overlays
        switch (phase) {
            case READY -> drawCenterText(g, "READY!");
            case LEVEL_COMPLETE ->
                    drawCenterText(g, "LEVEL " + levelManager.getCurrentLevel().getLevel() + " COMPLETE!");
            case LIFE_LOST -> drawCenterText(g, "LIFE LOST");
            default -> { /* no overlay */ }
        }
    }

    private void drawCenterText(Graphics2D g, String text) {
        g.setFont(GameFonts.arcade(18F));
        g.setColor(Color.YELLOW);
        var fm = g.getFontMetrics();
        int cx = GameMap.OFFSET_X + map.columns() * GameMap.MAP_TILESIZE / 2;
        int cy = GameMap.OFFSET_Y + map.rows() * GameMap.MAP_TILESIZE / 2;
        g.drawString(text, cx - fm.stringWidth(text) / 2, cy);
    }

    private void drawUI(Graphics2D g) {
        g.setColor(Color.WHITE);
        g.setFont(GameFonts.arcade(18F));

        // Score (above map, left)
        g.drawString("Your Score", 48, 48);
        g.drawString("" + score, 48, 72);

        // High Score (above map, right)
        g.drawString("High Score", 248, 48);
        g.drawString("" + gameOverState.getHighScores().top().stream().findFirst().orElse(0), 248, 72);

        // Lives (below map, left)
        for (int i = 1; i < lives; i++) {
            g.drawImage(pacman.getLifeIcon(),
                    6 * GameMap.OFFSET_X - i * 24,
                    map.rows() * GameMap.MAP_TILESIZE + GameMap.OFFSET_Y,
                    null);
        }
    }

    @Override
    public void keyPressed(KeyEvent e) {
        if (phase != RoundPhase.PLAYING) return;
        switch (e.getKeyCode()) {
            case KeyEvent.VK_W -> move(Direction.UP);
            case KeyEvent.VK_S -> move(Direction.DOWN);
            case KeyEvent.VK_A -> move(Direction.LEFT);
            case KeyEvent.VK_D -> move(Direction.RIGHT);
        }
    }

    private void move(Direction direction) {
        pacman.setMoving(true);
        pacman.setDirection(direction);
    }

    @Override
    public void keyReleased(KeyEvent e) {
        pacman.setMoving(false);
    }

    private void checkCollisions() {
        for (Ghost ghost : ghostManager.getGhosts()) {
            if (deathInProgress) return; // guard
            if (overlaps(pacman, ghost)) {
                //double dist = pacman.distanceTo(ghost.getPosition().asPoint());
                //if (dist < GameMap.MAP_TILESIZE / 2.0) {
                if (ghost.isEaten()) return;
                if (ghost.isFrightened()) {
                    log.debug("Pacman eats ghost");
                    sound.play(SoundEffect.GHOST_EATEN);
                    int pts = 200 * (1 << (ghostManager.getGhosts().size() - ghostManager.isFrightened()));
                    score += pts;
                    scorePopups.spawn(pts);
                    ghost.setMode(GhostMode.EATEN);
                } else {
                    log.debug("Pacman loses a life");
                    sound.play(SoundEffect.DEATH);
                    ghostManager.setFrozen(true);
                    pacman.setState(PacMan.PacmanState.DYING);
                    deathInProgress = true;
                    // Pac-Man loses a life
                    lives--;
                    setPhase(RoundPhase.LIFE_LOST);
                }
            }
        }
    }

    private boolean overlaps(PacMan p, Ghost g) {
        // center-distance or AABB; center distance keeps the arcade feel
        double dx = p.getPosition().x - g.getPosition().x;
        double dy = p.getPosition().y - g.getPosition().y;
        double r = GameMap.MAP_TILESIZE * 1.0; // tune threshold
        return (dx * dx + dy * dy) <= r * r;
    }

    private void endGame() {
        gameOverState.setScore(score);
        gameOverState.setLevel(levelManager.getCurrentLevel().getLevel());
        gameStateManager.setState(GameStateType.GAME_OVER);
    }

    public void setScore(int score) {
        scorePopups.spawn(score);
        this.score += score;
    }
}
