# Imports
from flask import Blueprint, abort, request, redirect
from os import urandom, getenv as env
import src.utils.database as database
import src.utils.cap as cap
import logging, datetime, threading, time


# Create blueprint
bp = Blueprint(
    'snake', 
    __name__,
    template_folder=env('TEMPLATE_FOLDER', default='../templates'),
    static_folder=env('STATIC_FOLDER', default='../static')
)


# Create logger
log = logging.getLogger(__name__)


# Create database instance
db = database.Database(db_name=env('DB_NAME', default='db.sqlite'))
db.execute('CREATE TABLE IF NOT EXISTS snake_scores (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, score INTEGER)')
db.execute('''CREATE TABLE IF NOT EXISTS snake_tokens (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    token TEXT UNIQUE NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    ip TEXT UNIQUE NOT NULL
)''')

# Input validation function
def valid_length(value, min_length=1, max_length=100):
    if not isinstance(value, str):
        return False
    return min_length <= len(value) <= max_length


def valid_score(score, game_token):
    start_time = db.execute('SELECT created_at FROM snake_tokens WHERE token = ?', (game_token,)).fetchone()
    if not start_time:
        log.error("Game token not found.")
        return False
    
    start_time = datetime.datetime.fromisoformat(start_time[0])
    current_time = datetime.datetime.now()
    elapsed_time = (current_time - start_time).total_seconds()

    if elapsed_time < score / 10 * 3 + 10: # assuming that each point takes 3 seconds to achieve and 10 seconds to start the game and do captcha
        log.error("Score is too high for the elapsed time.")
        return False
    
    if score <= 0 or score > 10000:  # Arbitrary upper limit for scores
        log.error("Score is out of valid range.")
        return False
    
    if score % 10 != 0:
        log.error("Score is not a multiple of 10.")
        return False
    
    # delete the token after score validation
    db.execute('DELETE FROM snake_tokens WHERE token = ?', (game_token,))
    log.info(f"Score {score} validated successfully for token {game_token}.")

    return True


# Route for score submission
@bp.route('/snake/submit', methods=['POST'])
def submit_score():
    name = request.form.get('name')
    score = request.form.get('score')
    captcha_token = request.form.get('cap-token')
    game_token = request.form.get('game_token')

    if not cap.verify_captcha(captcha_token):
        log.error("Captcha verification failed.")
        abort(400, "Captcha verification failed")

    if not name or not score or not captcha_token or not game_token:
        log.error("Name, score, captcha token, or game token is missing.")
        abort(400, "Missing required fields")
    
    if not valid_length(name, min_length=3, max_length=15):
        log.error("Invalid name length.")
        abort(400, "Name must be between 3 and 15 characters long.")
    
    if not valid_score(int(score), game_token):
        log.error("Invalid score.")
        abort(400, "Score not vilid, so either you are trying to cheat the leaderboard or something is seriously wrong.")

    try:
        db.execute('INSERT INTO snake_scores (name, score) VALUES (?, ?)', (name, int(score)))
        db.execute('DELETE FROM snake_tokens WHERE token = ?', (game_token,))
        log.info(f"Score submitted: {name} - {score}")
        return redirect('/404')
    
    except Exception as e:
        log.error(f"Database error: {e}")
        abort(500, "Internal server error while submitting score.")


# Generate a unique game token
def generate_start_token():
    """Generate a unique start token for the game."""
    token = urandom(16).hex()
    ip = request.headers.get('X-Forwarded-For', request.remote_addr)

    ip_token = db.execute('SELECT token FROM snake_tokens WHERE ip = ?', (ip,)).fetchone()
    if ip_token:
        log.info(f"Token already exists for IP: {ip}, reusing token.")
        return ip_token[0]

    log.info(f"Generated start token: {token}")
    db.execute('INSERT INTO snake_tokens (token, ip) VALUES (?, ?)', (token, ip))
    return token


# Get leaderboard scores
def get_leaderboard():
    """Fetch scores from the leaderboard."""
    try:
        scores = db.execute('SELECT name, score FROM snake_scores ORDER BY score DESC').fetchall()
        leaderboard = [{'position': i + 1, 'name': score[0], 'score': score[1]} for i, score in enumerate(scores)]
        log.info("Leaderboard fetched successfully.")
        return leaderboard
    except Exception as e:
        log.error(f"Error fetching leaderboard: {e}")
        return []


# Clear all tokens older than 1 hour
def clear_old_tokens():
    while True:
        try:
            one_hour_ago = datetime.datetime.now() - datetime.timedelta(hours=1)
            db.execute('DELETE FROM snake_tokens WHERE created_at < ?', (one_hour_ago,))
            log.info("Old tokens cleared.")
        except Exception as e:
            log.error(f"Error clearing old tokens: {e}")
        time.sleep(3600)  # Run every hour


# Start the token clearing thread
token_thread = threading.Thread(target=clear_old_tokens, daemon=True)
token_thread.start()