from werkzeug.middleware.proxy_fix import ProxyFix
from werkzeug.utils import secure_filename as werkzeug_secure_filename
import unicodedata
import os
import re
import time
import hashlib
import logging
import subprocess
import tempfile
import shutil
import threading
import mimetypes
import platform

from flask import (
    Flask, render_template, request, redirect, url_for, flash,
    session, send_file, send_from_directory, current_app, jsonify, abort, Blueprint, make_response
)
from flask_login import (
    LoginManager, current_user, login_required, login_user, logout_user
)
from flask_session import Session
from flask_compress import Compress
from sqlalchemy import text

import pandas as pd
import mistune
import frontmatter
from bs4 import BeautifulSoup

from sumy.parsers.plaintext import PlaintextParser
from sumy.nlp.tokenizers import Tokenizer
from sumy.summarizers.lex_rank import LexRankSummarizer

import nltk
nltk.download('punkt')

from models import db, User, Document, DocumentVersion

# -------------------------------------------------------------------
# Initialisation de l'application Flask
# -------------------------------------------------------------------

app = Flask(__name__, static_folder='static', static_url_path='/static')
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1)

app.config['PREFERRED_URL_SCHEME'] = 'https'
app.config['APPLICATION_ROOT'] = '/'
app.config["SECRET_KEY"] = os.getenv("SECRET_KEY", "fallback-secret-key")
app.config.update(
    ENV='production',
    SESSION_COOKIE_NAME='session',
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_HTTPONLY=True,
    SESSION_COOKIE_SAMESITE='Lax',
    PREFERRED_URL_SCHEME='https'
)

app.config["SQLALCHEMY_DATABASE_URI"] = (
    f"mysql+mysqlconnector://{os.getenv('DB_USER')}:{os.getenv('DB_PASSWORD')}"
    f"@{os.getenv('DB_HOST')}/{os.getenv('DB_NAME')}"
)
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["SQLALCHEMY_ECHO"] = True

app.config['UPLOAD_FOLDER'] = '/var/sftp/uploads'
app.config['MAX_CONTENT_LENGTH'] = 200 * 1024 * 1024
app.config['ALLOWED_EXTENSIONS'] = {
    'txt', 'pdf', 'docx', 'pptx', 'xlsx', 'md', 'html', 'htm', 'csv', 'json', 'xml'
}

app.config['SESSION_TYPE'] = 'filesystem'
Session(app)

Compress(app)

# Logging
handler = logging.FileHandler('/home/tecodataci/appliBDC/knowledge_base.log')
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
app.logger.addHandler(handler)
app.logger.setLevel(logging.DEBUG)

# DB
db.init_app(app)

# -------------------------------------------------------------------
# Flask-Login
# -------------------------------------------------------------------

login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = "main.login"

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

# -------------------------------------------------------------------
# Blueprint principal
# -------------------------------------------------------------------

main = Blueprint("main", __name__)

# -------------------------------------------------------------------
# Décorateur admin
# -------------------------------------------------------------------

from functools import wraps

def admin_required(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        if not current_user.is_authenticated or current_user.role != "admin":
            flash("Accès réservé aux administrateurs.", "danger")
            return redirect(url_for("main.document_search"))
        return f(*args, **kwargs)
    return wrapper

# -------------------------------------------------------------------
# Mappages fichiers / MIME / apps
# -------------------------------------------------------------------

FILE_TYPE_ICONS = {
    'txt': 'txt-icon.png',
    'pdf': 'pdf-icon.png',
    'docx': 'docx-icon.png',
    'pptx': 'pptx-icon.png',
    'xlsx': 'xlsx-icon.png',
    'html': 'html-icon.png',
    'htm': 'html-icon.png',
    'md': 'md-icon.png',
    'csv': 'csv-icon.png',
    'json': 'json-icon.png',
    'xml': 'xml-icon.png',
}

MIME_TYPES = {
    'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    'odt': 'application/vnd.oasis.opendocument.text',
    'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    'ods': 'application/vnd.oasis.opendocument.spreadsheet',
    'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
    'odp': 'application/vnd.oasis.opendocument.presentation',
    'pdf': 'application/pdf',
    'txt': 'text/plain',
    'md': 'text/markdown',
    'html': 'text/html',
    'htm': 'text/html',
    'csv': 'text/csv',
    'json': 'application/json',
    'xml': 'application/xml',
}

DEFAULT_APPS_LINUX = {
    'docx': 'libreoffice',
    'pptx': 'libreoffice',
    'xlsx': 'libreoffice',
    'pdf': 'xdg-open',
    'txt': 'xdg-open',
    'md': 'code',
    'html': 'xdg-open',
    'htm': 'xdg-open',
    'csv': 'libreoffice',
    'json': 'code',
    'xml': 'code',
}

DEFAULT_APPS_WINDOWS = {
    'docx': 'start',
    'pptx': 'start',
    'xlsx': 'start',
    'pdf': 'start',
    'txt': 'notepad',
    'md': 'code',
    'html': 'start',
    'htm': 'start',
    'csv': 'start',
    'json': 'code',
    'xml': 'code',
}

def get_default_apps():
    system = platform.system()
    if system == 'Linux':
        return DEFAULT_APPS_LINUX
    elif system == 'Windows':
        return DEFAULT_APPS_WINDOWS
    return DEFAULT_APPS_LINUX

def open_with_default_app(file_path, file_type):
    try:
        default_apps = get_default_apps()
        app_cmd = default_apps.get(file_type, None)

        if app_cmd is None:
            return False

        if platform.system() == 'Windows':
            if app_cmd == 'start':
                os.startfile(file_path)
            elif app_cmd == 'code':
                subprocess.Popen(['code', file_path])
            elif app_cmd == 'notepad':
                subprocess.Popen(['notepad', file_path])
            else:
                subprocess.Popen([app_cmd, file_path], shell=True)
        else:
            if app_cmd == 'xdg-open':
                subprocess.Popen(['/usr/bin/xdg-open', file_path])
            else:
                subprocess.Popen([app_cmd, file_path])
        return True
    except Exception as e:
        current_app.logger.error(f"Erreur lors de l'ouverture du fichier : {e}")
        return False

# -------------------------------------------------------------------
# Utilitaires fichiers / contenu
# -------------------------------------------------------------------

def secure_filename(filename):
    filename = unicodedata.normalize('NFKD', filename)
    filename = filename.encode('ascii', 'ignore').decode('ascii')
    filename = re.sub(r'[^\w\-_.() ]', '_', filename)
    filename = re.sub(r'[() ]', '_', filename)
    return werkzeug_secure_filename(filename)

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']

def render_markdown(filepath):
    with open(filepath, 'r', encoding='utf-8') as f:
        return mistune.markdown(f.read())

def read_html_file(filepath):
    with open(filepath, 'r', encoding='utf-8') as f:
        return f.read()

def generate_toc(markdown_content):
    headers = re.findall(r'^(#{1,6})\s*(.*)', markdown_content, re.MULTILINE)
    toc = []
    for level, text in headers:
        indent = '  ' * (int(level) - 1)
        anchor = text.lower().replace(' ', '-').replace('.', '').replace(',', '').replace(';', '')
        toc.append(f"{indent}- [{text}](#{anchor})")
    return '\n'.join(toc) if toc else None

def sanitize_html(html_content):
    soup = BeautifulSoup(html_content, 'html.parser')
    for script in soup(["script", "iframe", "frame", "object", "embed"]):
        script.decompose()
    for tag in soup.find_all(True):
        for attr in ["onerror", "onload", "onclick", "style", "javascript"]:
            if attr in tag.attrs:
                del tag[attr]
    return str(soup)

def sync_documents_with_db():
    with app.app_context():
        documents_dir = app.config['UPLOAD_FOLDER']
        for doc in Document.query.all():
            if doc.filename:
                expected_path = os.path.join(documents_dir, doc.filename)
                if os.path.exists(expected_path):
                    if doc.file_path != expected_path:
                        doc.file_path = expected_path
                        db.session.commit()
                else:
                    current_app.logger.warning(f"File {doc.filename} not found")

def generate_file_hash(content):
    return hashlib.md5(content.encode('utf-8')).hexdigest()

def highlight_keywords(text, query):
    if not query:
        return text
    words = query.split()
    for word in words:
        text = re.sub(f'({re.escape(word)})', r'<span class="highlight">\1</span>', text, flags=re.IGNORECASE)
    return text

def _resolve_document_path(document):
    if document.file_path and os.path.exists(document.file_path):
        return document.file_path
    return None

def generate_summary(content, sentences_count=2):
    if not content:
        return None
    parser = PlaintextParser.from_string(content, Tokenizer("french"))
    summarizer = LexRankSummarizer()
    summary = summarizer(parser.document, sentences_count)
    return " ".join([str(sentence) for sentence in summary])

# -------------------------------------------------------------------
# Contexte global
# -------------------------------------------------------------------

@app.context_processor
def inject_documents_count():
    return dict(documents_count=Document.query.count())

@app.after_request
def after_request(response):
    response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
    response.headers["Pragma"] = "no-cache"
    response.headers["Expires"] = "0"
    return response

# -------------------------------------------------------------------
# Routes Auth
# -------------------------------------------------------------------

@main.route("/")
def index():
    if current_user.is_authenticated:
        return redirect(url_for("main.document_search"))
    return redirect(url_for("main.login"))

@main.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        username = request.form.get("username")
        password = request.form.get("password")

        user = User.query.filter_by(username=username).first()
        if user and user.check_password(password):
            login_user(user)
            flash("Connexion réussie", "success")
            return redirect(url_for("main.document_search"))

        flash("Identifiants invalides", "danger")

    return render_template("login.html")

@main.route("/register", methods=["GET", "POST"])
def register():
    if request.method == "POST":
        username = request.form.get("username")
        password = request.form.get("password")

        if User.query.filter_by(username=username).first():
            flash("Nom d'utilisateur déjà utilisé", "danger")
            return redirect(url_for("main.register"))

        user = User(username=username)
        user.set_password(password)
        db.session.add(user)
        db.session.commit()

        flash("Compte créé, vous pouvez vous connecter.", "success")
        return redirect(url_for("main.login"))

    return render_template("register.html")

@main.route("/logout")
@login_required
def logout():
    logout_user()
    flash("Déconnecté.", "success")
    return redirect(url_for("main.login"))

# -------------------------------------------------------------------
# Admin utilisateurs
# -------------------------------------------------------------------

@main.route("/admin")
@login_required
@admin_required
def admin():
    users = User.query.all()
    return render_template("admin.html", users=users)

@main.route("/admin/users")
@login_required
@admin_required
def users_list():
    users = User.query.all()
    return render_template("users_list.html", users=users)

@main.route('/admin/add_user', methods=['GET', 'POST'])
@login_required
@admin_required
def add_user():
    if request.method == 'POST':
        username = request.form['username']
        email = request.form['email']
        role = request.form['role']

        if User.query.filter_by(username=username).first():
            flash(f"L'utilisateur {username} existe déjà.", 'error')
            return redirect(url_for('main.add_user'))

        new_user = User(username=username, email=email, role=role)
        db.session.add(new_user)
        db.session.commit()

        flash(f"Utilisateur {username} ajouté avec succès !", 'success')
        return redirect(url_for('main.admin'))

    return render_template('add_user.html')

@main.route('/admin/edit_user/<int:user_id>', methods=['GET', 'POST'])
@login_required
@admin_required
def edit_user(user_id):
    user = User.query.get_or_404(user_id)
    if request.method == 'POST':
        user.role = request.form['role']
        db.session.commit()
        flash(f"Rôle de l'utilisateur {user.username} mis à jour avec succès!", "success")
        return redirect(url_for('main.admin'))

    return render_template('edit_user.html', user=user)

@main.route('/admin/delete_user/<int:user_id>', methods=['POST'])
@login_required
@admin_required
def delete_user(user_id):
    user = User.query.get_or_404(user_id)
    db.session.delete(user)
    db.session.commit()
    flash(f"Utilisateur {user.username} supprimé avec succès!", "success")
    return redirect(url_for('main.admin'))

# -------------------------------------------------------------------
# Upload documents
# -------------------------------------------------------------------

@main.route('/upload', methods=['GET', 'POST'])
@login_required
@admin_required
def upload():
    if request.method == 'POST':
        if 'file' not in request.files:
            flash('Aucun fichier sélectionné', 'error')
            return redirect(request.url)

        files = request.files.getlist('file')
        if not files or all(file.filename == '' for file in files):
            flash('Aucun fichier sélectionné', 'error')
            return redirect(request.url)

        for file in files:
            if file.filename == '':
                continue

            filename = secure_filename(file.filename)
            filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
            os.makedirs(os.path.dirname(filepath), exist_ok=True)

            try:
                file.save(filepath)

                file_type = filename.split('.')[-1].lower()
                document = Document(
                    title=filename,
                    description=request.form.get('description', ''),
                    file_type=file_type,
                    file_path=filepath,
                    filename=filename,
                    uploaded_by=current_user.username
                )
                db.session.add(document)
                db.session.commit()

            except Exception as e:
                db.session.rollback()
                flash(f"Erreur lors de l'upload du fichier {filename}: {str(e)}", 'error')
                return redirect(request.url)

        flash('Fichier(s) uploadé(s) avec succès!', 'success')
        return redirect(url_for('main.document_search'))

    return render_template('upload.html')

@main.route('/preview_upload', methods=['POST'])
@login_required
@admin_required
def preview_upload():
    if 'file' not in request.files:
        return jsonify({'error': 'Aucun fichier sélectionné'}), 400
    file = request.files['file']
    if file.filename == '':
        return jsonify({'error': 'Aucun fichier sélectionné'}), 400
    if file and allowed_file(file.filename):
        filename = secure_filename(file.filename)
        return jsonify({'filename': filename}), 200
    return jsonify({'error': 'Type de fichier non autorisé'}), 400

# -------------------------------------------------------------------
# Recherche documents
# -------------------------------------------------------------------

@main.route("/document_search")
@login_required
def document_search():
    def generate_short_summary(content, max_words=30):
        if not content:
            return None
        words = content.split()[:max_words]
        summary = " ".join(words)
        if len(content.split()) > max_words:
            summary += "..."
        return summary

    def extract_content(file_path):
        import os
        file_ext = os.path.splitext(file_path)[1].lower()

        try:
            if file_ext == '.txt':
                with open(file_path, 'r', encoding='utf-8') as file:
                    return file.read()
            elif file_ext == '.pdf':
                from PyPDF2 import PdfReader
                reader = PdfReader(file_path)
                return "".join(page.extract_text() for page in reader.pages)
            elif file_ext == '.docx':
                from docx import Document as DocxDocument
                doc = DocxDocument(file_path)
                return "\n".join([para.text for para in doc.paragraphs])
            elif file_ext in ['.pptx', '.ppt']:
                from pptx import Presentation
                prs = Presentation(file_path)
                return "\n".join(
                    shape.text for slide in prs.slides for shape in slide.shapes if hasattr(shape, "text")
                )
            return None
        except Exception as e:
            current_app.logger.error(f"Erreur extraction {file_path}: {e}")
            return None

    try:
        query = request.args.get('q', '')
        file_type = request.args.get('type', '')
        page = request.args.get('page', 1, type=int)
        per_page = 6

        documents_query
        