"""
Production game views for the play screen.

Provides the main gameplay interface with 3-column layout:
- Left: Character stats + action buttons
- Middle: Narrative + location context
- Right: Accordions for history, quests, NPCs, map
"""

from flask import Blueprint, render_template, request, redirect, url_for
import structlog

from ..utils.api_client import get_api_client, APIError, APINotFoundError
from ..utils.auth import require_auth_web as require_auth, get_current_user

logger = structlog.get_logger(__name__)

game_bp = Blueprint('game', __name__, url_prefix='/play')


# ===== Action Definitions =====
# Actions organized by tier - context filtering happens in template
# These are static definitions, available actions come from API session state

DEFAULT_ACTIONS = {
    'free': [
        {'prompt_id': 'ask_locals', 'display_text': 'Ask Locals for Information', 'icon': 'chat', 'context': ['town', 'tavern']},
        {'prompt_id': 'search_supplies', 'display_text': 'Search for Supplies', 'icon': 'search', 'context': ['any'], 'cooldown': 2},
        {'prompt_id': 'rest_recover', 'display_text': 'Rest and Recover', 'icon': 'bed', 'context': ['town', 'tavern', 'safe_area'], 'cooldown': 3}
    ],
    'premium': [
        {'prompt_id': 'investigate_suspicious', 'display_text': 'Investigate Suspicious Activity', 'icon': 'magnifying_glass', 'context': ['any']},
        {'prompt_id': 'follow_lead', 'display_text': 'Follow a Lead', 'icon': 'footprints', 'context': ['any']},
        {'prompt_id': 'make_camp', 'display_text': 'Make Camp', 'icon': 'campfire', 'context': ['wilderness'], 'cooldown': 5}
    ],
    'elite': [
        {'prompt_id': 'consult_texts', 'display_text': 'Consult Ancient Texts', 'icon': 'book', 'context': ['library', 'town'], 'cooldown': 3},
        {'prompt_id': 'commune_nature', 'display_text': 'Commune with Nature', 'icon': 'leaf', 'context': ['wilderness'], 'cooldown': 4},
        {'prompt_id': 'seek_audience', 'display_text': 'Seek Audience with Authorities', 'icon': 'crown', 'context': ['town'], 'cooldown': 5}
    ]
}


def _get_user_tier(client) -> str:
    """Get user's subscription tier from API or session."""
    try:
        # Try to get user info which includes tier
        user_response = client.get('/api/v1/auth/me')
        user_data = user_response.get('result', {})
        return user_data.get('tier', 'free')
    except (APIError, APINotFoundError):
        # Default to free tier if we can't determine
        return 'free'


def _build_location_from_game_state(game_state: dict) -> dict:
    """Build location dict from game_state data."""
    return {
        'location_id': game_state.get('current_location_id') or game_state.get('current_location'),
        'name': game_state.get('current_location_name', game_state.get('current_location', 'Unknown')),
        'location_type': game_state.get('location_type', 'unknown'),
        'region': game_state.get('region', ''),
        'description': game_state.get('location_description', ''),
        'ambient_description': game_state.get('ambient_description', '')
    }


def _build_character_from_api(char_data: dict) -> dict:
    """
    Build character dict from API character response.

    Always returns a dict with all required fields, using sensible defaults
    if the API data is incomplete or empty.
    """
    if not char_data:
        char_data = {}

    # Extract stats from base_stats or stats, with defaults
    stats = char_data.get('base_stats', char_data.get('stats', {}))
    if not stats:
        stats = {
            'strength': 10,
            'dexterity': 10,
            'constitution': 10,
            'intelligence': 10,
            'wisdom': 10,
            'charisma': 10
        }

    # Calculate HP/MP - these may come from different places
    # For now use defaults based on level/constitution
    level = char_data.get('level', 1)
    constitution = stats.get('constitution', 10)
    intelligence = stats.get('intelligence', 10)

    # Simple HP/MP calculation (can be refined based on game rules)
    max_hp = max(1, 50 + (level * 10) + ((constitution - 10) * level))
    max_mp = max(1, 20 + (level * 5) + ((intelligence - 10) * level // 2))

    # Get class name from various possible locations
    class_name = 'Unknown'
    if char_data.get('player_class'):
        class_name = char_data['player_class'].get('name', 'Unknown')
    elif char_data.get('class_name'):
        class_name = char_data['class_name']
    elif char_data.get('class'):
        class_name = char_data['class'].replace('_', ' ').title()

    return {
        'character_id': char_data.get('character_id', ''),
        'name': char_data.get('name', 'Unknown Hero'),
        'class_name': class_name,
        'level': level,
        'current_hp': char_data.get('current_hp', max_hp),
        'max_hp': char_data.get('max_hp', max_hp),
        'current_mp': char_data.get('current_mp', max_mp),
        'max_mp': char_data.get('max_mp', max_mp),
        'stats': stats,
        'equipped': char_data.get('equipped', {}),
        'inventory': char_data.get('inventory', []),
        'gold': char_data.get('gold', 0),
        'experience': char_data.get('experience', 0)
    }


# ===== Main Routes =====

@game_bp.route('/session/<session_id>')
@require_auth
def play_session(session_id: str):
    """
    Production play screen for a game session.

    Displays 3-column layout with character panel, narrative area,
    and sidebar accordions for history/quests/NPCs/map.
    """
    client = get_api_client()

    try:
        # Get session state (includes game_state with location info)
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})

        # Extract game state and build location info
        game_state = session_data.get('game_state', {})
        location = _build_location_from_game_state(game_state)

        # Get character details - always build a valid character dict
        character_id = session_data.get('character_id')
        char_data = {}
        if character_id:
            try:
                char_response = client.get(f'/api/v1/characters/{character_id}')
                char_data = char_response.get('result', {})
            except (APINotFoundError, APIError) as e:
                logger.warning("failed_to_load_character", character_id=character_id, error=str(e))

        # Always build character with defaults for any missing fields
        character = _build_character_from_api(char_data)

        # Get session history (last DM response for display)
        history = []
        dm_response = "Your adventure awaits..."
        try:
            history_response = client.get(f'/api/v1/sessions/{session_id}/history?limit=10')
            history_data = history_response.get('result', {})
            history = history_data.get('history', [])
            # Get the most recent DM response for the main narrative panel
            if history:
                dm_response = history[0].get('dm_response', dm_response)
        except (APINotFoundError, APIError) as e:
            logger.warning("failed_to_load_history", session_id=session_id, error=str(e))

        # Get NPCs at current location
        npcs = []
        current_location_id = location.get('location_id')
        if current_location_id:
            try:
                npcs_response = client.get(f'/api/v1/npcs/at-location/{current_location_id}')
                npcs = npcs_response.get('result', {}).get('npcs', [])
            except (APINotFoundError, APIError) as e:
                logger.debug("no_npcs_at_location", location_id=current_location_id, error=str(e))

        # Get available travel destinations (discovered locations)
        discovered_locations = []
        try:
            travel_response = client.get(f'/api/v1/travel/available?session_id={session_id}')
            travel_result = travel_response.get('result', {})
            discovered_locations = travel_result.get('available_locations', [])
            # Mark current location
            for loc in discovered_locations:
                loc['is_current'] = loc.get('location_id') == current_location_id
        except (APINotFoundError, APIError) as e:
            logger.debug("failed_to_load_travel_destinations", session_id=session_id, error=str(e))

        # Get quests (from character's active_quests or session)
        quests = game_state.get('active_quests', [])
        # If quests are just IDs, we could expand them, but for now use what we have

        # Get user tier
        user_tier = _get_user_tier(client)

        # Fetch usage info for daily turn limits
        usage_info = {}
        try:
            usage_response = client.get("/api/v1/usage")
            usage_info = usage_response.get('result', {})
        except (APINotFoundError, APIError) as e:
            logger.debug("could_not_fetch_usage_info", error=str(e))

        # Build session object for template
        session = {
            'session_id': session_id,
            'turn_number': session_data.get('turn_number', 0),
            'status': session_data.get('status', 'active')
        }

        return render_template(
            'game/play.html',
            session_id=session_id,
            session=session,
            character=character,
            location=location,
            dm_response=dm_response,
            history=history,
            quests=quests,
            npcs=npcs,
            discovered_locations=discovered_locations,
            actions=DEFAULT_ACTIONS,
            user_tier=user_tier,
            # Usage display variables
            remaining=usage_info.get('remaining', 0),
            daily_limit=usage_info.get('daily_limit', 0),
            is_limited=usage_info.get('is_limited', False),
            is_unlimited=usage_info.get('is_unlimited', False),
            reset_time=usage_info.get('reset_time', '')
        )

    except APINotFoundError:
        logger.warning("session_not_found", session_id=session_id)
        return render_template('errors/404.html', message=f"Session {session_id} not found"), 404
    except APIError as e:
        logger.error("failed_to_load_play_session", session_id=session_id, error=str(e))
        return render_template('errors/500.html', message=str(e)), 500


# ===== Partial Refresh Routes =====

@game_bp.route('/session/<session_id>/character-panel')
@require_auth
def character_panel(session_id: str):
    """Refresh character stats and actions panel."""
    client = get_api_client()

    try:
        # Get session to find character and location
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        game_state = session_data.get('game_state', {})
        location = _build_location_from_game_state(game_state)

        # Get character - always build valid character dict
        char_data = {}
        character_id = session_data.get('character_id')
        if character_id:
            try:
                char_response = client.get(f'/api/v1/characters/{character_id}')
                char_data = char_response.get('result', {})
            except (APINotFoundError, APIError):
                pass

        character = _build_character_from_api(char_data)
        user_tier = _get_user_tier(client)

        # Fetch usage info for daily turn limits
        usage_info = {}
        try:
            usage_response = client.get("/api/v1/usage")
            usage_info = usage_response.get('result', {})
        except (APINotFoundError, APIError) as e:
            logger.debug("could_not_fetch_usage_info", error=str(e))

        return render_template(
            'game/partials/character_panel.html',
            session_id=session_id,
            character=character,
            location=location,
            actions=DEFAULT_ACTIONS,
            user_tier=user_tier,
            # Usage display variables
            remaining=usage_info.get('remaining', 0),
            daily_limit=usage_info.get('daily_limit', 0),
            is_limited=usage_info.get('is_limited', False),
            is_unlimited=usage_info.get('is_unlimited', False),
            reset_time=usage_info.get('reset_time', '')
        )
    except APIError as e:
        logger.error("failed_to_refresh_character_panel", session_id=session_id, error=str(e))
        return f'<div class="error">Failed to load character panel: {e}</div>', 500


@game_bp.route('/session/<session_id>/narrative')
@require_auth
def narrative_panel(session_id: str):
    """Refresh narrative content panel."""
    client = get_api_client()

    try:
        # Get session state
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        game_state = session_data.get('game_state', {})
        location = _build_location_from_game_state(game_state)

        # Get latest DM response from history
        dm_response = "Your adventure awaits..."
        try:
            history_response = client.get(f'/api/v1/sessions/{session_id}/history?limit=1')
            history_data = history_response.get('result', {})
            history = history_data.get('history', [])
            if history:
                dm_response = history[0].get('dm_response', dm_response)
        except (APINotFoundError, APIError):
            pass

        session = {
            'session_id': session_id,
            'turn_number': session_data.get('turn_number', 0),
            'status': session_data.get('status', 'active')
        }

        return render_template(
            'game/partials/narrative_panel.html',
            session_id=session_id,
            session=session,
            location=location,
            dm_response=dm_response
        )
    except APIError as e:
        logger.error("failed_to_refresh_narrative", session_id=session_id, error=str(e))
        return f'<div class="error">Failed to load narrative: {e}</div>', 500


@game_bp.route('/session/<session_id>/history')
@require_auth
def history_accordion(session_id: str):
    """Refresh history accordion content."""
    client = get_api_client()

    try:
        history_response = client.get(f'/api/v1/sessions/{session_id}/history?limit=20')
        history_data = history_response.get('result', {})
        history = history_data.get('history', [])

        return render_template(
            'game/partials/sidebar_history.html',
            session_id=session_id,
            history=history
        )
    except APIError as e:
        logger.error("failed_to_refresh_history", session_id=session_id, error=str(e))
        return f'<div class="error">Failed to load history: {e}</div>', 500


@game_bp.route('/session/<session_id>/quests')
@require_auth
def quests_accordion(session_id: str):
    """Refresh quests accordion content."""
    client = get_api_client()

    try:
        # Get session to access game_state.active_quests
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        game_state = session_data.get('game_state', {})
        quests = game_state.get('active_quests', [])

        return render_template(
            'game/partials/sidebar_quests.html',
            session_id=session_id,
            quests=quests
        )
    except APIError as e:
        logger.error("failed_to_refresh_quests", session_id=session_id, error=str(e))
        return f'<div class="error">Failed to load quests: {e}</div>', 500


@game_bp.route('/session/<session_id>/npcs')
@require_auth
def npcs_accordion(session_id: str):
    """Refresh NPCs accordion content."""
    client = get_api_client()

    try:
        # Get session to find current location
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        game_state = session_data.get('game_state', {})
        current_location_id = game_state.get('current_location_id') or game_state.get('current_location')

        # Get NPCs at location
        npcs = []
        if current_location_id:
            try:
                npcs_response = client.get(f'/api/v1/npcs/at-location/{current_location_id}')
                npcs = npcs_response.get('result', {}).get('npcs', [])
            except (APINotFoundError, APIError):
                pass

        return render_template(
            'game/partials/sidebar_npcs.html',
            session_id=session_id,
            npcs=npcs
        )
    except APIError as e:
        logger.error("failed_to_refresh_npcs", session_id=session_id, error=str(e))
        return f'<div class="error">Failed to load NPCs: {e}</div>', 500


@game_bp.route('/session/<session_id>/map')
@require_auth
def map_accordion(session_id: str):
    """Refresh map accordion content."""
    client = get_api_client()

    try:
        # Get session for current location
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        game_state = session_data.get('game_state', {})
        current_location = _build_location_from_game_state(game_state)
        current_location_id = current_location.get('location_id')

        # Get available travel destinations
        discovered_locations = []
        try:
            travel_response = client.get(f'/api/v1/travel/available?session_id={session_id}')
            travel_result = travel_response.get('result', {})
            discovered_locations = travel_result.get('available_locations', [])
            # Mark current location
            for loc in discovered_locations:
                loc['is_current'] = loc.get('location_id') == current_location_id
        except (APINotFoundError, APIError):
            pass

        return render_template(
            'game/partials/sidebar_map.html',
            session_id=session_id,
            discovered_locations=discovered_locations,
            current_location=current_location
        )
    except APIError as e:
        logger.error("failed_to_refresh_map", session_id=session_id, error=str(e))
        return f'<div class="error">Failed to load map: {e}</div>', 500


# ===== Action Routes =====

@game_bp.route('/session/<session_id>/action', methods=['POST'])
@require_auth
def take_action(session_id: str):
    """
    Submit an action - returns job polling partial.

    Handles two action types:
    - 'button': Predefined action via prompt_id
    - 'custom': Free-form player text input
    """
    client = get_api_client()
    action_type = request.form.get('action_type', 'button')

    try:
        # Build payload based on action type
        payload = {'action_type': action_type}

        if action_type == 'text' or action_type == 'custom':
            # Free-form text action from player input
            action_text = request.form.get('action_text', request.form.get('custom_text', '')).strip()
            if not action_text:
                return '<div class="dm-response error">Please enter an action.</div>', 400

            logger.info("Player text action submitted",
                       session_id=session_id,
                       action_text=action_text[:100])

            payload['action_type'] = 'custom'
            payload['custom_text'] = action_text
            player_action = action_text
        else:
            # Button action via prompt_id
            prompt_id = request.form.get('prompt_id')
            if not prompt_id:
                return '<div class="dm-response error">No action selected.</div>', 400

            logger.info("Player button action submitted",
                       session_id=session_id,
                       prompt_id=prompt_id)

            payload['prompt_id'] = prompt_id
            player_action = None  # Will display prompt_id display text

        # POST to API
        response = client.post(f'/api/v1/sessions/{session_id}/action', payload)
        result = response.get('result', {})
        job_id = result.get('job_id')

        if not job_id:
            # Immediate response (shouldn't happen, but handle it)
            dm_response = result.get('dm_response', 'Action completed.')
            return render_template(
                'game/partials/dm_response.html',
                session_id=session_id,
                dm_response=dm_response
            )

        # Return polling partial
        return render_template(
            'game/partials/job_polling.html',
            session_id=session_id,
            job_id=job_id,
            status=result.get('status', 'queued'),
            player_action=player_action
        )

    except APIError as e:
        logger.error("failed_to_take_action", session_id=session_id, error=str(e))
        return f'<div class="dm-response error">Action failed: {e}</div>', 500


@game_bp.route('/session/<session_id>/job/<job_id>')
@require_auth
def poll_job(session_id: str, job_id: str):
    """Poll job status - returns updated partial."""
    client = get_api_client()

    # Get hx_target and hx_swap from query params (passed through from original request)
    hx_target = request.args.get('_hx_target')
    hx_swap = request.args.get('_hx_swap')

    try:
        response = client.get(f'/api/v1/jobs/{job_id}/status')
        result = response.get('result', {})
        status = result.get('status', 'unknown')

        if status == 'completed':
            # Job done - check for NPC dialogue vs story action
            nested_result = result.get('result', {})
            if nested_result.get('context_type') == 'npc_dialogue':
                # NPC dialogue response - return dialogue partial
                return render_template(
                    'game/partials/npc_dialogue_response.html',
                    npc_name=nested_result.get('npc_name', 'NPC'),
                    character_name=nested_result.get('character_name', 'You'),
                    conversation_history=nested_result.get('conversation_history', []),
                    player_line=nested_result.get('player_line', ''),
                    dialogue=nested_result.get('dialogue', 'No response'),
                    session_id=session_id
                )
            else:
                # Standard DM response
                dm_response = result.get('dm_response', nested_result.get('dm_response', 'No response'))
                return render_template(
                    'game/partials/dm_response.html',
                    session_id=session_id,
                    dm_response=dm_response
                )

        elif status in ('failed', 'error'):
            error_msg = result.get('error', 'Unknown error occurred')
            return f'<div class="dm-response error">Action failed: {error_msg}</div>'

        else:
            # Still processing - return polling partial to continue
            # Pass through hx_target and hx_swap to maintain targeting
            return render_template(
                'game/partials/job_polling.html',
                session_id=session_id,
                job_id=job_id,
                status=status,
                hx_target=hx_target,
                hx_swap=hx_swap
            )

    except APIError as e:
        logger.error("failed_to_poll_job", job_id=job_id, session_id=session_id, error=str(e))
        return f'<div class="dm-response error">Failed to check job status: {e}</div>', 500


# ===== Modal Routes =====

@game_bp.route('/session/<session_id>/equipment-modal')
@require_auth
def equipment_modal(session_id: str):
    """Get equipment modal with character's gear."""
    client = get_api_client()

    try:
        # Get session to find character
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        character_id = session_data.get('character_id')

        character = {}
        if character_id:
            char_response = client.get(f'/api/v1/characters/{character_id}')
            char_data = char_response.get('result', {})
            character = _build_character_from_api(char_data)

        return render_template(
            'game/partials/equipment_modal.html',
            session_id=session_id,
            character=character
        )
    except APIError as e:
        logger.error("failed_to_load_equipment_modal", session_id=session_id, error=str(e))
        return f'''
        <div class="modal-overlay" onclick="this.remove()">
            <div class="modal-content" onclick="event.stopPropagation()">
                <h3>Equipment</h3>
                <div class="error">Failed to load equipment: {e}</div>
                <button class="modal-close" onclick="this.closest('.modal-overlay').remove()">Close</button>
            </div>
        </div>
        '''


@game_bp.route('/session/<session_id>/travel-modal')
@require_auth
def travel_modal(session_id: str):
    """Get travel modal with available destinations."""
    client = get_api_client()

    try:
        # Get available travel destinations
        response = client.get(f'/api/v1/travel/available?session_id={session_id}')
        result = response.get('result', {})
        available_locations = result.get('available_locations', [])
        current_location_id = result.get('current_location')

        # Get current location details from session
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        game_state = session_data.get('game_state', {})
        current_location = _build_location_from_game_state(game_state)

        # Filter out current location from destinations
        destinations = [loc for loc in available_locations if loc.get('location_id') != current_location_id]

        return render_template(
            'game/partials/travel_modal.html',
            session_id=session_id,
            destinations=destinations,
            current_location=current_location
        )
    except APIError as e:
        logger.error("failed_to_load_travel_modal", session_id=session_id, error=str(e))
        return f'''
        <div class="modal-overlay" onclick="this.remove()">
            <div class="modal-content" onclick="event.stopPropagation()">
                <h3>Travel</h3>
                <div class="error">Failed to load travel options: {e}</div>
                <button class="modal-close" onclick="this.closest('.modal-overlay').remove()">Close</button>
            </div>
        </div>
        '''


@game_bp.route('/session/<session_id>/travel', methods=['POST'])
@require_auth
def do_travel(session_id: str):
    """Execute travel to location - returns job polling partial or immediate response."""
    client = get_api_client()
    location_id = request.form.get('location_id')

    if not location_id:
        return '<div class="error">No destination selected.</div>', 400

    try:
        response = client.post('/api/v1/travel', {
            'session_id': session_id,
            'location_id': location_id
        })
        result = response.get('result', {})

        # Check if travel triggers a job (narrative generation)
        job_id = result.get('job_id')
        if job_id:
            # Close modal and return job polling partial
            return f'''
            <script>document.querySelector('.modal-overlay')?.remove();</script>
            ''' + render_template(
                'game/partials/job_polling.html',
                job_id=job_id,
                session_id=session_id,
                status='queued'
            )

        # Immediate response (no AI generation)
        narrative = result.get('narrative', result.get('description', 'You arrive at your destination.'))
        location_name = result.get('location_name', 'Unknown Location')

        # Close modal and update response area
        return f'''
        <script>document.querySelector('.modal-overlay')?.remove();</script>
        <div class="dm-response">
            <strong>Arrived at {location_name}</strong><br><br>
            {narrative}
        </div>
        ''' + render_template(
            'game/partials/dm_response.html',
            session_id=session_id,
            dm_response=f"**Arrived at {location_name}**\n\n{narrative}"
        )

    except APIError as e:
        logger.error("failed_to_travel", session_id=session_id, location_id=location_id, error=str(e))
        return f'<div class="error">Travel failed: {e}</div>', 500


@game_bp.route('/session/<session_id>/monster-modal')
@require_auth
def monster_modal(session_id: str):
    """
    Get monster selection modal with encounter options.

    Fetches random encounter groups appropriate for the current location
    and character level from the API.
    """
    client = get_api_client()

    try:
        # Get encounter options from API
        response = client.get(f'/api/v1/combat/encounters?session_id={session_id}')
        result = response.get('result', {})

        location_name = result.get('location_name', 'Unknown Area')
        encounters = result.get('encounters', [])

        return render_template(
            'game/partials/monster_modal.html',
            session_id=session_id,
            location_name=location_name,
            encounters=encounters
        )

    except APINotFoundError as e:
        # No enemies found for this location
        return render_template(
            'game/partials/monster_modal.html',
            session_id=session_id,
            location_name='this area',
            encounters=[]
        )
    except APIError as e:
        logger.error("failed_to_load_monster_modal", session_id=session_id, error=str(e))
        return f'''
        <div class="modal-overlay" onclick="if(event.target === this) closeModal()">
            <div class="modal-content">
                <div class="modal-header">
                    <h3 class="modal-title">⚔️ Search for Monsters</h3>
                    <button class="modal-close" onclick="closeModal()">&times;</button>
                </div>
                <div class="modal-body">
                    <div class="error">Failed to search for monsters: {e}</div>
                </div>
                <div class="modal-footer">
                    <button class="btn btn-secondary" onclick="closeModal()">Close</button>
                </div>
            </div>
        </div>
        '''


@game_bp.route('/session/<session_id>/combat/start', methods=['POST'])
@require_auth
def start_combat(session_id: str):
    """
    Start combat with selected enemies.

    Called when player selects an encounter from the monster modal.
    Initiates combat via API and redirects to combat UI.

    If there's already an active combat session, shows a conflict modal
    allowing the user to resume or abandon the existing combat.
    """
    from flask import make_response

    client = get_api_client()

    # Get enemy_ids from request
    # HTMX hx-vals sends as form data (not JSON), where arrays become multiple values
    if request.is_json:
        enemy_ids = request.json.get('enemy_ids', [])
    else:
        # Form data: array values come as multiple entries with the same key
        enemy_ids = request.form.getlist('enemy_ids')

    if not enemy_ids:
        return '<div class="error">No enemies selected.</div>', 400

    try:
        # Start combat via API
        response = client.post('/api/v1/combat/start', {
            'session_id': session_id,
            'enemy_ids': enemy_ids
        })
        result = response.get('result', {})
        encounter_id = result.get('encounter_id')

        if not encounter_id:
            logger.error("combat_start_no_encounter_id", session_id=session_id)
            return '<div class="error">Failed to start combat - no encounter ID returned.</div>', 500

        logger.info("combat_started_from_modal",
                   session_id=session_id,
                   encounter_id=encounter_id,
                   enemy_count=len(enemy_ids))

        # Close modal and redirect to combat page
        resp = make_response('')
        resp.headers['HX-Redirect'] = url_for('combat.combat_view', session_id=session_id)
        return resp

    except APIError as e:
        # Check if this is an "already in combat" error
        error_str = str(e)
        if 'already in combat' in error_str.lower() or 'ALREADY_IN_COMBAT' in error_str:
            # Fetch existing combat info and show conflict modal
            try:
                check_response = client.get(f'/api/v1/combat/{session_id}/check')
                combat_info = check_response.get('result', {})

                if combat_info.get('has_active_combat'):
                    return render_template(
                        'game/partials/combat_conflict_modal.html',
                        session_id=session_id,
                        combat_info=combat_info,
                        pending_enemy_ids=enemy_ids
                    )
            except APIError:
                pass  # Fall through to generic error

        logger.error("failed_to_start_combat", session_id=session_id, error=str(e))
        return f'<div class="error">Failed to start combat: {e}</div>', 500


@game_bp.route('/session/<session_id>/combat/check', methods=['GET'])
@require_auth
def check_combat_status(session_id: str):
    """
    Check if the session has an active combat.

    Returns JSON with combat status that can be used by HTMX
    to decide whether to show the monster modal or conflict modal.
    """
    client = get_api_client()

    try:
        response = client.get(f'/api/v1/combat/{session_id}/check')
        result = response.get('result', {})
        return result

    except APIError as e:
        logger.error("failed_to_check_combat", session_id=session_id, error=str(e))
        return {'has_active_combat': False, 'error': str(e)}


@game_bp.route('/session/<session_id>/combat/abandon', methods=['POST'])
@require_auth
def abandon_combat(session_id: str):
    """
    Abandon an existing combat session.

    Called when player chooses to abandon their current combat
    in order to start a fresh one.
    """
    client = get_api_client()

    try:
        response = client.post(f'/api/v1/combat/{session_id}/abandon', {})
        result = response.get('result', {})

        if result.get('success'):
            logger.info("combat_abandoned", session_id=session_id)
            # Return success - the frontend will then try to start new combat
            return render_template(
                'game/partials/combat_abandoned_success.html',
                session_id=session_id,
                message="Combat abandoned. You can now start a new encounter."
            )
        else:
            return '<div class="error">No active combat to abandon.</div>', 400

    except APIError as e:
        logger.error("failed_to_abandon_combat", session_id=session_id, error=str(e))
        return f'<div class="error">Failed to abandon combat: {e}</div>', 500


@game_bp.route('/session/<session_id>/combat/abandon-and-start', methods=['POST'])
@require_auth
def abandon_and_start_combat(session_id: str):
    """
    Abandon existing combat and start a new one in a single action.

    This is a convenience endpoint that combines abandon + start
    for a smoother user experience in the conflict modal.
    """
    from flask import make_response

    client = get_api_client()

    # Get enemy_ids from request
    if request.is_json:
        enemy_ids = request.json.get('enemy_ids', [])
    else:
        enemy_ids = request.form.getlist('enemy_ids')

    if not enemy_ids:
        return '<div class="error">No enemies selected.</div>', 400

    try:
        # First abandon the existing combat
        abandon_response = client.post(f'/api/v1/combat/{session_id}/abandon', {})
        abandon_result = abandon_response.get('result', {})

        if not abandon_result.get('success'):
            # No combat to abandon, but that's fine - proceed with start
            logger.info("no_combat_to_abandon", session_id=session_id)

        # Now start the new combat
        start_response = client.post('/api/v1/combat/start', {
            'session_id': session_id,
            'enemy_ids': enemy_ids
        })
        result = start_response.get('result', {})
        encounter_id = result.get('encounter_id')

        if not encounter_id:
            logger.error("combat_start_no_encounter_id_after_abandon", session_id=session_id)
            return '<div class="error">Failed to start combat - no encounter ID returned.</div>', 500

        logger.info("combat_started_after_abandon",
                   session_id=session_id,
                   encounter_id=encounter_id,
                   enemy_count=len(enemy_ids))

        # Close modal and redirect to combat page
        resp = make_response('')
        resp.headers['HX-Redirect'] = url_for('combat.combat_view', session_id=session_id)
        return resp

    except APIError as e:
        logger.error("failed_to_abandon_and_start_combat", session_id=session_id, error=str(e))
        return f'<div class="error">Failed to start combat: {e}</div>', 500


@game_bp.route('/session/<session_id>/npc/<npc_id>')
@require_auth
def npc_chat_page(session_id: str, npc_id: str):
    """
    Dedicated NPC chat page (mobile-friendly full page view).
    Used on mobile devices for better UX.
    """
    client = get_api_client()

    try:
        # Get session to find character
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        character_id = session_data.get('character_id')

        # Get NPC details with relationship info
        npc_response = client.get(f'/api/v1/npcs/{npc_id}?character_id={character_id}')
        npc_data = npc_response.get('result', {})

        npc = {
            'npc_id': npc_data.get('npc_id'),
            'name': npc_data.get('name'),
            'role': npc_data.get('role'),
            'appearance': npc_data.get('appearance', {}).get('brief', ''),
            'tags': npc_data.get('tags', []),
            'image_url': npc_data.get('image_url')
        }

        # Get relationship info
        interaction_summary = npc_data.get('interaction_summary', {})
        relationship_level = interaction_summary.get('relationship_level', 50)
        interaction_count = interaction_summary.get('interaction_count', 0)

        # Conversation history would come from character's npc_interactions
        # For now, we'll leave it empty - the API returns it in dialogue responses
        conversation_history = []

        return render_template(
            'game/npc_chat_page.html',
            session_id=session_id,
            npc=npc,
            conversation_history=conversation_history,
            relationship_level=relationship_level,
            interaction_count=interaction_count
        )

    except APINotFoundError:
        return render_template('errors/404.html', message="NPC not found"), 404
    except APIError as e:
        logger.error("failed_to_load_npc_chat_page", session_id=session_id, npc_id=npc_id, error=str(e))
        return render_template('errors/500.html', message=f"Failed to load NPC: {e}"), 500


@game_bp.route('/session/<session_id>/npc/<npc_id>/chat')
@require_auth
def npc_chat_modal(session_id: str, npc_id: str):
    """
    Get NPC chat modal with conversation history.
    Used on desktop for modal overlay experience.
    """
    client = get_api_client()

    try:
        # Get session to find character
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        character_id = session_data.get('character_id')

        # Get NPC details with relationship info
        npc_response = client.get(f'/api/v1/npcs/{npc_id}?character_id={character_id}')
        npc_data = npc_response.get('result', {})

        npc = {
            'npc_id': npc_data.get('npc_id'),
            'name': npc_data.get('name'),
            'role': npc_data.get('role'),
            'appearance': npc_data.get('appearance', {}).get('brief', ''),
            'tags': npc_data.get('tags', []),
            'image_url': npc_data.get('image_url')
        }

        # Get relationship info
        interaction_summary = npc_data.get('interaction_summary', {})
        relationship_level = interaction_summary.get('relationship_level', 50)
        interaction_count = interaction_summary.get('interaction_count', 0)

        # Conversation history would come from character's npc_interactions
        # For now, we'll leave it empty - the API returns it in dialogue responses
        conversation_history = []

        return render_template(
            'game/partials/npc_chat_modal.html',
            session_id=session_id,
            npc=npc,
            conversation_history=conversation_history,
            relationship_level=relationship_level,
            interaction_count=interaction_count
        )

    except APINotFoundError:
        return '<div class="error">NPC not found</div>', 404
    except APIError as e:
        logger.error("failed_to_load_npc_chat", session_id=session_id, npc_id=npc_id, error=str(e))
        return f'''
        <div class="modal-overlay" onclick="this.remove()">
            <div class="modal-content" onclick="event.stopPropagation()">
                <h3>Talk to NPC</h3>
                <div class="error">Failed to load NPC info: {e}</div>
                <button class="modal-close" onclick="this.closest('.modal-overlay').remove()">Close</button>
            </div>
        </div>
        '''


@game_bp.route('/session/<session_id>/npc/<npc_id>/history')
@require_auth
def npc_chat_history(session_id: str, npc_id: str):
    """Get last 5 chat messages for history sidebar."""
    client = get_api_client()

    try:
        # Get session to find character_id
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        character_id = session_data.get('character_id')

        # Fetch last 5 messages from chat service
        # API endpoint: GET /api/v1/characters/{character_id}/chats/{npc_id}?limit=5
        history_response = client.get(
            f'/api/v1/characters/{character_id}/chats/{npc_id}',
            params={'limit': 5, 'offset': 0}
        )
        result_data = history_response.get('result', {})
        messages = result_data.get('messages', [])  # Extract messages array from result

        # Render history partial
        return render_template(
            'game/partials/npc_chat_history.html',
            messages=messages,
            session_id=session_id,
            npc_id=npc_id
        )

    except APIError as e:
        logger.error("failed_to_load_chat_history", session_id=session_id, npc_id=npc_id, error=str(e))
        return '<div class="history-empty">Failed to load history</div>', 500


# ===== Inventory Routes =====

@game_bp.route('/session/<session_id>/inventory-modal')
@require_auth
def inventory_modal(session_id: str):
    """
    Get inventory modal with all items.

    Supports filtering by item type via ?filter= parameter.
    """
    client = get_api_client()
    filter_type = request.args.get('filter', 'all')

    try:
        # Get session to find character
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        character_id = session_data.get('character_id')

        inventory = []
        equipped = {}
        gold = 0
        inventory_count = 0
        inventory_max = 100

        if character_id:
            try:
                inv_response = client.get(f'/api/v1/characters/{character_id}/inventory')
                inv_data = inv_response.get('result', {})
                inventory = inv_data.get('inventory', [])
                equipped = inv_data.get('equipped', {})
                inventory_count = inv_data.get('inventory_count', len(inventory))
                inventory_max = inv_data.get('max_inventory', 100)

                # Get gold from character
                char_response = client.get(f'/api/v1/characters/{character_id}')
                char_data = char_response.get('result', {})
                gold = char_data.get('gold', 0)
            except (APINotFoundError, APIError) as e:
                logger.warning("failed_to_load_inventory", character_id=character_id, error=str(e))

        # Filter inventory by type if specified
        if filter_type != 'all':
            inventory = [item for item in inventory if item.get('item_type') == filter_type]

        return render_template(
            'game/partials/inventory_modal.html',
            session_id=session_id,
            inventory=inventory,
            equipped=equipped,
            gold=gold,
            inventory_count=inventory_count,
            inventory_max=inventory_max,
            filter=filter_type
        )

    except APIError as e:
        logger.error("failed_to_load_inventory_modal", session_id=session_id, error=str(e))
        return f'''
        <div class="modal-overlay" onclick="closeModal()">
            <div class="modal-content inventory-modal">
                <div class="modal-header">
                    <h2>Inventory</h2>
                    <button class="modal-close" onclick="closeModal()">&times;</button>
                </div>
                <div class="modal-body">
                    <div class="inventory-empty">Failed to load inventory: {e}</div>
                </div>
            </div>
        </div>
        '''


@game_bp.route('/session/<session_id>/inventory/item/<item_id>')
@require_auth
def inventory_item_detail(session_id: str, item_id: str):
    """Get item detail partial for HTMX swap."""
    client = get_api_client()

    try:
        # Get session to find character
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        character_id = session_data.get('character_id')

        item = None
        if character_id:
            # Get inventory and find the item
            inv_response = client.get(f'/api/v1/characters/{character_id}/inventory')
            inv_data = inv_response.get('result', {})
            inventory = inv_data.get('inventory', [])

            for inv_item in inventory:
                if inv_item.get('item_id') == item_id:
                    item = inv_item
                    break

        if not item:
            return '<div class="item-detail-empty">Item not found</div>', 404

        # Determine suggested slot for equipment
        suggested_slot = None
        item_type = item.get('item_type', '')
        if item_type == 'weapon':
            suggested_slot = 'weapon'
        elif item_type == 'armor':
            # Could be any armor slot - default to chest
            suggested_slot = 'chest'

        return render_template(
            'game/partials/inventory_item_detail.html',
            session_id=session_id,
            item=item,
            suggested_slot=suggested_slot
        )

    except APIError as e:
        logger.error("failed_to_load_item_detail", session_id=session_id, item_id=item_id, error=str(e))
        return f'<div class="item-detail-empty">Failed to load item: {e}</div>', 500


@game_bp.route('/session/<session_id>/inventory/use', methods=['POST'])
@require_auth
def inventory_use(session_id: str):
    """Use a consumable item."""
    client = get_api_client()
    item_id = request.form.get('item_id')

    if not item_id:
        return '<div class="error">No item selected</div>', 400

    try:
        # Get session to find character
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        character_id = session_data.get('character_id')

        if not character_id:
            return '<div class="error">No character found</div>', 400

        # Use the item via API
        client.post(f'/api/v1/characters/{character_id}/inventory/use', {
            'item_id': item_id
        })

        # Return updated character panel
        return redirect(url_for('game.character_panel', session_id=session_id))

    except APIError as e:
        logger.error("failed_to_use_item", session_id=session_id, item_id=item_id, error=str(e))
        return f'<div class="error">Failed to use item: {e}</div>', 500


@game_bp.route('/session/<session_id>/inventory/equip', methods=['POST'])
@require_auth
def inventory_equip(session_id: str):
    """Equip an item to a slot."""
    client = get_api_client()
    item_id = request.form.get('item_id')
    slot = request.form.get('slot')

    if not item_id:
        return '<div class="error">No item selected</div>', 400

    try:
        # Get session to find character
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        character_id = session_data.get('character_id')

        if not character_id:
            return '<div class="error">No character found</div>', 400

        # Equip the item via API
        payload = {'item_id': item_id}
        if slot:
            payload['slot'] = slot

        client.post(f'/api/v1/characters/{character_id}/inventory/equip', payload)

        # Return updated character panel
        return redirect(url_for('game.character_panel', session_id=session_id))

    except APIError as e:
        logger.error("failed_to_equip_item", session_id=session_id, item_id=item_id, error=str(e))
        return f'<div class="error">Failed to equip item: {e}</div>', 500


@game_bp.route('/session/<session_id>/inventory/<item_id>', methods=['DELETE'])
@require_auth
def inventory_drop(session_id: str, item_id: str):
    """Drop (delete) an item from inventory."""
    client = get_api_client()

    try:
        # Get session to find character
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        character_id = session_data.get('character_id')

        if not character_id:
            return '<div class="error">No character found</div>', 400

        # Delete the item via API
        client.delete(f'/api/v1/characters/{character_id}/inventory/{item_id}')

        # Return updated inventory modal
        return redirect(url_for('game.inventory_modal', session_id=session_id))

    except APIError as e:
        logger.error("failed_to_drop_item", session_id=session_id, item_id=item_id, error=str(e))
        return f'<div class="error">Failed to drop item: {e}</div>', 500


@game_bp.route('/session/<session_id>/npc/<npc_id>/talk', methods=['POST'])
@require_auth
def talk_to_npc(session_id: str, npc_id: str):
    """Send message to NPC - returns dialogue response or job polling partial."""
    client = get_api_client()

    # Support both topic (initial greeting) and player_response (conversation)
    player_response = request.form.get('player_response')
    topic = request.form.get('topic', 'greeting')

    try:
        payload = {'session_id': session_id}
        if player_response:
            # Player typed a custom response
            payload['player_response'] = player_response
        else:
            # Initial greeting click
            payload['topic'] = topic

        response = client.post(f'/api/v1/npcs/{npc_id}/talk', payload)
        result = response.get('result', {})

        # Check if it's a job-based response (async) or immediate
        job_id = result.get('job_id')
        if job_id:
            # Return job polling partial for the chat area
            # Use hx-target="this" and hx-swap="outerHTML" to replace loading div with response in-place
            return render_template(
                'game/partials/job_polling.html',
                job_id=job_id,
                session_id=session_id,
                status='queued',
                is_npc_dialogue=True,
                hx_target='this',       # Target the loading div itself
                hx_swap='outerHTML'     # Replace entire loading div with response
            )

        # Immediate response (if AI is sync or cached)
        dialogue = result.get('dialogue', result.get('response', 'The NPC has nothing to say.'))
        npc_name = result.get('npc_name', 'NPC')

        # Return dialogue in chat format
        player_display = player_response if player_response else f"[{topic}]"
        return f'''
        <div class="chat-message chat-message--player">
            <strong>You:</strong> {player_display}
        </div>
        <div class="chat-message chat-message--npc">
            <strong>{npc_name}:</strong> {dialogue}
        </div>
        '''

    except APINotFoundError:
        return '<div class="error">NPC not found.</div>', 404
    except APIError as e:
        logger.error("failed_to_talk_to_npc", session_id=session_id, npc_id=npc_id, error=str(e))
        return f'<div class="error">Failed to talk to NPC: {e}</div>', 500
