#!/usr/bin/env python3
"""
Minecraft whitelist web app — lightweight self-service whitelisting.
Sethian Dark theme. minecraft.sethpc.xyz
"""

import html
import json
import os
import re
import socket
import struct
import time
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import parse_qs

PORT = 8099
INVITE_KEY = os.environ.get("INVITE_KEY", "REDACTED_INVITE_KEY")

SERVERS = [
    {"name": "Paper AI", "host": "127.0.0.1", "rcon_port": 25577, "rcon_pass": "REDACTED_RCON", "show": True, "address": "sethpc.xyz:25567", "desc": "Full AI stack — pray to God, sudo commands, divine interventions"},
    {"name": "Shrink World", "host": "127.0.0.1", "rcon_port": 25576, "rcon_pass": "REDACTED_RCON", "show": True, "address": "sethpc.xyz:25566", "desc": "Survival challenge — border shrinks on death, 5x creepers, pray for help"},
    {"name": "Vanilla", "host": "127.0.0.1", "rcon_port": 25575, "rcon_pass": "REDACTED_RCON", "show": False, "address": None, "desc": None},
]

WHITELIST_LOG = "/var/log/mc_whitelist.log"

LOGO_URL = "https://git.sethpc.xyz/Seth/Mortdecai/raw/branch/master/branding/mortdec_ai_rajdhani.png"


def rcon_command(cmd, host, port, password):
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(5)
        s.connect((host, port))
        def send_packet(req_id, ptype, payload):
            data = struct.pack("<ii", req_id, ptype) + payload.encode("utf-8") + b"\x00\x00"
            s.sendall(struct.pack("<i", len(data)) + data)
        def recv_packet():
            raw = s.recv(4)
            if len(raw) < 4: return None
            length = struct.unpack("<i", raw)[0]
            data = s.recv(length)
            return data[8:-2].decode("utf-8", errors="replace")
        send_packet(1, 3, password)
        time.sleep(0.1)
        recv_packet()
        send_packet(2, 2, cmd)
        time.sleep(0.2)
        result = recv_packet()
        s.close()
        return result or ""
    except Exception as e:
        return f"ERROR: {e}"


def lookup_mojang_uuid(username):
    """Look up a player's real Mojang UUID from the API."""
    try:
        import urllib.request
        url = f"https://api.mojang.com/users/profiles/minecraft/{username}"
        req = urllib.request.Request(url, headers={"User-Agent": "MortdecaiWhitelist/1.0"})
        resp = urllib.request.urlopen(req, timeout=5)
        data = json.loads(resp.read())
        raw = data.get("id", "")
        if len(raw) == 32:
            # Format as UUID with dashes
            return f"{raw[:8]}-{raw[8:12]}-{raw[12:16]}-{raw[16:20]}-{raw[20:]}"
        return raw
    except Exception:
        return None


def whitelist_player(username):
    """Whitelist player on all servers, fixing UUIDs to real Mojang UUIDs."""
    results = {}

    # Get real UUID from Mojang
    uuid = lookup_mojang_uuid(username)

    for srv in SERVERS:
        # RCON whitelist add (creates entry, possibly with wrong offline UUID)
        result = rcon_command(f"whitelist add {username}", srv["host"], srv["rcon_port"], srv["rcon_pass"])
        results[srv["name"]] = result.strip()

    # Fix UUIDs in whitelist.json files directly
    if uuid:
        WHITELIST_FILES = [
            "/opt/paper-ai-25567/whitelist.json",
            "/opt/mcsmanager/daemon/data/InstanceData/shrinkborder1234567890abcdef12345/whitelist.json",
            "/opt/mcsmanager/daemon/data/InstanceData/d39f55861cb34204a92a18a9e1c78ca6/whitelist.json",
        ]
        for wl_path in WHITELIST_FILES:
            try:
                with open(wl_path) as f:
                    wl = json.load(f)
                changed = False
                for entry in wl:
                    if entry["name"].lower() == username.lower() and entry["uuid"] != uuid:
                        entry["uuid"] = uuid
                        entry["name"] = username
                        changed = True
                if changed:
                    with open(wl_path, "w") as f:
                        json.dump(wl, f, indent=2)
            except Exception:
                pass

        # Reload whitelists
        for srv in SERVERS:
            try:
                rcon_command("whitelist reload", srv["host"], srv["rcon_port"], srv["rcon_pass"])
            except Exception:
                pass

    return results


def is_valid_username(name):
    return bool(re.match(r'^[a-zA-Z0-9_]{3,16}$', name))


PAGE = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mortdecai — Minecraft AI</title>
<style>
  * {{ margin: 0; padding: 0; box-sizing: border-box; }}
  body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
         background: #1a1a1a; color: #e0e0e0; min-height: 100vh; }}
  a {{ color: #D35400; text-decoration: none; }}
  a:hover {{ color: #e65c00; }}

  .header {{ background: #000; padding: 1rem; text-align: center; border-bottom: 2px solid #D35400; }}
  .header img {{ height: 40px; vertical-align: middle; border: none; }}
  .header span {{ font-size: 1.2rem; font-weight: 700; color: #D35400; margin-left: 0.5rem; vertical-align: middle; display: none; }}

  .container {{ max-width: 600px; margin: 0 auto; padding: 1.5rem; }}

  .hero {{ text-align: center; margin: 2rem 0 1.5rem; }}
  .hero h1 {{ font-size: 1.6rem; color: #D35400; margin-bottom: 0.5rem; }}
  .hero p {{ color: #ccc; font-size: 0.95rem; line-height: 1.5; }}

  .mission {{ background: #252525; border: 1px solid #333; border-radius: 8px;
              padding: 1.2rem; margin: 1.5rem 0; }}
  .mission h2 {{ color: #D35400; font-size: 1rem; margin-bottom: 0.5rem; }}
  .mission p {{ color: #ccc; font-size: 0.85rem; line-height: 1.5; margin-bottom: 0.5rem; }}
  .mission ul {{ color: #ccc; font-size: 0.85rem; line-height: 1.6; padding-left: 1.2rem; }}
  .mission code {{ background: #333; padding: 0.15rem 0.4rem; border-radius: 3px; color: #D35400; font-size: 0.85rem; }}

  .card {{ background: #252525; border: 1px solid #333; border-radius: 8px;
           padding: 1.5rem; margin: 1.5rem 0; }}
  .card h2 {{ color: #D35400; font-size: 1.1rem; margin-bottom: 1rem; text-align: center; }}

  label {{ display: block; font-size: 0.85rem; color: #aaa; margin-bottom: 0.3rem; margin-top: 1rem; }}
  input {{ width: 100%; padding: 0.6rem; border-radius: 6px; border: 1px solid #444;
          background: #2a2a2a; color: #fff; font-size: 1rem; }}
  input:focus {{ outline: none; border-color: #D35400; box-shadow: 0 0 5px rgba(211,84,0,0.5); }}

  button {{ width: 100%; padding: 0.7rem; border-radius: 6px; border: none;
           background: #D35400; color: #fff; font-size: 1rem; font-weight: 600;
           cursor: pointer; margin-top: 1.5rem; }}
  button:hover {{ background: #e65c00; }}

  .error {{ color: #ff6b6b; font-size: 0.85rem; margin-top: 0.5rem; text-align: center; }}

  .success {{ background: #1a3a1a; border: 1px solid #2d7a2d; border-radius: 8px;
             padding: 1.2rem; margin-top: 1.5rem; }}
  .success h2 {{ color: #4caf50; font-size: 1.1rem; margin-bottom: 0.5rem; }}

  .server {{ background: #252525; border: 1px solid #333; border-left: 3px solid #D35400;
            border-radius: 6px; padding: 0.8rem; margin-top: 0.8rem; }}
  .server .name {{ color: #D35400; font-weight: 600; font-size: 0.95rem; }}
  .server .addr {{ font-family: monospace; color: #fff; font-size: 1.1rem; margin: 0.3rem 0;
                   background: #1a1a1a; padding: 0.3rem 0.6rem; border-radius: 4px; display: inline-block; }}
  .server .desc {{ color: #999; font-size: 0.8rem; margin-top: 0.3rem; }}

  .commands {{ background: #252525; border: 1px solid #333; border-radius: 8px;
               padding: 1rem; margin-top: 1rem; }}
  .commands h3 {{ color: #D35400; font-size: 0.9rem; margin-bottom: 0.5rem; }}
  .cmd {{ font-family: monospace; background: #1a1a1a; padding: 0.2rem 0.5rem;
          border-radius: 3px; color: #D35400; }}
  .commands p {{ font-size: 0.8rem; color: #bbb; margin-bottom: 0.3rem; }}

  .footer {{ text-align: center; color: #555; font-size: 0.75rem; margin: 2rem 0 1rem; }}
</style>
</head>
<body>

<div class="header">
  <img src="{logo}" alt="logo" class="noborder">
  <span>mortdec.ai</span>
</div>

<div class="container">
  <div class="hero">
    <h1>Mortdecai</h1>
    <p>An AI runs on this server that listens to in-game chat and does things in the world based on what you say.</p>
  </div>

  <div class="mission">
    <h2>What Is This?</h2>
    <p>There's an AI character playing <strong>God</strong> on the server. It runs on local hardware — no cloud, no OpenAI — using a small open-source model we're actively training.</p>
    <p>Every interaction you have gets logged as training data to improve the model. The more you play, the smarter it gets.</p>
    <h2 style="margin-top: 0.8rem;">What Can You Do?</h2>
    <ul>
      <li><code>pray &lt;message&gt;</code> — Talk to God. Pray for items, smite your enemies, or say something offensive and get punished.</li>
      <li><code>sudo &lt;request&gt;</code> — Natural language commands. "sudo give me a diamond sword" just works.</li>
      <li><code>bug_log &lt;description&gt;</code> — Report when something goes wrong. Helps us fix the AI.</li>
    </ul>
    <h2 style="margin-top: 0.8rem;">What We Need</h2>
    <p>Try to break it. Ask for weird things. Confuse it. Phrase things in ways nobody would expect. Every interaction — good or bad — makes the model better.</p>
  </div>

  {content}

  <div class="footer">
    Java Edition 1.21.x &middot; Whitelisted &middot; Hosted on a homelab
  </div>
</div>

</body>
</html>"""

FORM = """
  <div class="card">
    <h2>Join the Server</h2>
    <form method="POST">
      <label>Minecraft Java Edition Username</label>
      <input name="username" placeholder="Your username" required autofocus>
      <label>Invite Key</label>
      <input name="key" type="password" placeholder="Paste the key you were given" required>
      <button type="submit">Get Whitelisted</button>
    </form>
    {error}
  </div>
"""

def success_content(username, results):
    servers = ""
    for srv in SERVERS:
        if not srv["show"]:
            continue
        servers += f"""
        <div class="server">
          <div class="name">{srv['name']}</div>
          <div class="addr">{srv['address']}</div>
          <div class="desc">{srv['desc']}</div>
        </div>"""

    return f"""
    <div class="success">
      <h2>Welcome, {html.escape(username)}!</h2>
      <p style="color:#ccc; font-size:0.9rem;">You're whitelisted. Add these servers in Minecraft:</p>
      {servers}
    </div>

    <div class="commands">
      <h3>Quick Start</h3>
      <p><span class="cmd">pray lord give me tools</span> — ask God for help</p>
      <p><span class="cmd">sudo give me a diamond sword</span> — direct command translation</p>
      <p><span class="cmd">sudo set time to day</span> — world commands work too</p>
      <p><span class="cmd">bug_log it gave me the wrong item</span> — report issues</p>
    </div>"""


class Handler(BaseHTTPRequestHandler):
    def log_message(self, fmt, *args):
        pass

    def do_GET(self):
        content = FORM.format(error="")
        self.send_response(200)
        self.send_header("Content-Type", "text/html")
        self.end_headers()
        self.wfile.write(PAGE.format(content=content, logo=LOGO_URL).encode())

    def do_POST(self):
        length = int(self.headers.get("Content-Length", 0))
        body = self.rfile.read(length).decode()
        params = parse_qs(body)
        username = params.get("username", [""])[0].strip()
        key = params.get("key", [""])[0].strip()

        if key != INVITE_KEY:
            content = FORM.format(error='<p class="error">Invalid invite key.</p>')
            self.send_response(200)
            self.send_header("Content-Type", "text/html")
            self.end_headers()
            self.wfile.write(PAGE.format(content=content, logo=LOGO_URL).encode())
            return

        if not is_valid_username(username):
            content = FORM.format(error='<p class="error">Invalid username. 3-16 characters, letters/numbers/underscore only.</p>')
            self.send_response(200)
            self.send_header("Content-Type", "text/html")
            self.end_headers()
            self.wfile.write(PAGE.format(content=content, logo=LOGO_URL).encode())
            return

        results = whitelist_player(username)
        try:
            with open(WHITELIST_LOG, "a") as f:
                f.write(json.dumps({"time": time.strftime("%Y-%m-%dT%H:%M:%SZ"), "username": username, "results": results}) + "\n")
        except:
            pass

        content = success_content(username, results)
        self.send_response(200)
        self.send_header("Content-Type", "text/html")
        self.end_headers()
        self.wfile.write(PAGE.format(content=content, logo=LOGO_URL).encode())


if __name__ == "__main__":
    print(f"Whitelist app running on port {PORT}")
    print(f"Invite key: {INVITE_KEY}")
    HTTPServer(("0.0.0.0", PORT), Handler).serve_forever()
