import json
import sqlite3
import os
import subprocess
import psutil
import socket
from flask import Flask, render_template, redirect, url_for, request, session, g, jsonify
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor

app = Flask(__name__)
app.secret_key = 'neuropulse_secret'

# Charger la config
with open('config.json') as f:
    config = json.load(f)

DATABASE = os.path.join(os.path.dirname(__file__), 'neuropulse.db')
executor = ThreadPoolExecutor(max_workers=4)  # Augmenté pour gérer plus de tâches

services_to_monitor = [
    {"name": "apache2", "title": "Apache arrêté", "desc": "Le service Apache ne fonctionne plus.", "crit": "Critique", "service": "Web/HTTP"},
    {"name": "mysql", "title": "MySQL arrêté", "desc": "Le service MySQL est hors ligne.", "crit": "Critique", "service": "Base de données"},
    {"name": "ssh", "title": "SSH arrêté", "desc": "Le service SSH est injoignable.", "crit": "Élevée", "service": "Accès distant"},
    {"name": "postgresql", "title": "PostgreSQL arrêté", "desc": "Le service PostgreSQL est hors ligne.", "crit": "Critique", "service": "Base de données"},
    {"name": "nginx", "title": "Nginx arrêté", "desc": "Le service Nginx ne fonctionne plus.", "crit": "Critique", "service": "Web/HTTP"},
]

LOG_FILE_PATH = config.get("log_file_path", "/var/log/syslog")
NETWORK_INTERFACE = config.get("network_interface", "eth0")

# Initialisation des statistiques réseau
last_network_stats = None
last_network_update = None

def get_db():
    db = getattr(g, '_database', None)
    if db is None:
        db = g._database = sqlite3.connect(DATABASE)
        db.row_factory = sqlite3.Row
    return db

def init_db():
    with app.app_context():
        db = get_db()
        db.execute('''
            CREATE TABLE IF NOT EXISTS tickets (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                titre TEXT NOT NULL,
                description TEXT NOT NULL,
                criticite TEXT NOT NULL,
                service TEXT NOT NULL,
                date_creation TEXT DEFAULT CURRENT_TIMESTAMP
            );
        ''')
        db.execute('''
            CREATE TABLE IF NOT EXISTS executor_logs (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                commande TEXT NOT NULL,
                resultat TEXT NOT NULL,
                date_execution TEXT DEFAULT CURRENT_TIMESTAMP
            );
        ''')
        db.commit()

@app.teardown_appcontext
def close_connection(exception):
    db = getattr(g, '_database', None)
    if db is not None:
        db.close()

def ticket_exists_today(titre):
    db = get_db()
    today = datetime.now().strftime('%Y-%m-%d')
    cur = db.execute("SELECT COUNT(*) FROM tickets WHERE titre = ? AND date(date_creation) = ?", (titre, today))
    return cur.fetchone()[0] > 0

def is_service_running(service_name):
    try:
        status = subprocess.check_output(['systemctl', 'is-active', service_name], stderr=subprocess.DEVNULL)
        return b'active' in status
    except Exception:
        return False

def get_system_metrics():
    # CPU
    cpu_percent = psutil.cpu_percent(interval=0.5)
    cpu_cores = psutil.cpu_count(logical=True)
    cpu_freq = psutil.cpu_freq().current if hasattr(psutil, 'cpu_freq') else "N/A"
    
    # RAM
    ram = psutil.virtual_memory()
    ram_percent = ram.percent
    ram_total = ram.total
    ram_used = ram.used
    swap = psutil.swap_memory()
    ram_swap = swap.used
    ram_swap_total = swap.total
    
    # Disque
    try:
        disk = psutil.disk_usage('/')
        disk_percent = disk.percent
        disk_total = disk.total
        disk_free = disk.free
        disk_used = disk.used
    except Exception:
        disk_percent = 0
        disk_total = 0
        disk_free = 0
        disk_used = 0
    
    # Réseau
    global last_network_stats, last_network_update
    network_stats = psutil.net_io_counters(pernic=True).get(NETWORK_INTERFACE, None)
    
    # Services
    services_status = []
    for service in services_to_monitor:
        services_status.append({
            "name": service["name"],
            "running": is_service_running(service["name"])
        })
    
    # Uptime
    uptime = psutil.boot_time()
    
    # Température CPU (si disponible)
    try:
        temps = psutil.sensors_temperatures()
        cpu_temp = temps['coretemp'][0].current if 'coretemp' in temps else None
    except Exception:
        cpu_temp = None
    
    # Adresse IP
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(("8.8.8.8", 80))
        network_ip = s.getsockname()[0]
        s.close()
    except Exception:
        network_ip = "N/A"
    
    return {
        "cpu": cpu_percent,
        "ram": ram_percent,
        "disk": disk_percent,
        "cpu_cores": cpu_cores,
        "cpu_freq": cpu_freq,
        "cpu_temp": cpu_temp,
        "ram_total": ram_total,
        "ram_used": ram_used,
        "ram_swap": ram_swap,
        "ram_swap_total": ram_swap_total,
        "disk_total": disk_total,
        "disk_free": disk_free,
        "disk_used": disk_used,
        "network_stats": network_stats._asdict() if network_stats else None,
        "services": services_status,
        "uptime": datetime.now().timestamp() - uptime,
        "network_ip": network_ip
    }

def check_alerts(stats):
    with app.app_context():
        db = get_db()

        # CPU
        if stats.get("cpu", 0) > 80 and not ticket_exists_today("CPU élevé"):
            db.execute('''
                INSERT INTO tickets (titre, description, criticite, service)
                VALUES (?, ?, ?, ?)
            ''', ("CPU élevé", f"Utilisation CPU à {stats['cpu']}%", "Élevée", "Système"))
            db.commit()

        # RAM
        if stats.get("ram", 0) > 90 and not ticket_exists_today("RAM critique"):
            db.execute('''
                INSERT INTO tickets (titre, description, criticite, service)
                VALUES (?, ?, ?, ?)
            ''', ("RAM critique", f"Utilisation RAM à {stats['ram']}%", "Critique", "Système"))
            db.commit()

        # Disque
        if stats.get("disk", 0) > 90 and not ticket_exists_today("Espace disque critique"):
            db.execute('''
                INSERT INTO tickets (titre, description, criticite, service)
                VALUES (?, ?, ?, ?)
            ''', ("Espace disque critique", f"Utilisation disque à {stats['disk']}%", "Critique", "Système"))
            db.commit()

        # Services
        for svc in services_to_monitor:
            if not is_service_running(svc["name"]) and not ticket_exists_today(svc["title"]):
                db.execute('''
                    INSERT INTO tickets (titre, description, criticite, service)
                    VALUES (?, ?, ?, ?)
                ''', (svc["title"], svc["desc"], svc["crit"], svc["service"]))
                db.commit()

def async_check_alerts(stats):
    executor.submit(check_alerts, stats)

def read_last_log_lines(log_path, lines=20):
    try:
        with open(log_path, 'rb') as f:
            f.seek(0, os.SEEK_END)
            filesize = f.tell()
            buffer = bytearray()
            pointer = filesize - 1
            count_lines = 0

            while pointer >= 0 and count_lines < lines:
                f.seek(pointer)
                byte = f.read(1)
                if byte == b'\n' and pointer != filesize - 1:
                    count_lines += 1
                    if count_lines == lines:
                        break
                buffer.extend(byte)
                pointer -= 1

            buffer.reverse()
            return buffer.decode(errors='replace').strip()
    except Exception as e:
        return f"Erreur lecture log: {e}"

@app.route('/')
def index():
    if 'user' in session:
        return redirect(url_for('dashboard'))
    return redirect(url_for('login'))

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        if username == 'admin' and password == 'admin':
            session['user'] = username
            return redirect(url_for('dashboard'))
        else:
            return render_template('login.html', error='Identifiants invalides')
    return render_template('login.html')

@app.route('/dashboard')
def dashboard():
    if 'user' not in session:
        return redirect(url_for('login'))

    stats = get_system_metrics()
    async_check_alerts(stats)

    db = get_db()
    cur = db.execute('SELECT * FROM tickets ORDER BY date_creation DESC LIMIT 5')
    tickets = cur.fetchall()

    return render_template('dashboard.html', 
                           stats=stats, 
                           tickets=tickets,
                           LOG_FILE_PATH=LOG_FILE_PATH)

@app.route('/api/stats')
def api_stats():
    if 'user' not in session:
        return jsonify({"error": "Unauthorized"}), 401

    stats = get_system_metrics()
    async_check_alerts(stats)

    log_excerpt = read_last_log_lines(LOG_FILE_PATH, lines=20)

    return jsonify({
        "cpu": stats.get("cpu", 0),
        "ram": stats.get("ram", 0),
        "disk": stats.get("disk", 0),
        "cpu_cores": stats.get("cpu_cores", 0),
        "cpu_freq": stats.get("cpu_freq", 0),
        "cpu_temp": stats.get("cpu_temp", 0),
        "ram_total": stats.get("ram_total", 0),
        "ram_used": stats.get("ram_used", 0),
        "ram_swap": stats.get("ram_swap", 0),
        "ram_swap_total": stats.get("ram_swap_total", 0),
        "disk_total": stats.get("disk_total", 0),
        "disk_free": stats.get("disk_free", 0),
        "disk_used": stats.get("disk_used", 0),
        "network_stats": stats.get("network_stats", None),
        "services": stats.get("services", []),
        "uptime": stats.get("uptime", 0),
        "network_ip": stats.get("network_ip", "N/A"),
        "log_excerpt": log_excerpt
    })

@app.route('/executor', methods=['GET', 'POST'])
def web_executor():
    output = ''
    if request.method == 'POST':
        cmd = request.form['command']
        try:
            output = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, text=True, timeout=30)
        except subprocess.CalledProcessError as e:
            output = f"Erreur (code {e.returncode}): {e.output}"
        except subprocess.TimeoutExpired:
            output = "Erreur: Commande a expiré (timeout 30s)"
        except Exception as e:
            output = f"Erreur inattendue: {str(e)}"

        # Sauvegarder dans la base
        db = get_db()
        db.execute('''
            INSERT INTO executor_logs (commande, resultat)
            VALUES (?, ?)
        ''', (cmd, output))
        db.commit()

    return render_template('executor.html', output=output)

@app.route('/executor/logs')
def executor_logs():
    if 'user' not in session:
        return redirect(url_for('login'))
    
    db = get_db()
    cur = db.execute('SELECT id, commande, resultat, date_execution FROM executor_logs ORDER BY id DESC')
    logs = cur.fetchall()

    return render_template('executor_logs.html', logs=logs)

@app.route('/tickets')
def tickets():
    if 'user' not in session:
        return redirect(url_for('login'))

    db = get_db()
    cur = db.execute('SELECT * FROM tickets ORDER BY date_creation DESC')
    all_tickets = cur.fetchall()

    return render_template('tickets.html', tickets=all_tickets)

@app.route('/tickets/<int:ticket_id>')
def ticket_detail(ticket_id):
    if 'user' not in session:
        return redirect(url_for('login'))

    db = get_db()
    cur = db.execute('SELECT * FROM tickets WHERE id = ?', (ticket_id,))
    ticket = cur.fetchone()

    if not ticket:
        return "Ticket introuvable", 404

    return render_template('ticket_detail.html', ticket=ticket)

@app.route('/tickets/new', methods=['GET', 'POST'])
def new_ticket():
    if 'user' not in session:
        return redirect(url_for('login'))

    if request.method == 'POST':
        titre = request.form['title']
        description = request.form['description']
        criticite = request.form['criticite']
        service = request.form['service']

        db = get_db()
        db.execute(
            'INSERT INTO tickets (titre, description, criticite, service) VALUES (?, ?, ?, ?)',
            (titre, description, criticite, service)
        )
        db.commit()
        return redirect(url_for('tickets'))

    return render_template('new_ticket.html')

@app.route('/logout')
def logout():
    session.pop('user', None)
    return redirect(url_for('login'))

if __name__ == '__main__':
    init_db()
    app.run(host='0.0.0.0', port=5000, debug=True)