#include "PlayingState.h"
#include "../core/state/StateManager.h"
#include "../gameplay/core/Game.h"
#include "../gameplay/coop/CoopGame.h"
#include "../gameplay/effects/LineEffect.h"
#include "../persistence/Scores.h"
#include "../audio/Audio.h"
#include "../audio/SoundEffect.h"
#include "../graphics/Font.h"
#include "../graphics/renderers/GameRenderer.h"
#include "../core/Settings.h"
#include "../core/Config.h"
#include "../network/CoopNetButtons.h"
#include <SDL3/SDL.h>

// File-scope transport/spawn detection state
static uint64_t s_lastPieceSequence = 0;
static bool s_pendingTransport = false;

PlayingState::PlayingState(StateContext& ctx) : State(ctx) {}

void PlayingState::onEnter() {
    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[PLAYING] Entering Playing state");
    // Initialize the game based on mode: endless/cooperate use chosen start level, challenge keeps its run state
    if (ctx.game) {
        if (ctx.game->getMode() == GameMode::Endless || ctx.game->getMode() == GameMode::Cooperate) {
            if (ctx.startLevelSelection) {
                SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[PLAYING] Resetting game with level %d", *ctx.startLevelSelection);
                const bool coopNetActive = (ctx.game->getMode() == GameMode::Cooperate) && ctx.coopNetEnabled && ctx.coopNetSession;

                // For network co-op, MenuState already performed a deterministic reset using the negotiated seed.
                // Re-resetting here would overwrite it (and will desync).
                if (!coopNetActive) {
                    ctx.game->reset(*ctx.startLevelSelection);
                    if (ctx.game->getMode() == GameMode::Cooperate && ctx.coopGame) {
                        ctx.coopGame->reset(*ctx.startLevelSelection);
                    }
                } else {
                    ctx.game->setPaused(false);
                }
            }
        } else {
            // Challenge run is prepared before entering; ensure gameplay is unpaused
            ctx.game->setPaused(false);
        }

        s_lastPieceSequence = ctx.game->getCurrentPieceSequence();
        s_pendingTransport = false;
    }
    
    // (transport state is tracked at file scope)
}

void PlayingState::onExit() {
    if (m_renderTarget) {
        SDL_DestroyTexture(m_renderTarget);
        m_renderTarget = nullptr;
    }

    // If we are leaving gameplay during network co-op, tear down the session so
    // hosting/joining again works without restarting the app.
    if (ctx.coopNetSession) {
        ctx.coopNetSession->shutdown();
        ctx.coopNetSession.reset();
    }
    ctx.coopNetEnabled = false;
    ctx.coopNetStalled = false;
    ctx.coopNetDesyncDetected = false;
    ctx.coopNetTick = 0;
    ctx.coopNetPendingButtons = 0;
}

void PlayingState::handleEvent(const SDL_Event& e) {
    if (!ctx.game) return;

    // If a transport animation is active, ignore gameplay input entirely.
    if (GameRenderer::isTransportActive()) {
        return;
    }

    const bool coopActive = ctx.game->getMode() == GameMode::Cooperate && ctx.coopGame;

    auto setExitSelection = [&](int idx) {
        if (ctx.exitPopupSelectedButton) {
            *ctx.exitPopupSelectedButton = idx;
        }
    };
    auto getExitSelection = [&]() -> int {
        return ctx.exitPopupSelectedButton ? *ctx.exitPopupSelectedButton : 1;
    };

    if (e.type != SDL_EVENT_KEY_DOWN || e.key.repeat) {
        return;
    }

    // If exit-confirm popup is visible, handle shortcuts here
    if (ctx.showExitConfirmPopup && *ctx.showExitConfirmPopup) {
        // Navigate between YES (0) and NO (1) buttons
        if (e.key.scancode == SDL_SCANCODE_LEFT || e.key.scancode == SDL_SCANCODE_UP) {
            setExitSelection(0);
            return;
        }
        if (e.key.scancode == SDL_SCANCODE_RIGHT || e.key.scancode == SDL_SCANCODE_DOWN) {
            setExitSelection(1);
            return;
        }

        // Activate selected button with Enter or Space
        if (e.key.scancode == SDL_SCANCODE_RETURN || e.key.scancode == SDL_SCANCODE_KP_ENTER || e.key.scancode == SDL_SCANCODE_SPACE) {
            const bool confirmExit = (getExitSelection() == 0);
            *ctx.showExitConfirmPopup = false;
            if (confirmExit) {
                // YES - Reset game and return to menu
                if (ctx.startLevelSelection) {
                    ctx.game->reset(*ctx.startLevelSelection);
                } else {
                    ctx.game->reset(0);
                }
                ctx.game->setPaused(false);
                if (ctx.stateManager) ctx.stateManager->setState(AppState::Menu);
            } else {
                // NO - Just close popup and resume
                ctx.game->setPaused(false);
            }
            return;
        }
        // Cancel with Esc (same as NO)
        if (e.key.scancode == SDL_SCANCODE_ESCAPE) {
            *ctx.showExitConfirmPopup = false;
            ctx.game->setPaused(false);
            setExitSelection(1);
            return;
        }
        // While modal is open, suppress other gameplay keys
        return;
    }

    // ESC key - open confirmation popup
    if (e.key.scancode == SDL_SCANCODE_ESCAPE) {
        if (ctx.showExitConfirmPopup) {
            ctx.game->setPaused(true);
            *ctx.showExitConfirmPopup = true;
            setExitSelection(1); // Default to NO for safety
        }
        return;
    }

    // Debug: skip to next challenge level (B)
    if (e.key.scancode == SDL_SCANCODE_B && ctx.game->getMode() == GameMode::Challenge) {
        ctx.game->beginNextChallengeLevel();
        // Cancel any countdown so play resumes immediately on the new level
        if (ctx.gameplayCountdownActive) *ctx.gameplayCountdownActive = false;
        if (ctx.menuPlayCountdownArmed) *ctx.menuPlayCountdownArmed = false;
        ctx.game->setPaused(false);
        return;
    }

    // Pause toggle (P) - matches classic behavior; disabled during countdown
    if (e.key.scancode == SDL_SCANCODE_P) {
        // Network co-op uses lockstep; local pause would desync/stall the peer.
        if (ctx.coopNetEnabled && ctx.coopNetSession) {
            return;
        }
        const bool countdown = (ctx.gameplayCountdownActive && *ctx.gameplayCountdownActive) ||
                               (ctx.menuPlayCountdownArmed && *ctx.menuPlayCountdownArmed);
        if (!countdown) {
            ctx.game->setPaused(!ctx.game->isPaused());
        }
        return;
    }

    // Tetris controls (only when not paused)
    if (ctx.game->isPaused()) {
        return;
    }

    if (coopActive && ctx.coopGame) {
        // Network co-op: route one-shot actions into a pending bitmask for lockstep.
        if (ctx.coopNetEnabled && ctx.coopNetSession) {
            const bool localIsLeft = ctx.coopNetLocalIsLeft;
            const SDL_Scancode sc = e.key.scancode;
            if (localIsLeft) {
                if (sc == SDL_SCANCODE_W) {
                    ctx.coopNetPendingButtons |= coopnet::RotCW;
                    return;
                }
                if (sc == SDL_SCANCODE_Q) {
                    ctx.coopNetPendingButtons |= coopnet::RotCCW;
                    return;
                }
                if (sc == SDL_SCANCODE_LSHIFT || sc == SDL_SCANCODE_E) {
                    ctx.coopNetPendingButtons |= coopnet::HardDrop;
                    return;
                }
                if (sc == SDL_SCANCODE_LCTRL) {
                    ctx.coopNetPendingButtons |= coopnet::Hold;
                    return;
                }
            } else {
                if (sc == SDL_SCANCODE_UP) {
                    const bool upIsCW = Settings::instance().isUpRotateClockwise();
                    ctx.coopNetPendingButtons |= upIsCW ? coopnet::RotCW : coopnet::RotCCW;
                    return;
                }
                if (sc == SDL_SCANCODE_RALT) {
                    ctx.coopNetPendingButtons |= coopnet::RotCCW;
                    return;
                }
                if (sc == SDL_SCANCODE_SPACE || sc == SDL_SCANCODE_RSHIFT) {
                    ctx.coopNetPendingButtons |= coopnet::HardDrop;
                    return;
                }
                if (sc == SDL_SCANCODE_RCTRL) {
                    ctx.coopNetPendingButtons |= coopnet::Hold;
                    return;
                }
            }
            // If coopNet is active, suppress local co-op direct action keys.
        }

        const bool coopAIEnabled = (ctx.coopVsAI && *ctx.coopVsAI);

        // Player 1 (left): when AI is enabled it controls the left side so
        // ignore direct player input for the left board.
        if (coopAIEnabled) {
            // Left side controlled by AI; skip left-side input handling here.
        } else {
            // Player 1 manual controls (left side)
            if (e.key.scancode == SDL_SCANCODE_W) {
                ctx.coopGame->rotate(CoopGame::PlayerSide::Left, 1);
                return;
            }
            if (e.key.scancode == SDL_SCANCODE_Q) {
                ctx.coopGame->rotate(CoopGame::PlayerSide::Left, -1);
                return;
            }
            // Hard drop (left): keep LSHIFT, also allow E for convenience.
            if (e.key.scancode == SDL_SCANCODE_LSHIFT || e.key.scancode == SDL_SCANCODE_E) {
                SoundEffectManager::instance().playSound("hard_drop", 0.7f);
                ctx.coopGame->hardDrop(CoopGame::PlayerSide::Left);
                return;
            }
            if (e.key.scancode == SDL_SCANCODE_LCTRL) {
                ctx.coopGame->holdCurrent(CoopGame::PlayerSide::Left);
                return;
            }
        }
        if (e.key.scancode == SDL_SCANCODE_UP) {
            bool upIsCW = Settings::instance().isUpRotateClockwise();
            ctx.coopGame->rotate(CoopGame::PlayerSide::Right, upIsCW ? 1 : -1);
            return;
        }
        if (e.key.scancode == SDL_SCANCODE_RALT) {
            ctx.coopGame->rotate(CoopGame::PlayerSide::Right, -1);
            return;
        }
        // Hard drop (right): SPACE is the primary key for arrow controls; keep RSHIFT as an alternate.
        if (e.key.scancode == SDL_SCANCODE_SPACE || e.key.scancode == SDL_SCANCODE_RSHIFT) {
            SoundEffectManager::instance().playSound("hard_drop", 0.7f);
            ctx.coopGame->hardDrop(CoopGame::PlayerSide::Right);
            if (coopAIEnabled) {
                // Mirror human-initiated hard-drop to AI on left
                ctx.coopGame->hardDrop(CoopGame::PlayerSide::Left);
            }
            return;
        }
        if (e.key.scancode == SDL_SCANCODE_RCTRL) {
            ctx.coopGame->holdCurrent(CoopGame::PlayerSide::Right);
            return;
        }
    } else {
        // Single-player classic controls
        // Hold / swap current piece (H)
        if (e.key.scancode == SDL_SCANCODE_H) {
            ctx.game->holdCurrent();
            return;
        }

        // Rotation (still event-based for precise timing)
        if (e.key.scancode == SDL_SCANCODE_UP) {
            // Use user setting to determine whether UP rotates clockwise
            bool upIsCW = Settings::instance().isUpRotateClockwise();
            ctx.game->rotate(upIsCW ? 1 : -1);
            return;
        }
        if (e.key.scancode == SDL_SCANCODE_X) {
            // Toggle the mapping so UP will rotate in the opposite direction
            bool current = Settings::instance().isUpRotateClockwise();
            Settings::instance().setUpRotateClockwise(!current);
            Settings::instance().save();
            // Play a subtle feedback sound if available
            SoundEffectManager::instance().playSound("menu_toggle", 0.6f);
            return;
        }

        // Hard drop (space)
        if (e.key.scancode == SDL_SCANCODE_SPACE) {
            SoundEffectManager::instance().playSound("hard_drop", 0.7f);
            ctx.game->hardDrop();
            return;
        }
    }

    // Note: Left/Right movement and soft drop are now handled by
    // ApplicationManager's update handler for proper DAS/ARR timing
}

void PlayingState::update(double frameMs) {
    if (!ctx.game) return;

    const bool coopActive = ctx.game->getMode() == GameMode::Cooperate && ctx.coopGame;

    if (coopActive) {
        // Visual effects only; gravity and movement handled from ApplicationManager for coop
        ctx.coopGame->updateVisualEffects(frameMs);
        // Update line clear effect for coop mode as well (renderer starts the effect)
        if (ctx.lineEffect && ctx.lineEffect->isActive()) {
            if (ctx.lineEffect->update(frameMs / 1000.0f)) {
                ctx.coopGame->clearCompletedLines();
            }
        }
        return;
    }

    ctx.game->updateVisualEffects(frameMs);
    // If a transport animation is active, pause gameplay updates and ignore inputs
    if (GameRenderer::isTransportActive()) {
        // Keep visual effects updating but skip gravity/timers while transport runs
        return;
    }

    // forward per-frame gameplay updates (gravity, line effects)
    if (!ctx.game->isPaused()) {
        ctx.game->tickGravity(frameMs);
        // Detect spawn event (sequence increment) and request transport effect
        uint64_t seq = ctx.game->getCurrentPieceSequence();
        if (seq != s_lastPieceSequence) {
            s_lastPieceSequence = seq;
            s_pendingTransport = true;
        }
        ctx.game->updateElapsedTime();

        if (ctx.lineEffect && ctx.lineEffect->isActive()) {
            if (ctx.lineEffect->update(frameMs / 1000.0f)) {
                ctx.game->clearCompletedLines();
            }
        }
    }
    
    // Note: Game over detection and state transition is now handled by ApplicationManager
}

void PlayingState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logicalVP) {
    if (!ctx.game) return;

    const bool coopActive = ctx.game->getMode() == GameMode::Cooperate && ctx.coopGame;

    // Get current window size
    int winW = 0, winH = 0;
    SDL_GetRenderOutputSize(renderer, &winW, &winH);

    // Create or resize render target if needed
    if (!m_renderTarget || m_targetW != winW || m_targetH != winH) {
        if (m_renderTarget) SDL_DestroyTexture(m_renderTarget);
        m_renderTarget = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, winW, winH);
        SDL_SetTextureBlendMode(m_renderTarget, SDL_BLENDMODE_BLEND);
        m_targetW = winW;
        m_targetH = winH;
    }

    bool paused = ctx.game->isPaused();
    bool exitPopup = ctx.showExitConfirmPopup && *ctx.showExitConfirmPopup;
    bool countdown = (ctx.gameplayCountdownActive && *ctx.gameplayCountdownActive) || 
                     (ctx.menuPlayCountdownArmed && *ctx.menuPlayCountdownArmed);
    bool challengeClearFx = ctx.challengeClearFxActive && *ctx.challengeClearFxActive;
    const std::vector<int>* challengeClearOrder = ctx.challengeClearFxOrder;
    double challengeClearElapsed = ctx.challengeClearFxElapsedMs ? *ctx.challengeClearFxElapsedMs : 0.0;
    double challengeClearDuration = ctx.challengeClearFxDurationMs ? *ctx.challengeClearFxDurationMs : 0.0;
    
    // Only blur if paused AND NOT in countdown (and not exit popup, though exit popup implies paused)
    // Actually, exit popup should probably still blur/dim.
    // But countdown should definitely NOT show the "PAUSED" overlay.
    bool shouldBlur = paused && !countdown && !challengeClearFx;

    auto renderNetOverlay = [&]() {
        if (!coopActive || !ctx.coopNetEnabled || !ctx.pixelFont) return;
        if (!ctx.coopNetDesyncDetected && !ctx.coopNetStalled) return;

        const char* text = ctx.coopNetDesyncDetected ? "NET: DESYNC" : "NET: STALLED";
        SDL_Color textColor = ctx.coopNetDesyncDetected ? SDL_Color{255, 230, 180, 255} : SDL_Color{255, 224, 130, 255};
        float scale = 0.75f;
        int tw = 0, th = 0;
        ctx.pixelFont->measure(text, scale, tw, th);

        SDL_BlendMode prevBlend = SDL_BLENDMODE_NONE;
        SDL_GetRenderDrawBlendMode(renderer, &prevBlend);
        SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);

        const float pad = 8.0f;
        const float x = 18.0f;
        const float y = 14.0f;
        SDL_FRect bg{ x - pad, y - pad, (float)tw + pad * 2.0f, (float)th + pad * 2.0f };
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 160);
        SDL_RenderFillRect(renderer, &bg);
        ctx.pixelFont->draw(renderer, x, y, text, scale, textColor);

        SDL_SetRenderDrawBlendMode(renderer, prevBlend);
    };

    if (shouldBlur && m_renderTarget) {
        // Render game to texture
        SDL_SetRenderTarget(renderer, m_renderTarget);
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
        SDL_RenderClear(renderer);

        // Apply the same view/scale as main.cpp uses
        SDL_SetRenderViewport(renderer, &logicalVP);
        SDL_SetRenderScale(renderer, logicalScale, logicalScale);

        // Render game content (no overlays)
        // If a transport effect was requested due to a recent spawn, start it here so
        // the renderer has the correct layout and renderer context to compute coords.
        if (!coopActive && s_pendingTransport) {
            GameRenderer::startTransportEffectForGame(ctx.game, ctx.blocksTex, 1200.0f, 1000.0f, logicalScale, (float)winW, (float)winH, 0.4f);
            s_pendingTransport = false;
        }

        if (coopActive && ctx.coopGame) {
            GameRenderer::renderCoopPlayingState(
                renderer,
                ctx.coopGame,
                ctx.pixelFont,
                ctx.lineEffect,
                ctx.blocksTex,
                ctx.statisticsPanelTex,
                ctx.scorePanelTex,
                ctx.nextPanelTex,
                ctx.holdPanelTex,
                paused,
                1200.0f,
                1000.0f,
                logicalScale,
                (float)winW,
                (float)winH
            );
        } else {
            GameRenderer::renderPlayingState(
                renderer,
                ctx.game,
                ctx.pixelFont,
                ctx.lineEffect,
                ctx.blocksTex,
                ctx.asteroidsTex,
                ctx.statisticsPanelTex,
                ctx.scorePanelTex,
                ctx.nextPanelTex,
                ctx.holdPanelTex,
                countdown,
                1200.0f, // LOGICAL_W
                1000.0f, // LOGICAL_H
                logicalScale,
                (float)winW,
                (float)winH,
                challengeClearFx,
                challengeClearOrder,
                challengeClearElapsed,
                challengeClearDuration,
                countdown ? nullptr : ctx.challengeStoryText,
                countdown ? 0.0f : (ctx.challengeStoryAlpha ? *ctx.challengeStoryAlpha : 0.0f)
            );
        }

        // Reset to screen
        SDL_SetRenderTarget(renderer, nullptr);
        
        // Draw blurred texture
        SDL_Rect oldVP;
        SDL_GetRenderViewport(renderer, &oldVP);
        float oldSX, oldSY;
        SDL_GetRenderScale(renderer, &oldSX, &oldSY);
        
        SDL_SetRenderViewport(renderer, nullptr);
        SDL_SetRenderScale(renderer, 1.0f, 1.0f);
        
        SDL_FRect dst{0, 0, (float)winW, (float)winH};
        
        // Blur pass (accumulate multiple offset copies)
        int offset = Config::Visuals::PAUSE_BLUR_OFFSET;
        int iterations = Config::Visuals::PAUSE_BLUR_ITERATIONS;
        
        // Base layer
        SDL_SetTextureAlphaMod(m_renderTarget, Config::Visuals::PAUSE_BLUR_ALPHA); 
        SDL_RenderTexture(renderer, m_renderTarget, nullptr, &dst);
        
        // Accumulate offset layers
        for (int i = 1; i <= iterations; ++i) {
            float currentOffset = (float)(offset * i);
            
            SDL_FRect d1 = dst; d1.x -= currentOffset; d1.y -= currentOffset;
            SDL_RenderTexture(renderer, m_renderTarget, nullptr, &d1);
            
            SDL_FRect d2 = dst; d2.x += currentOffset; d2.y -= currentOffset;
            SDL_RenderTexture(renderer, m_renderTarget, nullptr, &d2);
            
            SDL_FRect d3 = dst; d3.x -= currentOffset; d3.y += currentOffset;
            SDL_RenderTexture(renderer, m_renderTarget, nullptr, &d3);
            
            SDL_FRect d4 = dst; d4.x += currentOffset; d4.y += currentOffset;
            SDL_RenderTexture(renderer, m_renderTarget, nullptr, &d4);
        }
        
        SDL_SetTextureAlphaMod(m_renderTarget, 255);

        // Restore state
        SDL_SetRenderViewport(renderer, &oldVP);
        SDL_SetRenderScale(renderer, oldSX, oldSY);

        // Net overlay (on top of blurred game, under pause/exit overlays)
        renderNetOverlay();

        // Draw overlays
        if (exitPopup) {
            GameRenderer::renderExitPopup(
                renderer,
                ctx.pixelFont,
                (float)winW,
                (float)winH,
                logicalScale,
                (ctx.exitPopupSelectedButton ? *ctx.exitPopupSelectedButton : 1)
            );
        } else {
            GameRenderer::renderPauseOverlay(
                renderer,
                ctx.pixelFont,
                (float)winW,
                (float)winH,
                logicalScale
            );
        }
        
    } else {
        // Render normally directly to screen
        if (!coopActive && s_pendingTransport) {
            GameRenderer::startTransportEffectForGame(ctx.game, ctx.blocksTex, 1200.0f, 1000.0f, logicalScale, (float)winW, (float)winH, 0.4f);
            s_pendingTransport = false;
        }

        if (coopActive && ctx.coopGame) {
            GameRenderer::renderCoopPlayingState(
                renderer,
                ctx.coopGame,
                ctx.pixelFont,
                ctx.lineEffect,
                ctx.blocksTex,
                ctx.statisticsPanelTex,
                ctx.scorePanelTex,
                ctx.nextPanelTex,
                ctx.holdPanelTex,
                paused,
                1200.0f,
                1000.0f,
                logicalScale,
                (float)winW,
                (float)winH
            );

            // Net overlay (on top of coop HUD)
            renderNetOverlay();
        } else {
            GameRenderer::renderPlayingState(
                renderer,
                ctx.game,
                ctx.pixelFont,
                ctx.lineEffect,
                ctx.blocksTex,
                ctx.asteroidsTex,
                ctx.statisticsPanelTex,
                ctx.scorePanelTex,
                ctx.nextPanelTex,
                ctx.holdPanelTex,
                countdown,
                1200.0f,
                1000.0f,
                logicalScale,
                (float)winW,
                (float)winH,
                challengeClearFx,
                challengeClearOrder,
                challengeClearElapsed,
                challengeClearDuration,
                countdown ? nullptr : ctx.challengeStoryText,
                countdown ? 0.0f : (ctx.challengeStoryAlpha ? *ctx.challengeStoryAlpha : 0.0f)
            );
        }
    }
}
