from PIL import Image
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from io import BytesIO
import matplotlib.dates as mdates
from flask import Flask, request, render_template, redirect, url_for, session, jsonify, Response, send_file
from flask_socketio import SocketIO, join_room, leave_room, emit
from pymongo import MongoClient
from bson.objectid import ObjectId
import uuid
import datetime
from flask_wtf.csrf import CSRFProtect
from werkzeug.security import generate_password_hash, check_password_hash
import subprocess
import signal
import threading
import psutil
import time
import json
import requests
import os
from datetime import datetime, timedelta

app = Flask(__name__, static_folder='/var/www/admin_website/static', template_folder='/var/www/admin_website/templates')
app.secret_key = os.urandom(24)
socketio = SocketIO(app, cors_allowed_origins="*", async_mode="threading")

csrf = CSRFProtect(app)

client = MongoClient("mongodb+srv://MrSniff:5169Galaxy@chainsaw.d0shx.mongodb.net/")
MONGO_URI = "mongodb+srv://MrSniff:5169Galaxy@chainsaw.d0shx.mongodb.net/"
db = client["moonlab"]
collection = db["anti_cheat"]
accounts_collection = db["website_accounts"]
delete_requests_collection = db["delete_requests"]
logs_collection = db["logs"]
user_ages_collection = db["user_ages"]
projects_collection = db["projects"]
changelogs_collection = db["changelogs"]
appointments_collection = db["appointments"]
scpsl_playerdata_collection = db["scpsl-playerdata"]

ETS2_DB_NAME = "ets2_license_db"
ETS2_COLLECTION_NAME = "licenses"
ets2_db = client[ETS2_DB_NAME]
ets2_licenses = ets2_db[ETS2_COLLECTION_NAME]

SCP_SL_API_URL = "https://api.scplist.kr/api/servers/85734"

app.config.update(
    SESSION_COOKIE_HTTPONLY=True,
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_SAMESITE='Lax',
    REMEMBER_COOKIE_HTTPONLY=True,
    REMEMBER_COOKIE_SECURE=True,
    REMEMBER_COOKIE_SAMESITE='Lax'
)

if accounts_collection.count_documents({}) == 0:
    accounts_collection.insert_one({
        "username": "Admin",
        "display_name": "Admin",
        "password": generate_password_hash("1812"),
        "permissions": ["user_management", "permission_management", "delete_requests", "logs", "create_license_key",
                        "view_license_keys", "create_event_feedback", "view_event_feedback", "verify_age",
                        "project_manager", "ip_checker", "changelogs"]
    })


def generate_license_key():
    return str(uuid.uuid4())


def add_license_key_to_db(license_key):
    if collection.find_one({"license_key": license_key}):
        return False
    else:
        collection.insert_one({"license_key": license_key, "status": "unused"})
        return True


def add_log(message):
    logs_collection.insert_one({"timestamp": datetime.now(), "message": message})


def stream_logs(process):
    def generate():
        for line in iter(process.stdout.readline, b''):
            yield line.decode('utf-8')

    return Response(generate(), mimetype='text/plain')


def load_bot_config():
    try:
        with open("bot_config.json", "r") as file:
            return json.load(file)
    except FileNotFoundError:
        return {}


def save_bot_config(config):
    with open("bot_config.json", "w") as file:
        json.dump(config, file, indent=4)


bot_config = load_bot_config()


@app.route("/website.py", methods=["GET"])
def deny_access():
    return "Access denied", 403


@app.route("/", methods=["GET", "POST"])
def login():
    error = None
    if request.method == "POST":
        username = request.form["username"]
        password = request.form["password"]
        remember_me = "remember_me" in request.form

        user = accounts_collection.find_one({"username": username})
        if user and check_password_hash(user["password"], password):
            session["user_id"] = str(user["_id"])
            session["username"] = user["username"]
            session["display_name"] = user.get("display_name", user["username"])
            session["permissions"] = user.get("permissions", [])
            add_log(f"{username} hat sich eingeloggt")

            if remember_me:
                session.permanent = True

            return redirect(url_for("dashboard"))
        else:
            error = "Du hast ein(en) falsches Passwort/Benutzernamen eingegeben"
    return render_template("login.html", error=error)


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

    user = accounts_collection.find_one({"_id": ObjectId(session["user_id"])})
    if not user:
        return redirect(url_for("login"))

    session["permissions"] = user.get("permissions", [])

    if "Style" not in user:
        accounts_collection.update_one({"_id": ObjectId(session["user_id"])}, {"$set": {"Style": "lightmode"}})
        session["theme"] = "lightmode"
    else:
        session["theme"] = user["Style"]

    if session["theme"] == "darkmode":
        return render_template("dashboard_dark.html")
    else:
        return render_template("dashboard_light.html")


@app.route('/view_changelogs', methods=['GET'])
def view_changelogs():
    if "username" not in session:
        return redirect(url_for("login"))
    changelogs = [
        {"text": changelog["text"], "timestamp": changelog["timestamp"]}
        for changelog in changelogs_collection.find().sort("timestamp", -1)
    ]
    return render_template("view_changelogs.html", changelogs=changelogs)


@app.route("/user_management", methods=["GET", "POST"])
def user_management():
    if "username" not in session or "user_management" not in session["permissions"]:
        return redirect(url_for("login"))
    users = accounts_collection.find()
    return render_template("user_management.html", users=users)


@app.route("/add_user", methods=["POST"])
def add_user():
    if "username" not in session or "user_management" not in session["permissions"]:
        return redirect(url_for("login"))
    new_username = request.form["username"]
    new_display_name = request.form["display_name"]
    new_password = request.form["password"]
    new_permissions = request.form.getlist("permissions")

    if accounts_collection.find_one({"username": new_username}):
        return "Benutzername existiert bereits", 400

    accounts_collection.insert_one({
        "username": new_username,
        "display_name": new_display_name,
        "password": generate_password_hash(new_password),
        "permissions": new_permissions
    })
    add_log(f"{session['username']} created or modified user {new_username}")
    return redirect(url_for("user_management"))


@app.route("/change_permissions/<user_id>", methods=["POST"])
def change_permissions(user_id):
    if "username" not in session or "permission_management" not in session["permissions"]:
        return redirect(url_for("login"))

    user = accounts_collection.find_one({"_id": ObjectId(user_id)})
    if not user:
        return "User not found", 404

    if "remove_permission" in request.form:
        permission_to_remove = request.form["remove_permission"]
        accounts_collection.update_one({"_id": ObjectId(user_id)}, {"$pull": {"permissions": permission_to_remove}})
        add_log(f"{session['username']} removed permission {permission_to_remove} from user {user['username']}")

    if "add_permission" in request.form:
        permission_to_add = request.form["add_permission"]
        accounts_collection.update_one({"_id": ObjectId(user_id)}, {"$addToSet": {"permissions": permission_to_add}})
        add_log(f"{session['username']} added permission {permission_to_add} to user {user['username']}")

    if str(user["_id"]) == session.get("user_id"):
        updated_user = accounts_collection.find_one({"_id": ObjectId(user_id)})
        session["permissions"] = updated_user.get("permissions", [])

    return redirect(url_for("user_management"))


@app.route("/delete_user/<user_id>", methods=["POST"])
def delete_user(user_id):
    if "username" not in session or "user_management" not in session["permissions"]:
        return redirect(url_for("login"))
    user = accounts_collection.find_one({"_id": ObjectId(user_id)})
    if user:
        accounts_collection.delete_one({"_id": ObjectId(user_id)})
        add_log(f"{session['username']} deleted user {user['username']}")
    return redirect(url_for("user_management"))


UPLOAD_FOLDER = "/var/www/admin_website/static/pfps"
ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg"}


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


@app.route("/edit_user/<user_id>", methods=["GET", "POST"])
def edit_user(user_id):
    if "username" not in session:
        return redirect(url_for("login"))

    user = accounts_collection.find_one({"_id": ObjectId(user_id)})

    if "permission_management" not in session["permissions"] and session["user_id"] != str(user["_id"]):
        return "Access denied", 403

    if request.method == "POST":
        new_display_name = request.form.get("display_name")
        new_password = request.form.get("password")
        profile_pic = request.files.get("pfp")

        update_data = {"display_name": new_display_name}

        pfp_uploaded = False
        pfp_url = None

        if new_password:
            update_data["password"] = generate_password_hash(new_password, method='scrypt')

        if profile_pic and profile_pic.filename != "":
            ext = profile_pic.filename.rsplit('.', 1)[-1].lower()

            if ext in ["png", "jpg", "jpeg"]:
                filename = f"{user_id}.png"
                upload_folder = "/var/www/admin_website/static/pfps"
                os.makedirs(upload_folder, exist_ok=True)
                save_path = os.path.join(upload_folder, filename)
                try:
                    img = Image.open(profile_pic.stream)
                    if img.mode == 'RGBA' or (img.mode == 'P' and 'transparency' in img.info):
                        img.save(save_path, "PNG")
                    else:
                        img = img.convert("RGB")
                        img.save(save_path, "PNG")
                    website_data = user.get("website_accounts", {})
                    website_data["pfp_last_updated"] = int(time.time())
                    website_data["pfp_filename"] = filename
                    update_data["website_accounts"] = website_data
                    pfp_uploaded = True
                    pfp_url = f"/static/pfps/{filename}"
                except Exception as e:
                    print(f"Fehler beim Speichern oder Konvertieren des Profilbildes: {e}")
            else:
                print(f"Unerlaubter Dateityp für Profilbild: {ext}")

        accounts_collection.update_one({"_id": ObjectId(user_id)}, {"$set": update_data})

        if user_id == session.get("user_id"):
            session["display_name"] = new_display_name
            if pfp_uploaded:
                session["pfp_last_updated"] = update_data["website_accounts"]["pfp_last_updated"]

        if pfp_uploaded:
            return jsonify({"success": True, "message": "Profilbild erfolgreich aktualisiert!", "pfp_url": pfp_url})
        else:
            return jsonify({"success": True, "message": "Profil erfolgreich aktualisiert!", "pfp_url": None})

    return render_template("edit_user.html", user=user)


@app.route('/get_data', methods=['GET'])
def get_data():
    if "username" not in session:
        return redirect(url_for("login"))
    data = collection.find()
    result = []
    for doc in data:
        if 'ingame_name' in doc and doc['ingame_name']:
            doc['_id'] = str(doc['_id'])
            result.append(doc)
    return render_template("get_data.html", data=result)


@app.route('/license=<license_key>', methods=['GET'])
def view_license(license_key):
    if "username" not in session:
        return redirect(url_for("login"))
    license_data = collection.find_one({"license_key": license_key})
    if license_data:
        license_data['_id'] = str(license_data['_id'])
        return render_template("view_license.html", license_data=license_data)
    else:
        return "Lizenzschlüssel nicht gefunden", 404


@app.route('/delete_license/<license_key>', methods=['POST'])
def delete_license(license_key):
    if "username" not in session:
        return redirect(url_for("login"))
    collection.delete_one({"license_key": license_key})
    add_log(f"{session['username']} hat Lizenzschlüssel {license_key} gelöscht")
    return redirect(url_for("get_data"))


@app.route('/request_delete_license/<license_key>', methods=['POST'])
def request_delete_license(license_key):
    if "username" not in session:
        return redirect(url_for("login"))
    delete_requests_collection.insert_one({"license_key": license_key, "username": session["username"]})
    add_log(f"{session['username']} hat eine Delete-Request für Lizenzschlüssel {license_key} gestellt")
    return redirect(url_for("get_data"))


@app.route('/delete_requests', methods=['GET'])
def delete_requests():
    if "username" not in session or "delete_requests" not in session["permissions"]:
        return redirect(url_for("login"))
    delete_requests = list(delete_requests_collection.find())
    return render_template("delete_request.html", delete_requests=delete_requests)


@app.route('/approve_delete_request/<request_id>', methods=['POST'])
def approve_delete_request(request_id):
    if "username" not in session or "delete_requests" not in session["permissions"]:
        return redirect(url_for("login"))
    request = delete_requests_collection.find_one({"_id": ObjectId(request_id)})
    if request:
        collection.delete_one({"license_key": request["license_key"]})
        delete_requests_collection.delete_one({"_id": ObjectId(request_id)})
        add_log(f"{session['username']} hat die Delete-Request für Lizenzschlüssel {request['license_key']} genehmigt")
    return redirect(url_for("delete_requests"))


@app.route('/deny_delete_request/<request_id>', methods=['POST'])
def deny_delete_request(request_id):
    if "username" not in session or "delete_requests" not in session["permissions"]:
        return redirect(url_for("login"))
    delete_requests_collection.delete_one({"_id": ObjectId(request_id)})
    add_log(f"{session['username']} hat die Delete-Request für Lizenzschlüssel {request_id} abgelehnt")
    return redirect(url_for("delete_requests"))


@app.route('/add_data', methods=['POST'])
def add_data():
    if "username" not in session:
        return redirect(url_for("login"))

    new_data = request.get_json()

    result = collection.insert_one(new_data)

    return jsonify({"message": "Daten erfolgreich hinzugefügt", "inserted_id": str(result.inserted_id)})


@app.route('/create_license_key', methods=['GET', 'POST'])
def create_license_key():
    if "username" not in session or "create_license_key" not in session["permissions"]:
        return redirect(url_for("login"))
    if request.method == "POST":
        license_key = generate_license_key()
        if add_license_key_to_db(license_key):
            add_log(f"{session['username']} hat Lizenzschlüssel {license_key} erstellt")
            return render_template("create_license_key.html", license_key=license_key)
        else:
            return render_template("create_license_key.html", error="Fehler beim Erstellen des Lizenzschlüssels")
    return render_template("create_license_key.html")


@app.route('/database_entries', methods=['GET'])
def database_entries():
    if "username" not in session:
        return redirect(url_for("login"))
    data = collection.find()
    result = []
    for doc in data:
        doc['_id'] = str(doc['_id'])
        result.append(doc)
    return render_template("view_license.html", data=result)


@app.route('/license_keys', methods=['GET'])
def license_keys():
    if "username" not in session:
        return redirect(url_for("login"))
    return render_template("license_keys.html")


@app.route('/search_license_key', methods=['GET'])
def search_license_key():
    license_key = request.args.get('license_key')
    license_data = collection.find_one({"license_key": license_key})
    if license_data:
        return jsonify({"found": True})
    else:
        return jsonify({"found": False})


@app.route('/logs', methods=['GET'])
def logs():
    if "username" not in session or "logs" not in session["permissions"]:
        return redirect(url_for("login"))
    logs = list(logs_collection.find().sort("timestamp", -1))
    return render_template("logs.html", logs=logs)


@app.route('/event_feedback', methods=['GET', 'POST'])
def event_feedback():
    if "username" not in session or "create_event_feedback" not in session["permissions"]:
        return redirect(url_for("login"))
    if request.method == "POST":
        event_name = request.form["event_name"]
        if event_name == "Anderes":
            event_name = request.form["other_event_name"]
        event_progress = request.form["event_progress"]
        if event_progress == "Anderes":
            event_progress = request.form["other_event_progress"]
        event_end = request.form["event_end"]
        if event_end == "Anderes":
            event_end = request.form["other_event_end"]
        community_feedback = request.form["community_feedback"]
        if community_feedback == "Anderes":
            community_feedback = request.form["other_community_feedback"]
        improvement_suggestions = request.form["improvement_suggestions"]
        feedback_data = {
            "event_name": event_name,
            "event_progress": event_progress,
            "event_end": event_end,
            "community_feedback": community_feedback,
            "improvement_suggestions": improvement_suggestions,
            "timestamp": datetime.now(),
            "username": session["username"]
        }
        db.event_feedback.insert_one(feedback_data)
        add_log(f"{session['username']} hat Feedback für das Event {event_name} abgegeben")
        return redirect(url_for("dashboard"))
    return render_template("event_feedback.html")


@app.route('/event=<event_id>', methods=['GET'])
def view_event_feedback(event_id):
    if "username" not in session:
        return redirect(url_for("login"))
    feedback = db.event_feedback.find_one({"_id": ObjectId(event_id)})
    if feedback:
        feedback['_id'] = str(feedback['_id'])
        return render_template("view_event_feedback.html", feedback=feedback)
    else:
        return "Event-Feedback nicht gefunden", 404


@app.route('/view_event_feedbacks', methods=['GET'])
def view_event_feedbacks():
    if "username" not in session or "view_event_feedback" not in session["permissions"]:
        return redirect(url_for("login"))
    feedbacks = list(db.event_feedback.find().sort("timestamp", -1))
    return render_template("view_event_feedbacks.html", feedbacks=feedbacks)


@app.route("/events", methods=["GET", "POST"])
@csrf.exempt
def events():
    if "username" not in session:
        return redirect(url_for("login"))

    if request.method == "POST":
        event_name = request.form["event_name"]
        community_feedback = request.form["community_feedback"]
        players = int(request.form["players"])
        time = request.form["time"]
        weekday = request.form["weekday"]
        top = 1 if community_feedback == "top" else 0
        flop = 1 if community_feedback == "flop" else 0
        etwas = 1 if community_feedback == "etwas" else 0
        rating = top - flop + etwas

        event = db.events.find_one({"name": event_name, "time": time, "weekday": weekday})
        if event:
            db.events.update_one(
                {"_id": event["_id"]},
                {"$inc": {"top": top, "flop": flop, "etwas": etwas, "rating": rating}}
            )
            add_log(f"{session['username']} updated event {event_name} with rating {rating}")
        else:
            db.events.insert_one({
                "name": event_name,
                "players": players,
                "time": time,
                "weekday": weekday,
                "top": top,
                "flop": flop,
                "etwas": etwas,
                "rating": rating,
                "timestamp": datetime.now()
            })
            add_log(f"{session['username']} added event {event_name} with rating {rating}")

    events = list(db.events.find().sort("timestamp", -1))
    best_event = None
    best_events = None
    if request.args.get("calculate_best"):
        current_players = int(request.args.get("current_players"))
        current_time = request.args.get("current_time")
        current_weekday = request.args.get("current_weekday")
        event_type = request.args.get("event_type", None)
        best_events = calculate_best_events(current_players, current_time, current_weekday, event_type)
        if best_events:
            best_event = best_events[0]

    return render_template("events.html", events=events, best_event=best_event)


def calculate_best_events(current_players, current_time, current_weekday, event_type=None):
    events = list(db.events.find())
    scored_events = []
    for event in events:
        similarity_score = 0
        similarity_score -= abs(event['players'] - current_players) * 0.5
        if event['weekday'] != current_weekday:
            similarity_score -= 1
        similarity_score -= abs(int(event['time'].split(':')[0]) - int(current_time.split(':')[0])) * 0.2
        similarity_score += (event['rating'] * 1.0)
        if event_type and event.get('type') == event_type:
            similarity_score += 0.1
        scored_events.append((similarity_score, event))
    scored_events.sort(reverse=True, key=lambda x: x[0])
    return [e for _, e in scored_events[:5]]


@app.route("/dc_bots", methods=["GET"])
def dc_bots():
    if "username" not in session or "discord_bots" not in session["permissions"]:
        return redirect(url_for("login"))
    bots = list(db.dc_bots.find())
    return render_template("dc_bots.html", bots=bots)


@app.route("/add_dc_bot", methods=["POST"])
def add_dc_bot():
    if "username" not in session or "discord_bots" not in session["permissions"]:
        return redirect(url_for("login"))
    bot_name = request.form["bot_name"]
    bot_directory = request.form["bot_directory"]
    db.dc_bots.insert_one({"name": bot_name, "directory": bot_directory})
    add_log(f"{session['username']} added bot {bot_name}")
    return redirect(url_for("dc_bots"))


@app.route("/upload_bot_version/<bot_name>", methods=["POST"])
def upload_bot_version(bot_name):
    if "username" not in session or "discord_bots" not in session["permissions"]:
        return redirect(url_for("login"))

    bot = db.dc_bots.find_one({"name": bot_name})
    if not bot:
        return f"Bot {bot_name} nicht gefunden", 404

    if "bot_file" not in request.files:
        return "Keine Datei hochgeladen", 400

    bot_file = request.files["bot_file"]
    if bot_file.filename == "":
        return "Keine Datei ausgewählt", 400

    version = bot.get("version", "1.0")
    major, minor = map(int, version.split("."))
    new_version = f"{major}.{minor + 1}"

    base_directory_name = bot['directory'].rsplit('_v', 1)[0] if '_v' in bot['directory'] else bot['directory']
    bot_directory_with_version = f"{base_directory_name}_v{new_version}"

    os.makedirs(bot_directory_with_version, exist_ok=True)
    bot_file.save(os.path.join(bot_directory_with_version, bot_file.filename))

    db.dc_bots.update_one({"name": bot_name},
                          {"$set": {"directory": bot_directory_with_version, "version": new_version}})
    add_log(f"{session['username']} uploaded new version {new_version} for bot {bot_name}")

    return redirect(url_for("dc_bots"))


@app.route("/revert_bot_version/<bot_name>/<version>", methods=["POST"])
def revert_bot_version(bot_name, version):
    if "username" not in session or "discord_bots" not in session["permissions"]:
        return redirect(url_for("login"))

    bot = db.dc_bots.find_one({"name": bot_name})
    if not bot:
        return f"Bot {bot_name} nicht gefunden", 404

    base_directory_name = bot['directory'].rsplit('_v', 1)[0] if '_v' in bot['directory'] else bot['directory']
    bot_directory = f"{base_directory_name}_v{version}"

    if not os.path.exists(bot_directory):
        return f"Version {version} für Bot {bot_name} nicht gefunden", 404

    db.dc_bots.update_one({"name": bot_name}, {"$set": {"directory": bot_directory, "version": version}})
    add_log(f"{session['username']} reverted bot {bot_name} to version {version}")

    return redirect(url_for("dc_bots"))


def initialize_bot_versions():
    bots = db.dc_bots.find({"version": {"$exists": False}})
    for bot in bots:
        db.dc_bots.update_one({"_id": bot["_id"]}, {"$set": {"version": "1.0"}})
        add_log(f"Initialized version 1.0 for bot {bot['name']}")


initialize_bot_versions()

if db.events.count_documents({}) == 0:
    db.events.insert_one({
        "name": "Sample Event",
        "top": 0,
        "flop": 0,
        "rating": 0,
        "timestamp": datetime.now()
    })


def save_logs_to_file(process, log_file_path):
    try:
        with open(log_file_path, "w") as log_file:
            while True:
                output = process.stdout.readline()
                error = process.stderr.readline()

                if output:
                    log_file.write(repr(output) + "\n")
                    log_file.flush()
                    print(output, end="")
                if error:
                    log_file.write("[ERROR] " + repr(error) + "\n")
                    log_file.flush()
                    print("[ERROR] " + error, end="")

                if process.poll() is not None:
                    for line in process.stdout.readlines():
                        log_file.write(repr(line) + "\n")
                    for line in process.stderr.readlines():
                        log_file.write("[ERROR] " + repr(line) + "\n")
                    break

                time.sleep(0.5)
    except Exception as e:
        print(f"Fehler beim Speichern der Logs: {str(e)}")


@app.route("/turn_on_bot/<bot_name>", methods=["POST"])
def turn_on_bot(bot_name):
    if "username" not in session or "discord_bots" not in session["permissions"]:
        return redirect(url_for("login"))

    bot = db.dc_bots.find_one({"name": bot_name})
    if bot:
        if "pid" in bot and psutil.pid_exists(bot["pid"]):
            add_log(f"Bot {bot_name} is already running with PID {bot['pid']}")
            return f"Bot {bot_name} läuft bereits.", 400

        bot_directory = bot["directory"]
        try:
            command = ["python", bot_directory]

            process = subprocess.Popen(
                command,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True
            )

            log_file_path = f"/tmp/{bot_name}_log.txt"
            db.dc_bots.update_one({"name": bot_name}, {
                "$set": {
                    "pid": process.pid,
                    "log_file": log_file_path
                }
            })

            threading.Thread(target=save_logs_to_file, args=(process, log_file_path)).start()

            bot_config[bot_name] = {"status": "on", "pid": process.pid}
            save_bot_config(bot_config)

            add_log(f"{session['username']} turned on {bot_name}")
        except Exception as e:
            add_log(f"Error starting bot {bot_name}: {str(e)}")
            return f"Fehler beim Starten des Bots {bot_name}: {str(e)}", 500
    else:
        return f"Bot {bot_name} nicht gefunden", 404

    return redirect(url_for("dc_bots"))


@app.route("/turn_off_bot/<bot_name>", methods=["POST"])
def turn_off_bot(bot_name):
    if "username" not in session or "discord_bots" not in session["permissions"]:
        return redirect(url_for("login"))

    bot = db.dc_bots.find_one({"name": bot_name})
    if bot and "pid" in bot:
        try:
            pid = bot["pid"]

            if psutil.pid_exists(pid):
                os.kill(pid, signal.SIGTERM)
                db.dc_bots.update_one({"name": bot_name}, {"$unset": {"pid": ""}})

                bot_config[bot_name] = {"status": "off"}
                save_bot_config(bot_config)

                add_log(f"{session['username']} turned off {bot_name}")
            else:
                add_log(f"Process for bot {bot_name} does not exist")
                db.dc_bots.update_one({"name": bot_name}, {"$unset": {"pid": ""}})
        except Exception as e:
            add_log(f"Error stopping bot {bot_name}: {str(e)}")
            return f"Fehler beim Stoppen des Bots {bot_name}: {str(e)}", 500
    else:
        return f"Bot {bot_name} nicht gefunden oder kein aktiver Prozess vorhanden", 404

    return redirect(url_for("dc_bots"))


@app.route("/turn_on_all_bots", methods=["POST"])
def turn_on_all_bots():
    if "username" not in session or "discord_bots" not in session["permissions"]:
        return redirect(url_for("login"))

    bots = list(db.dc_bots.find())
    for bot in bots:
        if "pid" not in bot or not psutil.pid_exists(bot["pid"]):
            bot_directory = bot["directory"]
            try:
                command = ["python", bot_directory]
                process = subprocess.Popen(
                    command,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    text=True
                )
                log_file_path = f"/tmp/{bot['name']}_log.txt"
                db.dc_bots.update_one({"name": bot["name"]}, {
                    "$set": {
                        "pid": process.pid,
                        "log_file": log_file_path
                    }
                })
                threading.Thread(target=save_logs_to_file, args=(process, log_file_path)).start()

                bot_config[bot["name"]] = {"status": "on", "pid": process.pid}
                save_bot_config(bot_config)

                add_log(f"{session['username']} turned on {bot['name']}")
            except Exception as e:
                add_log(f"Error starting bot {bot['name']}: {str(e)}")

    return redirect(url_for("dc_bots"))


@app.route("/turn_off_all_bots", methods=["POST"])
def turn_off_all_bots():
    if "username" not in session or "discord_bots" not in session["permissions"]:
        return redirect(url_for("login"))

    bots = list(db.dc_bots.find())
    for bot in bots:
        if "pid" in bot:
            try:
                pid = bot["pid"]
                if psutil.pid_exists(pid):
                    os.kill(pid, signal.SIGTERM)
                    db.dc_bots.update_one({"name": bot["name"]}, {"$unset": {"pid": ""}})

                    bot_config[bot["name"]] = {"status": "off"}
                    save_bot_config(bot_config)

                    add_log(f"{session['username']} turned off {bot['name']}")
                else:
                    add_log(f"Process for bot {bot['name']} does not exist")
                    db.dc_bots.update_one({"name": bot["name"]}, {"$unset": {"pid": ""}})
            except Exception as e:
                add_log(f"Error stopping bot {bot['name']}: {str(e)}")

    return redirect(url_for("dc_bots"))


@app.route("/bot_logs/<bot_name>", methods=["GET"])
def bot_logs(bot_name):
    if "username" not in session or "discord_bots" not in session["permissions"]:
        return redirect(url_for("login"))

    bot = db.dc_bots.find_one({"name": bot_name})
    if bot and "log_file" in bot:
        log_file_path = bot["log_file"]

        try:
            with open(log_file_path, "r") as log_file:
                logs = log_file.readlines()
        except FileNotFoundError:
            logs = ["Keine Logs gefunden."]

        return render_template("bot_logs.html", bot_name=bot_name, logs=logs)
    else:
        return f"Logs für Bot {bot_name} nicht gefunden", 404


def start_bots_on_server_start():
    for bot_name, config in bot_config.items():
        if config["status"] == "on":
            bot = db.dc_bots.find_one({"name": bot_name})
            if bot:
                if "pid" in bot and psutil.pid_exists(bot["pid"]):
                    add_log(f"Bot {bot_name} is already running with PID {bot['pid']}")
                    continue

                bot_directory = bot["directory"]
                try:
                    command = ["python", bot_directory]
                    process = subprocess.Popen(
                        command,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        text=True
                    )
                    log_file_path = f"/tmp/{bot_name}_log.txt"
                    db.dc_bots.update_one({"name": bot_name}, {
                        "$set": {
                            "pid": process.pid,
                            "log_file": log_file_path
                        }
                    })
                    threading.Thread(target=save_logs_to_file, args=(process, log_file_path)).start()

                    bot_config[bot_name] = {"status": "on", "pid": process.pid}
                    save_bot_config(bot_config)

                    add_log(f"Bot {bot_name} automatically started on server start")
                except Exception as e:
                    add_log(f"Error starting bot {bot_name} on server start: {str(e)}")


start_bots_on_server_start()


@app.route('/logout')
def logout():
    add_log(f"{session['username']} hat sich ausgeloggt")
    session.pop("username", None)
    session.pop("permissions", None)
    session.pop("user_id", None)
    return redirect(url_for("login"))


@app.route('/toggle-theme', methods=['POST'])
@csrf.exempt
def toggle_theme():
    if "username" not in session:
        return "Unauthorized", 401

    user = accounts_collection.find_one({"_id": ObjectId(session["user_id"])})
    if not user:
        return "User not found", 404

    current_theme = user.get("Style", "lightmode")
    new_theme = "darkmode" if current_theme == "lightmode" else "lightmode"

    accounts_collection.update_one({"_id": ObjectId(session["user_id"])}, {"$set": {"Style": new_theme}})
    session["theme"] = new_theme

    return "", 204


@app.route("/ip_checker", methods=["GET", "POST"])
def ip_checker():
    if "username" not in session or "ip_checker" not in session["permissions"]:
        return redirect(url_for("login"))

    import ipaddress

    ip_data = None
    error = None
    similar_ips = []
    is_cheater_network = False
    similar_cheater_ips = []

    if request.method == "POST":
        ip_address = request.form.get("ip_address")
        is_cheater = request.form.get("is_cheater") == "yes"
        steam_id = request.form.get("steam_id") if is_cheater else None

        if not ip_address:
            error = "Bitte eine IP-Adresse eingeben."
        elif is_cheater and not steam_id:
            error = "SteamID ist erforderlich, wenn die IP als Cheater markiert wird."
        else:
            try:
                api_key = "015ada9472664329affb2f26d7036d47"
                response = requests.get(f"https://vpnapi.io/api/{ip_address}?key={api_key}")
                if response.status_code == 200:
                    ip_data = response.json()

                    if "security" not in ip_data or not isinstance(ip_data["security"], dict):
                        ip_data["security"] = {}
                    if "location" not in ip_data or not isinstance(ip_data["location"], dict):
                        ip_data["location"] = {}
                    if "network" not in ip_data or not isinstance(ip_data["network"], dict):
                        ip_data["network"] = {}

                    checked_network = None
                    if "network" in ip_data and "network" in ip_data["network"]:
                        cidr = str(ip_data["network"]["network"]).replace(" ", "")
                        try:
                            checked_network = ipaddress.ip_network(cidr, strict=False)
                        except Exception:
                            checked_network = None

                    cheater_ips = list(db.ip_infos.find({"is_cheater": True}))
                    for cheater in cheater_ips:
                        cheater_data = cheater.get("data", {})
                        cheater_network = None
                        if "network" in cheater_data and "network" in cheater_data["network"]:
                            cheater_cidr = str(cheater_data["network"]["network"]).replace(" ", "")
                            try:
                                cheater_network = ipaddress.ip_network(cheater_cidr, strict=False)
                            except Exception:
                                cheater_network = None
                        cheater_ip = cheater.get("ip")
                        if cheater_network and checked_network:
                            if checked_network.overlaps(cheater_network):
                                is_cheater_network = True
                                similar_cheater_ips.append(cheater)
                        if cheater_ip and checked_network:
                            try:
                                if ipaddress.ip_address(cheater_ip) in checked_network:
                                    is_cheater_network = True
                                    similar_cheater_ips.append(cheater)
                            except Exception:
                                pass

                    similar_ips = list(db.ip_infos.find({
                        "$or": [
                            {"ip": ip_address},
                            {"data.location.city": ip_data["location"].get("city")},
                            {"data.network.autonomous_system_number": ip_data["network"].get(
                                "autonomous_system_number")}
                        ]
                    }))

                    db.ip_infos.insert_one({
                        "ip": ip_address,
                        "is_cheater": is_cheater,
                        "steam_id": steam_id,
                        "data": ip_data,
                        "added_by": session["username"],
                        "timestamp": datetime.now()
                    })
                else:
                    error = "Fehler beim Abrufen der IP-Daten."
            except Exception as e:
                error = f"Fehler: {str(e)}"

    return render_template(
        "ip_checker.html",
        ip_data=ip_data,
        error=error,
        similar_ips=similar_ips,
        is_cheater_network=is_cheater_network,
        similar_cheater_ips=similar_cheater_ips
    )


@app.route('/add-changelog', methods=['GET', 'POST'])
def add_changelog():
    if 'changelogs' not in session.get('permissions', []):
        return "Unauthorized", 403

    if request.method == 'POST':
        changelog_text = request.form.get('changelog')
        if changelog_text:
            changelogs_collection.insert_one({
                'text': changelog_text,
                'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            })
        return redirect(url_for('dashboard'))

    return render_template('add_changelog.html')


@app.route("/appointments", methods=["GET", "POST"])
def appointments():
    if "username" not in session or "appointments" not in session["permissions"]:
        return redirect(url_for("login"))

    if request.method == "POST":
        title = request.form["title"]
        start_date = request.form["start_date"]
        end_date = request.form["end_date"]
        description = request.form.get("description", "")

        appointments_collection.insert_one({
            "username": session["username"],
            "title": title,
            "start_date": start_date,
            "end_date": end_date,
            "description": description,
            "created_at": datetime.now()
        })

        add_log(f"{session['username']} hat einen neuen Termin erstellt: {title}")

        return redirect(url_for("appointments"))

    user_appointments = list(appointments_collection.find({"username": session["username"]}).sort("start_date", 1))

    return render_template("appointments.html", appointments=user_appointments)


@app.route("/delete_appointment/<appointment_id>", methods=["POST"])
def delete_appointment(appointment_id):
    if "username" not in session or "appointments" not in session["permissions"]:
        return redirect(url_for("login"))

    appointment = appointments_collection.find_one({"_id": ObjectId(appointment_id), "username": session["username"]})
    if appointment:
        appointments_collection.delete_one({"_id": ObjectId(appointment_id)})
        add_log(f"{session['username']} hat den Termin '{appointment['title']}' gelöscht")

    return redirect(url_for("appointments"))


@app.route("/edit_appointment/<appointment_id>", methods=["GET", "POST"])
def edit_appointment(appointment_id):
    if "username" not in session or "appointments" not in session["permissions"]:
        return redirect(url_for("login"))

    appointment = appointments_collection.find_one({"_id": ObjectId(appointment_id), "username": session["username"]})
    if not appointment:
        return "Termin nicht gefunden", 404

    if request.method == "POST":
        title = request.form["title"]
        start_date = request.form["start_date"]
        end_date = request.form["end_date"]
        description = request.form.get("description", "")

        appointments_collection.update_one(
            {"_id": ObjectId(appointment_id)},
            {"$set": {
                "title": title,
                "start_date": start_date,
                "end_date": end_date,
                "description": description
            }}
        )

        add_log(f"{session['username']} hat den Termin '{title}' bearbeitet")

        return redirect(url_for("appointments"))

    return render_template("edit_appointment.html", appointment=appointment)


@app.route("/pcmachwas", methods=["GET", "POST"])
def pcmachwas():
    return render_template("stinker.html")


@app.route("/settings", methods=["GET"])
def settings():
    if "username" not in session:
        return redirect(url_for("login"))

    return redirect(url_for("edit_user", user_id=session["user_id"]))


@app.route("/ets2_map_cracked")
def ets2_map_cracked():
    scs_dir = "/var/www/admin_website/static/ets2_map_cracked"
    if not os.path.isdir(scs_dir):
        return f"Verzeichnis nicht gefunden oder keine Rechte: {scs_dir}", 404
    try:
        files = [
            f for f in os.listdir(scs_dir)
            if f.endswith(".scs") and os.path.isfile(os.path.join(scs_dir, f))
        ]
    except Exception as e:
        return f"Fehler beim Lesen des Verzeichnisses: {e}", 500
    if not files:
        return "Keine .scs Dateien gefunden.", 404
    links = "".join(
        f'<a href="/download_ets2_map_cracked/{fname}" download id="dl_{i}" style="display:none"></a>'
        for i, fname in enumerate(files)
    )
    js = f"""
    <script>
    const files = {files!r};
    let idx = 0;
    function downloadNext() {{
        if(idx >= files.length) return;
        document.getElementById('dl_' + idx).click();
        idx++;
        setTimeout(downloadNext, 1000);
    }}
    window.onload = downloadNext;
    </script>
    """
    return f"<h2>ETS2 Maps Cracked Downloads</h2>{links}{js}<p>Downloads werden automatisch gestartet...</p>"


@app.route("/download_ets2_map_cracked/<filename>")
def download_ets2_map_cracked(filename):
    scs_dir = "/var/www/admin_website/static/ets2_map_cracked"
    file_path = os.path.join(scs_dir, filename)
    if os.path.isfile(file_path) and filename.endswith(".scs"):
        return send_file(file_path, as_attachment=True)
    return "Datei nicht gefunden.", 404


# wallpaper
@app.route("/wallpaper")
def wallpaper():
    return render_template("wallpaper.html")


# NEUE PROXY-ROUTE für die SCP:SL API
@app.route('/api/scpsl_proxy')
def scpsl_proxy():
    try:
        response = requests.get(SCP_SL_API_URL, timeout=5)
        response.raise_for_status()
        return jsonify(response.json())
    except requests.exceptions.Timeout:
        return jsonify({"error": "Request timed out"}), 504
    except requests.exceptions.ConnectionError:
        return jsonify({"error": "Connection error"}), 503
    except requests.exceptions.RequestException as e:
        return jsonify({"error": f"Failed to fetch data: {e}"}), 500


def fetch_and_store_player_count():
    with app.app_context():
        try:
            response = requests.get(SCP_SL_API_URL, timeout=5)
            response.raise_for_status()
            data = response.json()
            players_str_full = data.get('players', '0/0')
            current_players = 0
            if '/' in players_str_full:
                try:
                    current_players = int(players_str_full.split('/')[0])
                except ValueError:
                    current_players = 0
            else:
                try:
                    current_players = int(players_str_full)
                except ValueError:
                    current_players = 0
            scpsl_playerdata_collection.insert_one({
                "timestamp": datetime.utcnow(),
                "players": current_players
            })
            scpsl_playerdata_collection.create_index("timestamp")
        except requests.exceptions.RequestException as e:
            print(f"Error fetching player count: {e}")
        except Exception as e:
            print(f"Unexpected error fetching/storing to MongoDB: {e}")


def background_task():
    while True:
        fetch_and_store_player_count()
        time.sleep(60)


threading.Thread(target=background_task, daemon=True).start()


@app.route('/api/player_history')
def player_history_api():
    graph_data = []
    one_day_ago = datetime.utcnow() - timedelta(hours=24)
    history_cursor = scpsl_playerdata_collection.find(
        {"timestamp": {"$gte": one_day_ago}},
        {"_id": 0, "timestamp": 1, "players": 1}
    ).sort("timestamp", 1)

    zero_start = None
    for doc in history_cursor:
        t = doc["timestamp"]
        p = doc["players"]
        if p == 0:
            if zero_start is None:
                zero_start = t
            continue
        else:
            if zero_start is not None:
                duration = (t - zero_start).total_seconds()
                if duration > 20:
                    graph_data.append({
                        "time": zero_start.isoformat() + "Z",
                        "players": 0
                    })
                    graph_data.append({
                        "time": t.isoformat() + "Z",
                        "players": p
                    })
                zero_start = None
            graph_data.append({
                "time": t.isoformat() + "Z",
                "players": p
            })
    if zero_start is not None:
        last_time = datetime.utcnow()
        duration = (last_time - zero_start).total_seconds()
        if duration > 20:
            graph_data.append({
                "time": zero_start.isoformat() + "Z",
                "players": 0
            })
            graph_data.append({
                "time": last_time.isoformat() + "Z",
                "players": 0
            })
    return jsonify(graph_data)


@app.route('/player_history_graph')
def player_history_graph():
    html = """
    <html>
    <head><title>SCP:SL Player Count (Letzte 24h)</title></head>
    <body style="background:#1a1a1a;">
    <img id="player_graph" src="/player_history_graph_img" style="width:90vw;max-width:1200px;border:2px solid #444;">
    <script>
    setInterval(function() {
        var img = document.getElementById('player_graph');
        img.src = '/player_history_graph_img?ts=' + new Date().getTime();
    }, 10000);
    </script>
    </body>
    </html>
    """
    return html


@app.route('/player_history_graph_img')
def player_history_graph_img():
    one_day_ago = datetime.utcnow() - timedelta(hours=24)
    history_cursor = scpsl_playerdata_collection.find(
        {"timestamp": {"$gte": one_day_ago}},
        {"_id": 0, "timestamp": 1, "players": 1}
    ).sort("timestamp", 1)
    zero_start = None
    filtered_times = []
    filtered_players = []
    for doc in history_cursor:
        t = doc["timestamp"]
        p = doc["players"]
        if p == 0:
            if zero_start is None:
                zero_start = t
            continue
        else:
            if zero_start is not None:
                duration = (t - zero_start).total_seconds()
                if duration > 20:
                    filtered_times.append(zero_start)
                    filtered_players.append(0)
                    filtered_times.append(t)
                    filtered_players.append(p)
                zero_start = None
            filtered_times.append(t)
            filtered_players.append(p)
    if zero_start is not None:
        last_time = datetime.utcnow()
        duration = (last_time - zero_start).total_seconds()
        if duration > 20:
            filtered_times.append(zero_start)
            filtered_players.append(0)
            filtered_times.append(last_time)
            filtered_players.append(0)
    fig, ax = plt.subplots(figsize=(10, 6))
    if not filtered_times:
        ax.text(0.5, 0.5, 'No player data available for the last 24 hours.',
                horizontalalignment='center', verticalalignment='center', transform=ax.transAxes, fontsize=12, color='red')
        ax.set_ylim(bottom=0, top=36)
    else:
        ax.plot(filtered_times, filtered_players, color='lime', linewidth=2)
        max_players_in_history = max(filtered_players) if filtered_players else 0
        if max_players_in_history > 0:
            ax.set_ylim(bottom=0, top=max_players_in_history + 5)
        else:
            ax.set_ylim(bottom=0, top=36)
    ax.set_title('SCP:SL Player Count (Letzte 24h)', color='white')
    ax.set_xlabel('Zeit', color='white')
    ax.set_ylabel('Spieleranzahl', color='white')
    ax.set_facecolor('#1a1a1a')
    fig.patch.set_facecolor('#1a1a1a')
    ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
    ax.xaxis.set_major_locator(mdates.HourLocator(interval=3))
    fig.autofmt_xdate()
    ax.tick_params(axis='x', colors='white')
    ax.tick_params(axis='y', colors='white')
    ax.yaxis.set_major_locator(plt.MaxNLocator(integer=True))
    ax.grid(True, linestyle='--', alpha=0.5, color='#444444')
    ax.spines['bottom'].set_color('white')
    ax.spines['top'].set_color('white')
    ax.spines['left'].set_color('white')
    ax.spines['right'].set_color('white')
    buffer = BytesIO()
    plt.savefig(buffer, format='png', bbox_inches='tight', dpi=100)
    buffer.seek(0)
    plt.close(fig)
    return send_file(buffer, mimetype='image/png')


# Voice-Channel Übersicht
@app.route("/vcs")
def vcs_overview():
    # Beispiel: Channels können dynamisch sein, hier statisch
    channels = [
        {"id": "talk1", "name": "Teamtalk"},
        {"id": "talk2", "name": "Privat"},
        {"id": "talk3", "name": "Privat 2"}
    ]
    return render_template("schisscord.html", channels=channels)


# Voice-Channel Join
@app.route("/join/<channel_id>")
def join_vc(channel_id):
    if "username" not in session:
        return redirect(url_for("login"))
    return render_template("vc_room.html", channel_id=channel_id, username=session["username"])


# SocketIO: Nutzer tritt Channel bei
@socketio.on("join_channel")
def handle_join_channel(data):
    channel = data.get("channel")
    username = data.get("username")
    join_room(channel)
    emit("user_joined", {"username": username}, room=channel)


@socketio.on("leave_channel")
def handle_leave_channel(data):
    channel = data.get("channel")
    username = data.get("username")
    leave_room(channel)
    emit("user_left", {"username": username}, room=channel)


@socketio.on("signal")
def handle_signal(data):
    channel = data.get("channel")
    signal_data = data.get("signal")
    sender = data.get("username")
    emit("signal", {"signal": signal_data, "username": sender}, room=channel, include_self=False)


MONGO_URI = "mongodb+srv://MrSniff:5169Galaxy@chainsaw.d0shx.mongodb.net/"
client = MongoClient(MONGO_URI)
ETS2_DB_NAME = "ets2_license_db"
ETS2_COLLECTION_NAME = "licenses"
ets2_db = client[ETS2_DB_NAME]
ets2_licenses = ets2_db[ETS2_COLLECTION_NAME]
print(f"[DEBUG] Using DB: {ETS2_DB_NAME}, Collection: {ETS2_COLLECTION_NAME}")

# --- DLC Folder ---
DLC_FOLDER = "/var/www/admin_website/static/ets2_dlcs"

# --- License Check Endpoint ---
@app.route("/checklicense", methods=["GET"])
def check_license():
    key = request.args.get("key")
    user = request.args.get("user")
    print(f"[DEBUG] /checklicense called with key='{key}' user='{user}'")

    if not key or not user:
        print("[DEBUG] Missing key or user")
        return "invalid", 400

    entry = ets2_licenses.find_one({"license_key": key})
    print(f"[DEBUG] MongoDB entry found: {entry}")

    if entry:
        if not entry.get("windows_user"):
            ets2_licenses.update_one({"license_key": key}, {"$set": {"windows_user": user}})
            print(f"[DEBUG] Key '{key}' registered to user '{user}'")
            return "valid"
        elif entry["windows_user"] == user:
            print(f"[DEBUG] Key '{key}' valid for user '{user}'")
            return "valid"
        else:
            print(f"[DEBUG] Key '{key}' already used by another user '{entry['windows_user']}'")
            return "invalid_user"
    else:
        print(f"[DEBUG] Key '{key}' not found in database")
        return "invalid"

# --- DLC Download Endpoint ---
@app.route("/download", methods=["GET"])
def download_dlc():
    key = request.headers.get("License-Key")
    user = request.headers.get("Windows-User")
    dlc_type = request.args.get("type", "all")  # "all_map_dlcs" oder "all_dlcs"
    print(f"[DEBUG] /download called with key='{key}', user='{user}', type='{dlc_type}'")

    if not key or not user:
        print("[DEBUG] Missing headers")
        return "missing_headers", 400

    entry = ets2_licenses.find_one({"license_key": key})
    print(f"[DEBUG] MongoDB entry for download: {entry}")

    if not entry or entry.get("windows_user") != user:
        print(f"[DEBUG] Unauthorized access for key '{key}' user '{user}'")
        return "unauthorized", 403

    # Bestimme die Datei basierend auf DLC Typ
    if dlc_type == "all_map_dlcs":
        file_name = "all_map_dlcs.zip"
    else:
        file_name = "all_dlcs.zip"

    file_path = os.path.join(DLC_FOLDER, file_name)
    if not os.path.exists(file_path):
        print(f"[DEBUG] File not found at {file_path}")
        return "not_found", 404

    print(f"[DEBUG] Sending file {file_path} to user '{user}'")
    return send_file(file_path, as_attachment=True)


if __name__ == "__main__":
    print("[DEBUG] Starting ETS2 DLC Server")
    print(f"[DEBUG] Using DB: {ETS2_DB_NAME}, Collection: {ETS2_COLLECTION_NAME}")
    socketio.run(app, debug=True, host='0.0.0.0', port=5000)
