from flask import Flask, render_template, request, redirect, url_for, send_from_directory, flash, session
import os
import asyncio
import pdfquery
from PIL import Image
import io
import re
import unicodedata
import tempfile
from concurrent.futures import ThreadPoolExecutor
from werkzeug.utils import secure_filename
from werkzeug.exceptions import RequestEntityTooLarge
from typing import List, Tuple, Optional
from functools import lru_cache

# Настройка приложения
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = os.path.join(os.getcwd(), 'uploads')
app.config['RESULTS_DIR'] = os.path.join(os.getcwd(), "processed_documents")
app.config['MAX_CONTENT_LENGTH'] = 110 * 1024 * 1024  # 110 MB limit
app.secret_key = 'your_secret_key'  # Задайте свой секретный ключ

os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
os.makedirs(app.config['RESULTS_DIR'], exist_ok=True)

def normalize_text(text: str) -> str:
    """Нормализует текст, удаляя нечитаемые символы."""
    text = unicodedata.normalize('NFKD', text)
    return re.sub(r'[^\x00-\x7F]+', '', text).strip()

@lru_cache(maxsize=32)
def safe_extract_text(page_content: str) -> Optional[str]:
    """Извлекает текст из страницы PDF, используя кэширование."""
    try:
        return normalize_text(page_content)
    except Exception:
        return None

async def process_pdf_async(pdf_file_path: str, output_folder: str) -> Tuple[List[str], List[str]]:
    """Асинхронно обрабатывает PDF-файл и разделяет его на главы."""
    chapters = []
    chapter_titles = []
    error_pages = []

    pdf = pdfquery.PDFQuery(pdf_file_path)
    pdf.load()
    
    current_chapter = []
    chapter_number = 1

    for page_num in range(len(pdf.tree.getroot().findall("*"))):
        page = pdf.tree.getroot()[page_num]
        text = safe_extract_text(page.text_content())
        
        if not text:
            error_pages.append(page_num + 1)
            continue

        chapter_match = re.search(r'^(?:глава|раздел)\s*(\d+)', text.lower())
        if chapter_match:
            if current_chapter:
                chapter_file = os.path.join(output_folder, f"chapter_{chapter_number}.txt")
                with open(chapter_file, 'w', encoding='utf-8') as f:
                    f.write('\n'.join(current_chapter))
                chapters.append(chapter_file)
                chapter_titles.append(f"Глава {chapter_number}")
                chapter_number += 1
                current_chapter = []
            
            chapter_number = int(chapter_match.group(1))
        
        current_chapter.append(text)

        # Имитация асинхронной операции
        await asyncio.sleep(0)

    if current_chapter:
        chapter_file = os.path.join(output_folder, f"chapter_{chapter_number}.txt")
        with open(chapter_file, 'w', encoding='utf-8') as f:
            f.write('\n'.join(current_chapter))
        chapters.append(chapter_file)
        chapter_titles.append(f"Глава {chapter_number}")

    if error_pages:
        error_message = f"Не удалось обработать следующие страницы: {', '.join(map(str, error_pages))}"
        flash(error_message, 'warning')

    return chapters, chapter_titles

@app.errorhandler(413)
@app.errorhandler(RequestEntityTooLarge)
def request_entity_too_large(error):
    """Обработчик ошибки превышения размера файла."""
    flash('Размер файла превышает допустимый лимит в 110 МБ', 'danger')
    return redirect(url_for('upload_file')), 413

@app.route('/', methods=['GET', 'POST'])
def upload_file():
    """Обработчик загрузки файла."""
    if request.method == 'POST':
        if 'file' not in request.files:
            flash('Нет части файла в запросе', 'danger')
            return redirect(request.url)
        file = request.files['file']
        if file.filename == '':
            flash('Файл не выбран', 'danger')
            return redirect(request.url)
        if file and allowed_file(file.filename):
            try:
                filename = secure_filename(file.filename)
                filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
                file.save(filepath)
                return redirect(url_for('process_file', filename=filename))
            except RequestEntityTooLarge:
                flash('Размер файла превышает допустимый лимит в 110 МБ', 'danger')
                return redirect(request.url)
        else:
            flash('Неверный формат файла. Пожалуйста, загрузите PDF-файл.', 'danger')
            return redirect(request.url)
    return render_template('upload.html')

@app.route('/process/<filename>')
async def process_file(filename):
    """Асинхронный обработчик для обработки загруженного файла."""
    filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    output_folder = os.path.join(app.config['RESULTS_DIR'], os.path.splitext(filename)[0])
    
    chapters, chapter_titles = await process_pdf_async(filepath, output_folder)

    if chapters:
        flash('Документ успешно обработан и разделен на главы.', 'success')
        session['chapters'] = chapters
        session['chapter_titles'] = chapter_titles
        session['output_folder'] = output_folder
        return redirect(url_for('show_chapters'))
    else:
        flash('Не удалось разделить документ на главы.', 'danger')
        return redirect(url_for('upload_file'))

@app.route('/chapters')
def show_chapters():
    """Отображает список глав."""
    if 'chapters' not in session or 'chapter_titles' not in session:
        flash('Нет обработанных глав. Загрузите файл снова.', 'warning')
        return redirect(url_for('upload_file'))
    
    chapters = session['chapters']
    chapter_titles = session['chapter_titles']
    output_folder = session['output_folder']
    
    return render_template('chapters.html', chapters=zip(chapter_titles, chapters), folder=os.path.basename(output_folder))

@app.route('/chapter/<int:chapter_index>')
def view_chapter(chapter_index: int):
    """Отображает содержимое выбранной главы."""
    if 'chapters' not in session or 'chapter_titles' not in session:
        flash('Нет обработанных глав. Загрузите файл снова.', 'warning')
        return redirect(url_for('upload_file'))
    
    chapters = session['chapters']
    chapter_titles = session['chapter_titles']
    output_folder = session['output_folder']
    
    if 0 <= chapter_index < len(chapters):
        chapter_file = chapters[chapter_index]
        chapter_title = chapter_titles[chapter_index]
        try:
            with open(chapter_file, 'r', encoding='utf-8') as f:
                content = f.read()
            return render_template('chapter.html', title=chapter_title, content=content, folder=os.path.basename(output_folder))
        except Exception as e:
            flash(f'Ошибка при чтении главы: {str(e)}', 'danger')
            return redirect(url_for('show_chapters'))
    else:
        flash('Глава не найдена.', 'danger')
        return redirect(url_for('show_chapters'))

def allowed_file(filename: str) -> bool:
    """Проверяет, является ли файл PDF."""
    return '.' in filename and filename.rsplit('.', 1)[1].lower() == 'pdf'

if __name__ == '__main__':
    app.run(debug=True)