from flask import Flask, request, render_template_string, send_file, abort, redirect, url_for, Response
import os
import subprocess
import tempfile
from datetime import datetime
import shutil
import json
import uuid
from functools import wraps

app = Flask(__name__)

TOKEN_DB_PATH = "/opt/wgconfig/tokens.json"
SUBNET_DB_PATH = "/opt/wgconfig/subnets.json"
USED_LOG_PATH = "/opt/wgconfig/used_tokens.log"
PEERS_CONF_PATH = "/opt/wgconfig/wg0-peers.conf"
KEY_LOG_PATH = "/var/log/wg_keys_issued.log"
BACKUP_DIR = "/opt/wgconfig/backups"

SERVER_PUBLIC_KEY = "bsped9nemtP9fRfcVLoLL3JWTj7zHFYWJebAhgH7RFw="
SERVER_ENDPOINT = "24.249.187.133:51820"

ADMIN_USERNAME = "admin"
ADMIN_PASSWORD = "changeme"

def check_auth(username, password):
    return username == ADMIN_USERNAME and password == ADMIN_PASSWORD

def authenticate():
    return Response("Login Required", 401, {"WWW-Authenticate": "Basic realm=\"Login Required\""})

def requires_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth = request.authorization
        if not auth or not check_auth(auth.username, auth.password):
            return authenticate()
        return f(*args, **kwargs)
    return decorated

HTML_FORM = '''
<!doctype html>
<title>WireGuard Config Generator</title>
<h2>WireGuard Config Generator</h2>
<p>Welcome, your token is valid. Click below to generate your secure config.</p>
<form method="post">
    <button type="submit">Generate My WireGuard Config</button>
</form>
<p><a href="/instructions" target="_blank">How to use this page</a></p>
'''

HTML_INSTRUCTIONS = '''
<!doctype html>
<title>How to Use This Page</title>
<h2>📜 How to Use This Page: WireGuard Config Generator</h2>
<ol>
<li>Go to <code>https://wgsetup.glorytoyah.org/setup?token=YourToken</code> with your one-time token</li>
<li>Click "Generate Config" to download your WireGuard config</li>
<li>Import it into the WireGuard app (mobile or desktop)</li>
</ol>
'''

HTML_ADMIN_GUIDE = '''
<!doctype html>
<title>Admin Guide</title>
<h2>Admin Guide</h2>
<ul>
  <li>📦 <strong>Token Creation</strong>: Use /generate-and-redirect/&lt;GroupName&gt; to generate a token.</li>
  <li>🔐 <strong>Access Token Panel</strong>: Visit /admin/tokens to manage tokens.</li>
  <li>🗃 <strong>Used Tokens</strong>: View /admin/used for audit logs of used/revoked tokens.</li>
  <li>🧰 <strong>Subnet Setup</strong>: Add or modify group subnets via /admin/subnets.</li>
  <li>💾 <strong>Backup</strong>: Download backups from /admin/backup.</li>
  <li>♻️ <strong>Restore</strong>: Unzip a backup and move files into /opt/wgconfig.</li>
</ul>
<h3>Common Issues:</h3>
<ul>
  <li>🚫 <strong>Permission denied?</strong> Ensure correct file permissions (e.g., chown www-data).</li>
  <li>❌ <strong>WireGuard fails to reload?</strong> Check for duplicate IPs or malformed entries.</li>
</ul>
<h3>FAQs:</h3>
<ul>
  <li><strong>Can a token be reused?</strong> No. Tokens are removed after use.</li>
  <li><strong>Can I edit peer configs manually?</strong> Yes, but ensure unique IPs and public keys.</li>
</ul>
'''

@app.route("/admin/guide")
@requires_auth
def admin_guide():
    return render_template_string(HTML_ADMIN_GUIDE)

@app.route("/instructions")
def instructions():
    return render_template_string(HTML_INSTRUCTIONS)

@app.route("/generate-and-redirect/<group>")
def generate_and_redirect(group):
    tokens = load_tokens()
    subnets = load_subnets()
    if group not in subnets:
        return "Invalid group name", 400
    base = subnets[group]
    used = [data["ip"] for data in tokens.values() if data.get("group") == group and data.get("ip", "").startswith(base)]
    next_host = 2 + len(used)
    new_ip = f"{base}.{next_host}/24"
    while ip_in_use(new_ip):
        next_host += 1
        new_ip = f"{base}.{next_host}/24"
    token = str(uuid.uuid4())
    tokens[token] = {"ip": new_ip, "group": group}
    save_tokens(tokens)
    return redirect(url_for("setup", token=token))

@app.route("/setup", methods=["GET", "POST"])
def setup():
    token = request.args.get("token")
    tokens = load_tokens()
    if token not in tokens:
        return abort(403, description="Invalid or missing token.")
    assigned_ip = tokens[token]["ip"]
    if request.method == "POST":
        try:
            private_key = subprocess.check_output(["wg", "genkey"]).decode().strip()
            public_key = subprocess.check_output(["wg", "pubkey"], input=private_key.encode()).decode().strip()
        except Exception as e:
            return f"Key generation failed: {e}", 500
        config = f"""
[Interface]
PrivateKey = {private_key}
Address = {assigned_ip}

[Peer]
PublicKey = {SERVER_PUBLIC_KEY}
Endpoint = {SERVER_ENDPOINT}
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
"""
        temp_dir = tempfile.mkdtemp()
        config_path = os.path.join(temp_dir, f"client_{token}.conf")
        with open(config_path, "w") as f:
            f.write(config)
        with open(KEY_LOG_PATH, "a") as log:
            log.write(f"[{datetime.now()}] {token} -> {public_key} ({assigned_ip})\n")
        peer_entry = f"\n[Peer]\nPublicKey = {public_key}\nAllowedIPs = {assigned_ip.split('/')[0]}/32\n"
        with open(PEERS_CONF_PATH, "a") as peerconf:
            peerconf.write(peer_entry)
        try:
            subprocess.run(["sudo", "wg", "addconf", "wg0", PEERS_CONF_PATH], check=True, stderr=subprocess.PIPE)
        except subprocess.CalledProcessError as e:
            app.logger.error(f"Failed to update live WireGuard config: {e.stderr.decode()}")
        with open(USED_LOG_PATH, "a") as used:
            used.write(f"[USED] {token} - {tokens[token]}\n")
        tokens.pop(token)
        save_tokens(tokens)
        return send_file(config_path, as_attachment=True)
    return render_template_string(HTML_FORM)

@app.route("/admin/tokens")
@requires_auth
def admin_tokens():
    tokens = load_tokens()
    html = "<h2>Active Tokens</h2><table border='1'><tr><th>Token</th><th>IP</th><th>Group</th><th>Revoke</th></tr>"
    for t, v in tokens.items():
        html += f"<tr><td>{t}</td><td>{v['ip']}</td><td>{v['group']}</td><td><a href='/admin/revoke/{t}'>Revoke</a></td></tr>"
    html += "</table><form method='post' action='/admin/tokens/purge'><button type='submit'>Purge All</button></form>"
    html += "<p><a href='/admin/used'>View Used</a> | <a href='/admin/subnets'>Subnets</a> | <a href='/admin/backup'>Backup</a> | <a href='/admin/guide'>Admin Guide</a></p>"
    return html

@app.route("/admin/revoke/<token>")
@requires_auth
def revoke_token(token):
    tokens = load_tokens()
    if token in tokens:
        with open(USED_LOG_PATH, "a") as log:
            log.write(f"[REVOKED] {token} - {tokens[token]}\n")
        tokens.pop(token)
        save_tokens(tokens)
    return redirect(url_for("admin_tokens"))

@app.route("/admin/tokens/purge", methods=["POST"])
@requires_auth
def purge_tokens():
    open(TOKEN_DB_PATH, "w").write("{}")
    return redirect(url_for("admin_tokens"))

@app.route("/admin/subnets", methods=["GET", "POST"])
@requires_auth
def manage_subnets():
    subnets = load_subnets()
    if request.method == "POST":
        name = request.form.get("name")
        base = request.form.get("base")
        if name and base:
            subnets[name] = base
            save_subnets(subnets)
    html = "<h2>Subnets</h2><table border='1'><tr><th>Group</th><th>Subnet</th></tr>"
    for k, v in subnets.items():
        html += f"<tr><td>{k}</td><td>{v}</td></tr>"
    html += "</table><form method='post'>Group: <input name='name'> Base: <input name='base'> <button>Add</button></form>"
    html += "<p><a href='/admin/tokens'>Back to Tokens</a></p>"
    return html

@app.route("/admin/used")
@requires_auth
def view_used():
    if not os.path.exists(USED_LOG_PATH):
        return "<p>No used tokens logged.</p><p><a href='/admin/tokens'>Back</a></p>"
    with open(USED_LOG_PATH) as f:
        return f"<h2>Used/Revoked Tokens</h2><pre>{f.read()}</pre><p><a href='/admin/tokens'>Back</a></p>"

@app.route("/admin/backup")
@requires_auth
def backup_all():
    os.makedirs(BACKUP_DIR, exist_ok=True)
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    zip_path = os.path.join(BACKUP_DIR, f"backup_{timestamp}.zip")
    shutil.make_archive(zip_path.replace(".zip", ""), 'zip', "/opt/wgconfig")
    return send_file(zip_path, as_attachment=True)

def load_tokens():
    if os.path.exists(TOKEN_DB_PATH):
        with open(TOKEN_DB_PATH) as f:
            return json.load(f)
    return {}

def save_tokens(tokens):
    with open(TOKEN_DB_PATH, "w") as f:
        json.dump(tokens, f, indent=2)

def load_subnets():
    if os.path.exists(SUBNET_DB_PATH):
        with open(SUBNET_DB_PATH) as f:
            return json.load(f)
    return {}

def save_subnets(subnets):
    with open(SUBNET_DB_PATH, "w") as f:
        json.dump(subnets, f, indent=2)

def ip_in_use(ip):
    if not os.path.exists(PEERS_CONF_PATH):
        return False
    with open(PEERS_CONF_PATH) as f:
        return ip.split('/')[0] in f.read()

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)
