import os
import logging  # Импортируем модуль логирования
from flask import Flask, render_template, request, redirect, url_for, session, flash, Response, jsonify, send_from_directory
import mysql.connector
from db_config import db_config
from registration import (register, confirm_registration, login, 
                        forgot_password, reset_password, change_password,
                        google_login_handler, google_callback_handler, check_auth, 
                        verify_confirmation_code, send_confirmation_code, new_password,
                        vk_login_handler, vk_callback_handler, yandex_callback_handler)
from chat import (chat, save_message, create_chat, get_last_messages, send_message,
                 get_chats, get_chat_content, get_summary, get_chapters_and_paragraphs,
                 convert_units, welcome, 
                 serve_images, get_subjects, upload_image, get_subject_folder)
from feedback import feedback, submit_feedback  # Импорт функций feedback и submit_feedback
from select_textbook import select_textbook
from datetime import datetime, timedelta
import json
from sympy import symbols, Eq, solve
from werkzeug.utils import secure_filename
from PIL import Image  # Pillow для обработки изображений
import time # Import time module for logging
import threading  # Threads to run Telegram bot alongside Flask
import asyncio  # Create event loop for Telegram bot thread
import uuid
import base64
from werkzeug.security import generate_password_hash, check_password_hash
from oauthlib.oauth2 import WebApplicationClient
from google.oauth2 import id_token
from google.auth.transport import requests as google_requests
from functools import wraps
import requests
from werkzeug.middleware.proxy_fix import ProxyFix
import socket  # Port-based singleton lock for Telegram bot

from dotenv import load_dotenv

# Настраиваем логирование
logging.basicConfig(
    level=logging.DEBUG,  # Устанавливаем уровень логирования
    format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)  # Создаем объект логгера

# Отключаем требование HTTP sdf
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'


app = Flask(__name__)
app.secret_key = 'kajshhhj345efsdkfjJhkhjJHUjs8735jk'  # Этот ключ используется для подписи session cookie

# Определение настроек в зависимости от среды запуска
if os.environ.get('FLASK_ENV') == 'production':
    app.config['SERVER_NAME'] = 'classgpt.ru'  # Домен для продакшена
    app.config['PREFERRED_URL_SCHEME'] = 'https'  # Схема для продакшена
    # Cookie settings for production (persistent session on mobile browsers)
    app.config.update(
        SESSION_COOKIE_SECURE=True,
        # SESSION_COOKIE_SAMESITE='None', # Required for cross-site OAuth flows (Edge/Opera)
        # SESSION_COOKIE_DOMAIN='.classgpt.ru',# Доступно для поддоменов
        PERMANENT_SESSION_LIFETIME=timedelta(days=30),
        SESSION_PERMANENT=True,
    )
else:
    # В режиме разработки не устанавливаем SERVER_NAME
    app.config['PREFERRED_URL_SCHEME'] = 'http'
    # Адекватные dev-настройки
    app.config.update(
        SESSION_COOKIE_SECURE=False,
        SESSION_COOKIE_SAMESITE='Lax',
        PERMANENT_SESSION_LIFETIME=timedelta(days=30),
        SESSION_PERMANENT=True,
    )

# Применяем ProxyFix, если приложение за прокси
# x_for=1, x_proto=1, x_host=1 означает, что мы доверяем заголовкам
# X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host от непосредственного прокси
app.wsgi_app = ProxyFix(
    app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1
)



# Добавляем фильтр для форматирования даты
@app.template_filter('datetimeformat')
def datetimeformat(value, format='%d.%m.%Y %H:%M'):
    return value.strftime(format)

# Добавляем фильтр для получения папки предмета
@app.template_filter('subject_folder')
def subject_folder(subject_id):
    return get_subject_folder(subject_id)

# Убедитесь, что у вас есть папка для загрузки изображений
UPLOAD_FOLDER = 'uploads'
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}

app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
logger.debug(f"FLASK_ENV: {os.environ.get('FLASK_ENV')}")
logger.debug(f"SESSION_COOKIE_SECURE: {app.config.get('SESSION_COOKIE_SECURE')}")
logger.debug(f"SESSION_COOKIE_SAMESITE: {app.config.get('SESSION_COOKIE_SAMESITE')}")

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

# Configure user images storage
USER_IMAGES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'user_images')
os.makedirs(USER_IMAGES_DIR, exist_ok=True)
app.config['USER_IMAGES_DIR'] = USER_IMAGES_DIR

def get_static_file_version(filename):
    """Return the last modification time of a static file as a string."""
    try:
        # app.static_folder is the absolute path to the static folder
        file_path = os.path.join(app.static_folder, filename)
        if os.path.exists(file_path):
            return str(int(os.path.getmtime(file_path)))
    except Exception as e:
        logger.error(f"Error getting version for {filename}: {e}")
    return '1'

app.jinja_env.globals.update(get_static_file_version=get_static_file_version)

def login_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'user_id' not in session:
            return jsonify({'error': 'Unauthorized'}), 401
        return f(*args, **kwargs)
    return decorated_function

@app.route('/')
def index():
    return welcome()

@app.route('/register', methods=['GET', 'POST'])
def register_route():
    return register()


@app.route('/login', methods=['GET', 'POST'])
def login_route():
    return login()

@app.route('/forgot_password', methods=['GET', 'POST'])
def forgot_password_route():
    return forgot_password()

@app.route('/reset_password', methods=['GET', 'POST'])
def reset_password_route():
    return reset_password()

@app.route('/logout')
def logout():
    session.pop('username', None)
    session.pop('selected_textbook', None)
    session.pop('user_id', None)
    return redirect(url_for('welcome_route'))


@app.route('/check_auth')
def check_auth_route():
    return check_auth()

@app.route('/chat')
def chat_route():
    return chat()

@app.route('/send_message', methods=['POST'])
@login_required
def send_message_route():
    user_message = request.json.get('message')  
    chat_id = request.json.get('chat_id')  
    summary = request.json.get('summary')
    mode = request.json.get('mode')
    # Trace start for end-to-end latency from POST
    trace_id = str(uuid.uuid4())[:8]
    t_request_epoch = time.time()
    t_request_perf = time.perf_counter()
    logger.info(f"APP: User request received at {t_request_epoch:.2f}, trace_id={trace_id}")
    return send_message(
        user_message=user_message,
        chat_id=chat_id,
        summary=summary,
        mode=mode,
        trace_id=trace_id,
        t_request_epoch=t_request_epoch,
        t_request_perf=t_request_perf,
    )
        
@app.route('/get_chats', methods=['GET'])
@login_required
def get_chats_route():
    return get_chats()

@app.route('/get_chat_content/<int:chat_id>', methods=['GET'])
@login_required
def get_chat_content_route(chat_id):
    return get_chat_content(chat_id)

@app.route('/get_summary/<int:chapter_id>', methods=['GET'])
def get_summary_route(chapter_id):
    return get_summary(chapter_id)

@app.route('/get_chapters_and_paragraphs/<int:textbook_id>', methods=['GET'])
def get_chapters_and_paragraphs_route(textbook_id):
    return get_chapters_and_paragraphs(textbook_id)

@app.route('/welcome', methods=['GET', 'POST'])
def welcome_route():
    return welcome()

@app.route('/select_textbook', methods=['GET', 'POST'])
def select_textbook_route():
    return select_textbook()

@app.route('/images/<path:filename>')
def images(filename):
    return serve_images(filename)

@app.route('/feedback')
def feedback_route():
    return feedback()

@app.route('/experts')
def experts_route():
    # Render experts page with subjects for header navigation
    return render_template('experts.html', subjects=get_subjects())

@app.route('/submit_feedback', methods=['POST'])
def submit_feedback_route():
    return submit_feedback()

# @app.context_processor

@app.route("/login/google")
def google_login_handler_route():
    return google_login_handler()


@app.route("/login/google/callback")
def google_callback_handler_route():
    return google_callback_handler()

@app.route("/login/vk")
def vk_login_handler_route():
    return vk_login_handler()

@app.route("/login/vk/callback", methods=['GET', 'POST'])
def vk_callback_handler_route():
    return vk_callback_handler()

@app.route("/login/ya/callback", methods=['GET', 'POST'])
def yandex_callback_handler_route():
    return yandex_callback_handler()

@app.route("/yandex_callback")
def yandex_callback_page():
    return render_template('yandex_callback.html')

@app.route('/upload_image', methods=['POST'])
def upload_image_route():
    return upload_image()

@app.route('/user_images/<path:filename>')
def serve_user_image(filename):
    try:
        # Split path into date folder and actual filename
        date_folder, image_file = filename.replace('\\', '/').split('/', 1)
        
        # Build the complete path
        image_path = os.path.join(app.config['USER_IMAGES_DIR'], date_folder)
        
        # Log the paths for debugging
        logger.debug(f"Requested image: {filename}")
        logger.debug(f"Looking in directory: {image_path}")
        logger.debug(f"For file: {image_file}")
        
        # Make sure the directory exists
        if not os.path.exists(image_path):
            logger.error(f"Directory not found: {image_path}")
            return "Directory not found", 404
        # Try to serve the file with an explicit mimetype to avoid application/octet-stream
        import mimetypes
        guessed_mime, _ = mimetypes.guess_type(image_file)
        # Fallback to common image type if unknown
        mimetype = guessed_mime or 'image/jpeg'
        return send_from_directory(image_path, image_file, mimetype=mimetype)
    except Exception as e:
        logger.error(f"Error serving image {filename}: {str(e)}")
        return "Error serving image", 404

@app.route('/verify_confirmation_code', methods=['POST'])
def verify_confirmation_code_route():
    return verify_confirmation_code()


@app.route('/send_confirmation_code', methods=['POST'])
def send_confirmation_code_route():
    return send_confirmation_code()

@app.route('/new_password/<token>', methods=['GET', 'POST'])
def new_password_route(token):
    return new_password(token)

@app.route('/yandex_50737c5f0b7744ae.html')
def yandex_verification():
    return send_from_directory('static', 'yandex_50737c5f0b7744ae.html')

# После создания app

# Запускаем Telegram-бот в отдельном потоке, чтобы один процесс обслуживал и сайт, и бота
def start_telegram_bot() -> None:
    """Start Telegram bot polling in a background daemon thread."""
    # Comments in English by request
    # Acquire cross-process singleton lock (prevents multiple bot instances)
    lock_sock = None
    try:
        host = os.getenv("TG_BOT_LOCK_HOST", "127.0.0.1")
        port = int(os.getenv("TG_BOT_LOCK_PORT", "37213"))
        lock_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            lock_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        except Exception:
            # Not critical; continue without SO_REUSEADDR when unavailable
            pass
        lock_sock.bind((host, port))
        lock_sock.listen(1)
        # Keep socket in app config to prevent GC/close
        app.config["_TG_BOT_LOCK_SOCKET"] = lock_sock
        logger.info(f"Telegram bot singleton lock acquired on {host}:{port}")
    except Exception as lock_err:
        if lock_sock:
            try:
                lock_sock.close()
            except Exception:
                pass
        logger.info(f"Skip starting Telegram bot: lock busy or failed ({lock_err})")
        return
    try:
        from classGPT_telegram import build_application
    except Exception as import_err:
        logger.exception("Failed to import Telegram bot: %s", import_err)
        return

    def _runner() -> None:
        try:
            # Ensure event loop exists in this non-main thread
            try:
                loop = asyncio.get_event_loop()
            except RuntimeError:
                loop = asyncio.new_event_loop()
                asyncio.set_event_loop(loop)

            tg_app = build_application()
            logger.info("Starting Telegram bot (polling mode)...")
            # Run with defaults; it creates and manages its own asyncio loop.
            # In a non-main thread we must avoid installing signal handlers.
            try:
                tg_app.run_polling(stop_signals=None, close_loop=False)
            except TypeError:
                # For older/newer PTB versions without stop_signals kw
                try:
                    tg_app.run_polling(close_loop=False)
                except Exception:
                    raise
        except Exception as run_err:
            logger.exception("Telegram bot stopped with error: %s", run_err)

    thread = threading.Thread(target=_runner, name="telegram-bot", daemon=True)
    thread.start()

# После всех маршрутов добавляем обработчик ошибок
@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'), 404

if __name__ == '__main__':
    # Определение параметров запуска в зависимости от среды
    env = os.environ.get('FLASK_ENV')
    # Запускаем бота:
    # - В проде: всегда
    # - В dev с перезагрузчиком: только в дочернем процессе (WERKZEUG_RUN_MAIN == 'true')
    # - Проверяем переменную окружения ENABLE_TG_BOT
    if (env == 'production' or os.environ.get('WERKZEUG_RUN_MAIN') == 'true') and os.environ.get("ENABLE_TG_BOT", "1") == "1":
        start_telegram_bot()
    if env == 'production':
        app.run(host='0.0.0.0', port=5000)
    else:
        app.run(debug=True, host='0.0.0.0', port=5000)