import os
import sys
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)

from flask import Flask, request, jsonify, Response, send_from_directory, session, redirect, url_for
from flask_socketio import SocketIO, emit
from flask_cors import CORS
from threading import Semaphore
import urllib.parse
import sqlite3
import secrets
import time
import json

# --- GEMINI API INTEGRATION ---
from google import genai
from google.genai import types

gemini_client = None
gemini_api_key = "AQ.Ab8RN6LOVSEKAej263_As1F_y6MPIlOe6QX9OI-jFZa0JpcFGw"
if gemini_api_key:
    try:
        gemini_client = genai.Client(api_key=gemini_api_key)
        print("Gemini API initialized successfully.")
    except Exception as e:
        print(f"Error initializing Gemini API: {e}")
else:
    print("Warning: GEMINI_API_KEY not found in environment variables. Lounge AI will use fallback replies.")

db_lock = Semaphore(1)

# Initialize Flask App
app = Flask(__name__)
app.secret_key = secrets.token_hex(24)
CORS(app)

socketio = SocketIO(app, cors_allowed_origins="*", async_mode='threading')

def get_db_connection():
    conn = sqlite3.connect('eden_blackjack.db', check_same_thread=False)
    conn.execute('PRAGMA busy_timeout = 5000')
    conn.row_factory = sqlite3.Row
    return conn

def init_db():
    with db_lock:
        conn = get_db_connection()
        conn.execute('''
            CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                nickname TEXT UNIQUE,
                email TEXT UNIQUE,
                balance INTEGER,
                peak_balance INTEGER DEFAULT 1000,
                password_hash TEXT
            )
        ''')
        conn.commit()
        conn.close()

# --- HTTP ROUTE HANDLERS ---

@app.route('/api/login', methods=['POST'])
def api_login():
    data = request.get_json() or {}
    email = data.get('email', '').strip()
    
    if not email:
        return jsonify({'success': False, 'message': 'Email address required.'}), 400

    with db_lock:
        conn = get_db_connection()
        user = conn.execute('SELECT * FROM users WHERE LOWER(email) = LOWER(?)', (email,)).fetchone()
        
        if not user:
            nickname = email.split('@')[0].upper() if '@' in email else "BLACKJACK"
            cursor = conn.cursor()
            cursor.execute('INSERT INTO users (nickname, email, balance, peak_balance) VALUES (?, ?, 10000, 10000)', (nickname, email))
            conn.commit()
            user = conn.execute('SELECT * FROM users WHERE id = ?', (cursor.lastrowid,)).fetchone()
        
        conn.close()

    return jsonify({
        'success': True,
        'nickname': user['nickname'],
        'balance': user['balance']
    })

@app.route('/api/register', methods=['POST'])
def api_register():
    data = request.get_json() or {}
    email = data.get('email', '').strip()
    nickname = data.get('nickname', '').strip().upper() or "BLACKJACK"

    if not email:
        return jsonify({'success': False, 'message': 'Email required.'}), 400

    with db_lock:
        conn = get_db_connection()
        try:
            conn.execute('INSERT INTO users (nickname, email, balance, peak_balance) VALUES (?, ?, 1000, 1000)', (nickname, email))
            conn.commit()
        except sqlite3.IntegrityError:
            conn.close()
            return jsonify({'success': False, 'message': 'Email or Nickname already registered.'}), 400
        conn.close()

    return jsonify({
        'success': True,
        'nickname': nickname,
        'balance': 1000
    })

# --- GAME STATE & LOGIC ---

game_state = {
    'seats': [None, None, None],
    'players': {},
    'dealer_hand': [],
    'phase': 'seating',
    'status': 'Ready',
    'current_turn': 0,
    'timer_running': False,
    'timer_id': 0,
    'turn_timer_id': 0,
    'turn_timer_count': 0,
    'strikes': {},
    'gemini_pater_treasury': 0.0,
    'eden_speech': 'Welcome back to my table. Let\'s see how the cards fall today.',
    'agent_status': 'CALIBRATED'
}

sid_to_nickname = {}

def calculate_score(hand):
    score = 0
    aces = 0
    for card in hand:
        val_str = card.split(' ')[0]
        if val_str in ['Jack', 'Queen', 'King']:
            score += 10
        elif val_str == 'Ace':
            aces += 1
            score += 11
        else:
            try: 
                score += int(val_str)
            except: 
                continue 
    
    while score > 21 and aces > 0:
        score -= 10
        aces -= 1
    return score

def is_splittable(hand):
    if len(hand) != 2:
        return False
    r1 = hand[0].split(' ')[0]
    r2 = hand[1].split(' ')[0]
    ten_cards = ['10', 'Jack', 'Queen', 'King']
    if r1 == r2:
        return True
    if r1 in ten_cards and r2 in ten_cards:
        return True
    return False

def create_deck():
    suits = ['Hearts', 'Diamonds', 'Clubs', 'Spades']
    vals = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'Jack', 'Queen', 'King', 'Ace']
    return [f"{v} of {s}" for s in suits for v in vals]

def deal_card():
    if 'deck' not in game_state or len(game_state['deck']) < 10:
        game_state['deck'] = create_deck()
        secrets.SystemRandom().shuffle(game_state['deck'])
    return game_state['deck'].pop()

def reset_for_new_round():
    try:
        game_state['dealer_hand'] = []
        for nick in list(game_state['players'].keys()):
            if isinstance(game_state['players'][nick], dict):
                game_state['players'][nick]['hand'] = []
                game_state['players'][nick]['split_hand'] = []
                game_state['players'][nick]['doubled'] = False  
                game_state['players'][nick]['current_bet'] = 0
                game_state['players'][nick]['split_bet'] = 0
                game_state['players'][nick]['status'] = 'waiting'
        
        game_state['current_turn'] = 0
        game_state['turn_timer_count'] = 0
        game_state['phase'] = 'betting'
        game_state['status'] = "Place your bets"
        game_state['eden_speech'] = "Place your bets."
        socketio.emit('game_state', game_state)
    except Exception as e:
        print(f"Error during reset_for_new_round: {e}")

def determine_winners():
    d_score = calculate_score(game_state['dealer_hand'])
    d_has_bj = (d_score == 21 and len(game_state['dealer_hand']) == 2)
    primary_speech = ""
    
    with db_lock:
        conn = get_db_connection() 
        try:
            for nick, p in game_state['players'].items():
                if not isinstance(p, dict): 
                    continue
                
                def settle_hand(hand, bet_amount):
                    if not hand or bet_amount <= 0:
                        return 0
                    p_score = calculate_score(hand)
                    p_has_bj = (p_score == 21 and len(hand) == 2 and not p.get('split_hand'))
                    
                    if p_score > 21:
                        return 0
                    if p_has_bj:
                        return int(bet_amount * (1.0 if d_has_bj else 2.5))
                    if d_has_bj:
                        return 0
                    if d_score > 21 or p_score > d_score:
                        return int(bet_amount * 2)
                    if p_score == d_score:
                        return int(bet_amount)
                    return 0

                payout_main = settle_hand(p.get('hand', []), p.get('current_bet', 0))
                payout_split = settle_hand(p.get('split_hand', []), p.get('split_bet', 0))
                
                p['balance'] += (payout_main + payout_split)
                p['current_bet'] = 0
                p['split_bet'] = 0
                p['active_hand_idx'] = 0

                user_row = conn.execute('SELECT peak_balance FROM users WHERE UPPER(nickname) = UPPER(?)', (str(nick),)).fetchone()
                current_peak = user_row['peak_balance'] if user_row else 0
                new_peak = max(current_peak, int(p['balance']))
                
                conn.execute('''
                    UPDATE users 
                    SET balance = ?, peak_balance = ? 
                    WHERE UPPER(nickname) = UPPER(?)
                ''', (int(p['balance']), int(new_peak), str(nick)))
                
            conn.commit() 
        except Exception as e:
            print(f"DB Error: {e}")
        finally: 
            conn.close()
    
    game_state['eden_speech'] = primary_speech
    socketio.emit('game_state', game_state)
    
def run_dealer_logic():
    with app.app_context():
        try:
            game_state['phase'] = 'dealer' 
            game_state['status'] = "Dealer's Turn"
            socketio.emit('game_state', game_state)
            time.sleep(1.0) 

            active_player_hands = []
            for p in game_state['players'].values():
                if isinstance(p, dict) and p.get('current_bet', 0) > 0:
                    if p.get('hand'):
                        active_player_hands.append(p['hand'])
                    if p.get('split_hand'):
                        active_player_hands.append(p['split_hand'])

            all_busted = all(calculate_score(h) > 21 for h in active_player_hands) if active_player_hands else True
            all_natural_bj = all(calculate_score(h) == 21 and len(h) == 2 for h in active_player_hands) if active_player_hands else False

            if all_busted:
                game_state['eden_speech'] = "All players busted. Dealer stands."
                socketio.emit('game_state', game_state)
                time.sleep(1.2)
            elif all_natural_bj:
                game_state['eden_speech'] = "All players have Blackjack!"
                socketio.emit('game_state', game_state)
                time.sleep(1.2)
            else:
                while calculate_score(game_state['dealer_hand']) < 17:
                    new_card = deal_card()
                    game_state['dealer_hand'].append(new_card)
                    d_score = calculate_score(game_state['dealer_hand'])
                    game_state['eden_speech'] = f"Dealer draws {new_card}. Total {d_score}."
                    socketio.emit('game_state', game_state)
                    time.sleep(1.2)
                
            determine_winners()
            
            game_state['phase'] = 'ended'
            game_state['status'] = 'Round Complete'
            socketio.emit('game_state', game_state)
            
            time.sleep(3.0)
            reset_for_new_round()
            
        except Exception as e:
            print(f"Error in dealer thread: {e}")
            
def advance_turn():
    for next_seat in range(game_state['current_turn'] + 1, 3):
        sn = game_state['seats'][next_seat]
        if sn and sn.upper() in game_state['players'] and game_state['players'][sn.upper()].get('current_bet', 0) > 0:
            game_state['current_turn'] = next_seat
            p_score = calculate_score(game_state['players'][sn.upper()]['hand'])
            game_state['eden_speech'] = f"Seat {next_seat + 1}, {sn}, your turn. Total: {p_score}."
            socketio.emit('game_state', game_state)
            return

    game_state['current_turn'] = -1
    game_state['phase'] = 'dealer' 
    game_state['status'] = "Dealer's Turn"
    
    if not game_state['dealer_hand']:
        game_state['dealer_hand'] = [deal_card(), deal_card()]
        
    socketio.emit('game_state', game_state)
    socketio.start_background_task(run_dealer_logic)

def execute_start_game():
    with app.app_context():
        any_bets = any(p.get('current_bet', 0) > 0 for p in game_state['players'].values() if isinstance(p, dict))
        if not any_bets:
            game_state['phase'] = 'seating'
            game_state['status'] = 'Waiting for bets...'
            socketio.emit('game_state', game_state)
            return

        game_state['phase'] = 'playing'
        game_state['status'] = "Game in Progress"
        game_state['dealer_hand'] = [deal_card(), deal_card()]
        
        for nick, player in game_state['players'].items():
            if isinstance(player, dict) and player.get('current_bet', 0) > 0:
                player['hand'] = [deal_card(), deal_card()]
                player['status'] = 'playing'
        
        d_score = calculate_score(game_state['dealer_hand'])
        dealer_has_bj = (d_score == 21 and len(game_state['dealer_hand']) == 2)
        
        active_players_have_bj = []
        has_active_non_bj_player = False
        
        for i, sn in enumerate(game_state['seats']):
            if sn and sn.upper() in game_state['players']:
                player = game_state['players'][sn.upper()]
                if player.get('current_bet', 0) > 0:
                    p_score = calculate_score(player['hand'])
                    p_has_bj = (p_score == 21 and len(player['hand']) == 2)
                    
                    if p_has_bj:
                        active_players_have_bj.append(sn)
                    else:
                        has_active_non_bj_player = True

        if dealer_has_bj or not has_active_non_bj_player:
            game_state['current_turn'] = -1
            socketio.emit('game_state', game_state)
            socketio.start_background_task(run_dealer_logic)
            return

        found_active = False
        for i, sn in enumerate(game_state['seats']):
            if sn and sn.upper() in game_state['players']:
                player = game_state['players'][sn.upper()]
                if player.get('current_bet', 0) > 0:
                    p_score = calculate_score(player['hand'])
                    if p_score < 21:
                        game_state['current_turn'] = i
                        game_state['eden_speech'] = f"Seat {i+1}, {sn}, your turn. Total: {p_score}."
                        found_active = True
                        break
        
        socketio.emit('game_state', game_state)
        
        if not found_active:
            socketio.start_background_task(run_dealer_logic)

# --- SOCKET EVENTS ---

@socketio.on('connect')
def handle_connect():
    pass

@socketio.on('disconnect')
def handle_disconnect():
    sid = request.sid
    if sid in sid_to_nickname:
        nickname = sid_to_nickname.pop(sid)
        
        for i, seat_owner in enumerate(game_state['seats']):
            if seat_owner == nickname:
                game_state['seats'][i] = None
                game_state['eden_speech'] = f"Seat {i + 1}, {nickname}, disconnected."
                
                if game_state['phase'] == 'playing' and game_state['current_turn'] == i:
                    advance_turn()
                else:
                    socketio.emit('game_state', game_state)
                break

@socketio.on('join_seat')
def handle_join(data):
    seat_idx = int(data.get('seat', 0))
    raw_nickname = data.get('nickname', '').strip()
    nickname = raw_nickname.upper()
    
    if not nickname or seat_idx < 0 or seat_idx >= len(game_state['seats']):
        return

    sid_to_nickname[request.sid] = nickname

    for i in range(len(game_state['seats'])):
        if game_state['seats'][i] == nickname:
            game_state['seats'][i] = None

    with db_lock:
        conn = get_db_connection()
        user_data = conn.execute('SELECT balance FROM users WHERE UPPER(nickname) = UPPER(?)', (nickname,)).fetchone()
        balance = user_data['balance'] if user_data else 1000
        conn.close()

    game_state['players'][nickname] = {
        'balance': balance, 
        'hand': [], 
        'split_hand': [],
        'current_bet': 0, 
        'split_bet': 0,
        'status': 'waiting',
        'doubled': False
    }
    
    game_state['seats'][seat_idx] = nickname
    game_state['eden_speech'] = f"{nickname} joined Seat {seat_idx + 1}. Place your bet."
    game_state['phase'] = 'betting'
    socketio.emit('game_state', game_state)

@socketio.on('place_bet')
def handle_bet(data):
    nickname = data.get('nickname', '').strip().upper()
    amount = int(data.get('amount', 0))
    
    if sid_to_nickname.get(request.sid) != nickname or nickname not in game_state['players']:
        return

    p = game_state['players'][nickname]
    if p['balance'] >= amount and amount > 0:
        p['balance'] -= amount
        p['current_bet'] += amount
        p['status'] = 'ready'
        
        occupied_seats = [s for s in game_state['seats'] if s is not None]
        all_ready = all(
            game_state['players'].get(s, {}).get('current_bet', 0) > 0 
            for s in occupied_seats
        ) if occupied_seats else False

        if all_ready and game_state['phase'] == 'betting':
            socketio.start_background_task(execute_start_game)
        else:
            game_state['eden_speech'] = f"{nickname} placed a bet of {amount} dollars."
            socketio.emit('game_state', game_state)

@socketio.on('player_action')
def handle_player_action(data):
    nickname = data.get('nickname', '').strip().upper()
    action = data.get('action')
    current_seat = game_state['current_turn']
    
    if current_seat == -1 or current_seat >= len(game_state['seats']):
        return
        
    seat_owner = game_state['seats'][current_seat]
    
    if sid_to_nickname.get(request.sid) != nickname:
        return

    if not seat_owner or seat_owner.upper() != nickname or game_state['phase'] != 'playing':
        return

    player = game_state['players'][nickname]
    
    active_hand_idx = player.get('active_hand_idx', 0)
    hand_key = 'hand' if active_hand_idx == 0 else 'split_hand'
    
    if action == 'hit':
        new_card = deal_card()
        player[hand_key].append(new_card)
        p_score = calculate_score(player[hand_key])
        
        if p_score >= 21:
            msg = "Bust!" if p_score > 21 else "Twenty-one!"
            game_state['eden_speech'] = f"{nickname} draws {new_card}. {msg}"
            socketio.emit('game_state', game_state)
            
            if active_hand_idx == 0 and len(player.get('split_hand', [])) > 0:
                player['active_hand_idx'] = 1
                game_state['eden_speech'] = f"{nickname}, playing second split hand."
                socketio.emit('game_state', game_state)
            else:
                time.sleep(1.0)
                advance_turn()
        else:
            game_state['eden_speech'] = f"{nickname} draws {new_card}. Total: {p_score}."
            socketio.emit('game_state', game_state)
            
    elif action == 'stand':
        if active_hand_idx == 0 and len(player.get('split_hand', [])) > 0:
            player['active_hand_idx'] = 1
            game_state['eden_speech'] = f"{nickname} stands on first hand. Playing split hand."
            socketio.emit('game_state', game_state)
        else:
            game_state['eden_speech'] = f"{nickname} stands."
            socketio.emit('game_state', game_state)
            advance_turn()
        
    elif action in ['double', 'double_down']:
        bet_key = 'current_bet' if active_hand_idx == 0 else 'split_bet'
        current_bet = player.get(bet_key, 0)
        
        if player['balance'] >= current_bet and len(player[hand_key]) == 2:
            player['balance'] -= current_bet
            player[bet_key] += current_bet
            
            new_card = deal_card()
            player[hand_key].append(new_card)
            p_score = calculate_score(player[hand_key])
            
            game_state['eden_speech'] = f"{nickname} doubles down, draws {new_card}. Total: {p_score}."
            socketio.emit('game_state', game_state)
            
            if active_hand_idx == 0 and len(player.get('split_hand', [])) > 0:
                player['active_hand_idx'] = 1
                socketio.emit('game_state', game_state)
            else:
                advance_turn()

    elif action == 'split':
        current_bet = player.get('current_bet', 0)
        if is_splittable(player['hand']) and player['balance'] >= current_bet and not player.get('split_hand'):
            player['balance'] -= current_bet
            card1 = player['hand'][0]
            card2 = player['hand'][1]
            
            player['hand'] = [card1, deal_card()]
            player['split_hand'] = [card2, deal_card()]
            player['split_bet'] = current_bet
            player['active_hand_idx'] = 0
            
            game_state['eden_speech'] = f"{nickname} splits the hand."
            socketio.emit('game_state', game_state)

    elif action == 'surrender':
        if len(player['hand']) == 2 and not player.get('split_hand'):
            half_bet = player['current_bet'] // 2
            player['balance'] += half_bet
            player['current_bet'] = 0
            player['hand'] = []
            
            game_state['eden_speech'] = f"{nickname} surrenders."
            socketio.emit('game_state', game_state)
            advance_turn()

# --- ISOLATED LOUNGE CHAT SOCKET EVENT ---

DEALER_LOUNGE_PROMPT = """
You are Eden, a smooth, witty, and charming casino dealer chatting 1-on-1 with a guest in your private VIP lounge.
- Keep responses concise and conversational (1 to 3 sentences max) so it sounds natural when spoken aloud.
- Maintain a warm, inviting, and confident personality.
- Stay completely in character as Eden.
"""

@socketio.on('lounge_chat_message')
def handle_lounge_chat(data):
    user_msg = data.get('message', '').strip()
    if not user_msg:
        return

    # Check if gemini_client is actually initialized
    if not gemini_client:
        print("[DEBUG] Gemini client is NONE. Check your API key or client initialization!")
        reply_text = f"I heard you say '{user_msg}', but my AI connection isn't initialized yet."
    else:
        try:
            response = gemini_client.models.generate_content(
                model='gemini-3.8-flash',
                contents=f"You are Eden, host of the VIP Residence Lounge. Talk directly with the guest in a warm, friendly tone. Guest says: {user_msg}"
            )
            if response and response.text:
                reply_text = response.text.strip()
            else:
                reply_text = "I'm having trouble thinking of a reply right now."
        except Exception as e:
            print(f"[DEBUG] Gemini API Error: {e}")
            reply_text = f"I ran into an issue processing that: {e}"

    # Broadcast response back to frontend
    socketio.emit('lounge_chat_response', {'message': reply_text})
# --- STATIC FILE SERVING & AUTH ROUTES ---

@app.route('/')
def index():
    try:
        return send_from_directory('.', 'lobby.html')
    except Exception:
        return send_from_directory(os.path.dirname(os.path.abspath(__file__)), 'lobby.html')

@app.route('/logout')
def logout():
    session.clear()
    return redirect('/login.html')

# --- TTS AUDIO PROXY ROUTE ---

@app.route('/api/tts', methods=['GET'])
def tts_proxy():
    text = request.args.get('text', '').strip()
    if not text:
        return jsonify({'error': 'No text provided'}), 400

    encoded_text = urllib.parse.quote(text)
    tts_url = f"https://translate.google.com/translate_tts?ie=UTF-8&q={encoded_text}&tl=en&client=tw-ob"

    import requests
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
    }

    try:
        res = requests.get(tts_url, headers=headers, timeout=5)
        if res.status_code == 200:
            return Response(res.content, mimetype='audio/mpeg')
        else:
            return jsonify({'error': f'TTS upstream error: {res.status_code}'}), 502
    except Exception as e:
        print(f"TTS Error: {e}")
        return jsonify({'error': 'Failed to fetch TTS audio'}), 500



# --- API ROUTES ---

@app.route('/api/leaderboard', methods=['GET'])
def api_leaderboard():
    with db_lock:
        conn = get_db_connection()
        players = conn.execute('''
            SELECT nickname, balance, peak_balance 
            FROM users 
            ORDER BY peak_balance DESC 
            LIMIT 100
        ''').fetchall()
        conn.close()

    leaderboard_data = [
        {
            'nickname': row['nickname'],
            'balance': row['balance'],
            'peak_balance': row['peak_balance'] if row['peak_balance'] is not None else row['balance']
        }
        for row in players
    ]

    return jsonify(leaderboard_data)

@app.route('/api/get_balance', methods=['GET'])
def api_get_balance():
    nickname = request.args.get('nickname')
    if not nickname:
        return jsonify({'success': False, 'error': 'Nickname required'}), 400

    with db_lock:
        conn = get_db_connection()
        user = conn.execute('SELECT balance, peak_balance FROM users WHERE nickname = ?', (nickname,)).fetchone()
        conn.close()

    if user:
        return jsonify({'success': True, 'balance': user['balance'], 'peak_balance': user['peak_balance']})
    return jsonify({'success': False, 'error': 'User not found'}), 404


@app.route('/api/claim_chips', methods=['POST'])
def api_claim_chips():
    data = request.get_json() or {}
    nickname = data.get('nickname') or session.get('nickname')

    if not nickname:
        return jsonify({'success': False, 'error': 'User not logged in'}), 400

    with db_lock:
        conn = get_db_connection()
        conn.execute('UPDATE users SET balance = balance + 1000 WHERE nickname = ?', (nickname,))
        conn.commit()
        updated_user = conn.execute('SELECT balance FROM users WHERE nickname = ?', (nickname,)).fetchone()
        conn.close()

    return jsonify({'success': True, 'new_balance': updated_user['balance'] if updated_user else 1000})


# --- CATCH-ALL STATIC ROUTE (ALWAYS LAST) ---

@app.route('/<path:path>')
def serve_static(path):
    return send_from_directory('.', path)


if __name__ == '__main__':
    init_db()
    socketio.run(app, host='0.0.0.0', port=5555, debug=False)