#!/usr/bin/env python3
"""
CCSite Fleet Dashboard — local aggregator running on MacBook.
Polls all 10 shop sites + BTCPay Server and displays real-time stats.
"""
import json
import time
import threading
import sqlite3
from datetime import datetime, timedelta
from collections import defaultdict
from flask import Flask, render_template, jsonify, request
import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# Import watchdog for health monitoring
try:
    from watchdog import get_state as get_watchdog_state, run_all_checks as watchdog_scan
    WATCHDOG_AVAILABLE = True
except ImportError:
    WATCHDOG_AVAILABLE = False

SITES = {
    1:  {"name": "VaultPass",           "ip": "10.30.20.210", "emoji": "🎮"},
    2:  {"name": "StreamPass",          "ip": "10.30.20.211", "emoji": "🎬"},
    3:  {"name": "KeyForge",            "ip": "10.30.20.212", "emoji": "💻"},
    4:  {"name": "SocialForge",         "ip": "10.30.20.213", "emoji": "🐦"},
    5:  {"name": "CardVault",           "ip": "10.30.20.214", "emoji": "💳"},
    6:  {"name": "CloudNexus",          "ip": "10.30.20.217", "emoji": "☁️"},
    7:  {"name": "LearnForge",          "ip": "10.30.20.218", "emoji": "📚"},
    8:  {"name": "PrivacyPass",         "ip": "10.30.20.219", "emoji": "🛡️"},
    9:  {"name": "CreatorForge",        "ip": "10.30.20.220", "emoji": "🎨"},
    10: {"name": "FoodVault",           "ip": "10.30.20.221", "emoji": "🍕"},
}

# BTCPay config
BTCPAY_URL = "https://10.30.20.140"
BTCPAY_KEY = "6026288e2e315984661c748baafd509e81a75f22"

# Per-site BTCPay store IDs
BTCPAY_STORES = {
    1: "AYZWEAt63TgY4QaxwNBawHASMKcimJabcZxSczRqZG81",
    2: "5pi94zmY3F26pTZJ99JfsFG9pb8atbz7fpALrPjS2BRq",
    3: "3mc58myPrkdN7hRFdsBwJxbVgBQQ3VvSYZygKQC3FiSG",
    4: "E1uZ5nmU6qSpMB8sna7fNdxZFiem9JmkxB9LdJkFN23B",
    5: "BcvaUvv8MM8if5dzqe5yLnNERG6EwjjmeMok54ATqjWf",
    6: "GvSsA9xaqymhswsiPo4APHMuT2sXCCtnBLgPWcHigxVx",
    7: "A2TXbpkpj88Wtj34dLS9yzjMgmcodysEaRhTjc3rFcUr",
    8: "9N8dTC99uXn3W3e2F9o8kVQ5ewijh87NevKmMATF1Jcd",
    9: "39BKRF3zQb6Gi8LiKo1US13bzJfhpCzkwoHP6pCyQFwD",
    10: "9vCjKrKbwgey4GH7DBAMa6FgkiApgHvd5khFpWSZzJL2",
}

DB_PATH = "/Users/drjones/ccsite-manager/dashboard.db"
BTC_ADDRESS = "35vTSjPCaqudcL5cj5ChpNDmqrpPFmEnss"
app = Flask(__name__, static_folder='/Users/drjones/ccsite-manager', static_url_path='')

def init_db():
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS snapshots (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        site_id INTEGER,
        timestamp REAL,
        users INTEGER,
        products INTEGER,
        orders INTEGER,
        revenue REAL,
        status TEXT,
        response_ms REAL
    )''')
    c.execute('''CREATE TABLE IF NOT EXISTS events (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        site_id INTEGER,
        timestamp REAL,
        event_type TEXT,
        detail TEXT
    )''')
    c.execute('''CREATE TABLE IF NOT EXISTS btcpay_snapshots (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        timestamp REAL,
        sync_pct REAL,
        blocks INTEGER,
        headers INTEGER,
        synced INTEGER,
        store_count INTEGER,
        stores_with_wallet INTEGER,
        total_invoices INTEGER
    )''')
    c.execute('''CREATE TABLE IF NOT EXISTS site_orders (
        site_id INTEGER, order_id INTEGER, product TEXT, amount REAL, qty INTEGER,
        status TEXT, email TEXT, code TEXT, date TEXT, ts REAL,
        PRIMARY KEY (site_id, order_id)
    )''')
    c.execute('''CREATE TABLE IF NOT EXISTS site_users (
        site_id INTEGER, user_id INTEGER, username TEXT, email TEXT, membership TEXT,
        balance REAL, orders INTEGER, joined TEXT, ts REAL,
        PRIMARY KEY (site_id, user_id)
    )''')
    c.execute('''CREATE TABLE IF NOT EXISTS site_txs (
        site_id INTEGER, tx_id INTEGER, user TEXT, amount REAL, method TEXT,
        type TEXT, date TEXT,
        PRIMARY KEY (site_id, tx_id)
    )''')
    c.execute('CREATE INDEX IF NOT EXISTS idx_orders_date ON site_orders(date DESC)')
    c.execute('CREATE INDEX IF NOT EXISTS idx_users_joined ON site_users(joined DESC)')
    c.execute('CREATE INDEX IF NOT EXISTS idx_snap_site_time ON snapshots(site_id, timestamp)')
    c.execute('CREATE INDEX IF NOT EXISTS idx_events_time ON events(timestamp DESC)')
    conn.commit()
    conn.close()

# In-memory current state (updated by poller)
state = {"sites": {}, "totals": {}, "events": [], "last_poll": None, "btcpay": {}}
state_lock = threading.Lock()

def poll_site(site_id, info):
    """Poll a single site for stats."""
    try:
        t0 = time.time()
        r = requests.get(f"http://{info['ip']}:5000/health", timeout=5)
        ms = (time.time() - t0) * 1000
        if r.status_code == 200:
            data = r.json()
            return {
                "site_id": site_id,
                "name": info["name"], "emoji": info["emoji"], "ip": info["ip"],
                "status": "online", "response_ms": round(ms),
                "users": data.get("users", 0),
                "products": data.get("products", 0),
                "site_name": data.get("site", "?"),
                "orders": data.get("orders_total", 0),
                "orders_today": data.get("orders_today", 0),
                "revenue": data.get("revenue_total", 0),
                "revenue_today": data.get("revenue_today", 0),
                "signups_today": data.get("signups_today", 0),
            }
    except Exception as e:
        return {
            "site_id": site_id, "name": info["name"], "emoji": info["emoji"], "ip": info["ip"],
            "status": "offline", "response_ms": 0, "users": 0, "products": 0, "site_name": "?",
            "orders": 0, "orders_today": 0, "revenue": 0, "revenue_today": 0, "signups_today": 0,
            "error": str(e)[:100],
        }

def poll_site_details(site_id, info):
    """Fetch /api/dashboard-data from a shop and upsert orders/users/txs.
    Returns per-site detail summary or None on failure."""
    try:
        r = requests.get(f"http://{info['ip']}:5000/api/dashboard-data", timeout=8)
        if r.status_code != 200:
            return None
        data = r.json()
        now = time.time()
        conn = sqlite3.connect(DB_PATH)
        c = conn.cursor()
        for o in data.get("orders", []):
            c.execute("""INSERT OR REPLACE INTO site_orders
                (site_id, order_id, product, amount, qty, status, email, code, date, ts)
                VALUES (?,?,?,?,?,?,?,?,?,?)""",
                (site_id, o.get("id"), o.get("product", "?"), o.get("amount", 0),
                 o.get("qty", 1), o.get("status", "?"), o.get("email", ""),
                 o.get("code", ""), o.get("date", ""), now))
        for u in data.get("users", []):
            c.execute("""INSERT OR REPLACE INTO site_users
                (site_id, user_id, username, email, membership, balance, orders, joined, ts)
                VALUES (?,?,?,?,?,?,?,?,?)""",
                (site_id, u.get("id"), u.get("username", "?"), u.get("email", ""),
                 u.get("membership", "free"), u.get("balance", 0), u.get("orders", 0),
                 u.get("joined", ""), now))
        for t in data.get("transactions", []):
            c.execute("""INSERT OR REPLACE INTO site_txs
                (site_id, tx_id, user, amount, method, type, date)
                VALUES (?,?,?,?,?,?,?)""",
                (site_id, t.get("id"), t.get("user", "?"), t.get("amount", 0),
                 t.get("method", ""), t.get("type", ""), t.get("date", "")))
        conn.commit()
        conn.close()
        orders = data.get("orders", [])
        pending = sum(1 for o in orders if o.get("status") == "pending")
        return {
            "pending_orders": pending,
            "delivered_orders": sum(1 for o in orders if o.get("status") == "delivered"),
            "memberships": {u.get("membership", "free"): True for u in data.get("users", [])},
            "balance_total": round(sum(u.get("balance", 0) for u in data.get("users", [])), 2),
        }
    except Exception:
        return None

def poll_btcpay():
    """Poll BTCPay Server for sync status, store data, and invoice counts."""
    try:
        # Server info (sync status)
        r = requests.get(f"{BTCPAY_URL}/api/v1/server/info",
                        headers={"Authorization": f"token {BTCPAY_KEY}"},
                        timeout=10, verify=False)
        if r.status_code != 200:
            return {"error": f"BTCPay returned {r.status_code}", "synced": False}
        
        info = r.json()
        sync_pct = 0
        blocks = 0
        headers = 0
        for s in info.get("syncStatus", []):
            ni = s.get("nodeInformation", {})
            if ni.get("headers") and ni.get("blocks"):
                blocks = ni["blocks"]
                headers = ni["headers"]
                sync_pct = (blocks / headers) * 100 if headers else 0
            elif ni.get("verificationProgress"):
                sync_pct = ni["verificationProgress"] * 100
        
        # Per-store data
        stores_data = {}
        total_invoices = 0
        stores_with_wallet = 0
        
        for sid, store_id in BTCPAY_STORES.items():
            try:
                # Check payment methods
                pm_r = requests.get(f"{BTCPAY_URL}/api/v1/stores/{store_id}/payment-methods",
                                   headers={"Authorization": f"token {BTCPAY_KEY}"},
                                   timeout=5, verify=False)
                has_wallet = False
                if pm_r.status_code == 200:
                    pms = pm_r.json()
                    has_wallet = any(pm.get("enabled") for pm in pms if isinstance(pm, dict))
                
                if has_wallet:
                    stores_with_wallet += 1
                
                # Count invoices
                inv_r = requests.get(f"{BTCPAY_URL}/api/v1/stores/{store_id}/invoices?take=100",
                                    headers={"Authorization": f"token {BTCPAY_KEY}"},
                                    timeout=5, verify=False)
                invoice_count = 0
                invoices_by_status = {}
                if inv_r.status_code == 200:
                    invoices = inv_r.json()
                    invoice_count = len(invoices)
                    total_invoices += invoice_count
                    for inv in invoices:
                        status = inv.get("status", "unknown")
                        invoices_by_status[status] = invoices_by_status.get(status, 0) + 1
                
                stores_data[sid] = {
                    "has_wallet": has_wallet,
                    "invoice_count": invoice_count,
                    "invoices_by_status": invoices_by_status,
                    "store_id": store_id[:16] + "...",
                }
            except Exception as e:
                stores_data[sid] = {"has_wallet": False, "invoice_count": 0, "error": str(e)[:80]}
        
        # Save snapshot to DB
        conn = sqlite3.connect(DB_PATH)
        c = conn.cursor()
        c.execute(
            "INSERT INTO btcpay_snapshots (timestamp, sync_pct, blocks, headers, synced, store_count, stores_with_wallet, total_invoices) VALUES (?,?,?,?,?,?,?,?)",
            (time.time(), round(sync_pct, 2), blocks, headers, 
             1 if info.get("fullySynched") else 0, len(BTCPAY_STORES), stores_with_wallet, total_invoices)
        )
        conn.commit()
        conn.close()
        
        return {
            "synced": info.get("fullySynched", False),
            "sync_pct": round(sync_pct, 2),
            "blocks": blocks,
            "headers": headers,
            "version": info.get("version", "?"),
            "stores_with_wallet": stores_with_wallet,
            "total_stores": len(BTCPAY_STORES),
            "total_invoices": total_invoices,
            "stores": stores_data,
            "error": None,
        }
    except Exception as e:
        return {"error": str(e)[:200], "synced": False, "sync_pct": 0, "stores": {}}

def poll_all():
    """Poll all 10 sites + BTCPay and update state."""
    global state
    results = {}
    total_users = 0
    total_products = 0
    online = 0

    for sid, info in SITES.items():
        result = poll_site(sid, info)
        details = poll_site_details(sid, info) if result["status"] == "online" else None
        if details:
            result["details"] = details
        results[sid] = result
        total_users += result["users"]
        total_products += result["products"]
        if result["status"] == "online":
            online += 1

    # BTCPay poll
    btcpay_data = poll_btcpay()

    # Store snapshots in DB
    now = time.time()
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    for sid, r in results.items():
        c.execute(
            "INSERT INTO snapshots (site_id, timestamp, users, products, orders, revenue, status, response_ms) VALUES (?,?,?,?,?,?,?,?)",
            (sid, now, r["users"], r["products"], r.get("orders", 0), r.get("revenue", 0), r["status"], r.get("response_ms", 0))
        )
    conn.commit()
    conn.close()

    with state_lock:
        old_state = state.get("sites", {})
        for sid, r in results.items():
            old_users = old_state.get(sid, {}).get("users", 0)
            old_orders = old_state.get(sid, {}).get("orders", 0)
            old_revenue = old_state.get(sid, {}).get("revenue", 0)
            
            if r["users"] > old_users and old_users > 0:
                event_detail = f"{r['emoji']} {r['name']}: +{r['users'] - old_users} new signup(s)"
                c = sqlite3.connect(DB_PATH).cursor()
                c.execute("INSERT INTO events (site_id, timestamp, event_type, detail) VALUES (?,?,?,?)",
                          (sid, now, "new_users", event_detail))
                c.connection.commit()
                c.connection.close()
            
            if r["orders"] > old_orders and old_orders > 0:
                new_orders = r["orders"] - old_orders
                new_rev = r["revenue"] - old_revenue
                event_detail = f"{r['emoji']} {r['name']}: +{new_orders} order(s) | +${new_rev:.2f} revenue"
                c = sqlite3.connect(DB_PATH).cursor()
                c.execute("INSERT INTO events (site_id, timestamp, event_type, detail) VALUES (?,?,?,?)",
                          (sid, now, "new_orders", event_detail))
                c.connection.commit()
                c.connection.close()

        # BTCPay events
        old_btcpay = old_state.get("btcpay", {}) if isinstance(old_state, dict) else {}
        if btcpay_data.get("synced") and not old_btcpay.get("synced"):
            event_detail = "₿ BTCPay: Node fully synced! Wallets can be generated."
            c = sqlite3.connect(DB_PATH).cursor()
            c.execute("INSERT INTO events (site_id, timestamp, event_type, detail) VALUES (?,?,?,?)",
                      (0, now, "btcpay_synced", event_detail))
            c.connection.commit()
            c.connection.close()

        total_revenue = sum(r.get("revenue", 0) for r in results.values())
        total_orders = sum(r.get("orders", 0) for r in results.values())
        
        state = {
            "sites": results,
            "totals": {
                "users": total_users,
                "products": total_products,
                "online": online,
                "offline": 10 - online,
                "orders": total_orders,
                "revenue": round(total_revenue, 2),
            },
            "last_poll": datetime.now().strftime("%H:%M:%S"),
            "last_poll_ts": now,
            "btcpay": btcpay_data,
        }

def poller_loop():
    """Background thread that polls every 30 seconds."""
    while True:
        try:
            poll_all()
        except Exception as e:
            print(f"Poll error: {e}")
        time.sleep(30)

@app.route('/')
def dashboard():
    return render_template('dashboard.html')

@app.route('/master')
def master():
    return render_template('master.html')

@app.route('/btcpay')
def btcpay_dash():
    """Dedicated BTCPay management view."""
    return render_template('btcpay.html')

@app.route('/recon')
def recon():
    """Payment processor recon tool."""
    return app.send_static_file('payment-recon/index.html')

@app.route('/watchdog')
def watchdog_page():
    """Self-healing watchdog status page."""
    return render_template('watchdog.html')

@app.route('/api/watchdog')
def api_watchdog():
    if WATCHDOG_AVAILABLE:
        return jsonify(get_watchdog_state())
    return jsonify({"error": "Watchdog not loaded"})

@app.route('/api/watchdog/run', methods=['POST'])
def api_watchdog_run():
    """Manually trigger a watchdog scan."""
    if WATCHDOG_AVAILABLE:
        watchdog_scan()
        return jsonify({"status": "ok", "result": get_watchdog_state()})
    return jsonify({"error": "Watchdog not available"})

@app.route('/api/state')
def api_state():
    with state_lock:
        s = dict(state)
    
    # Get recent events
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute("SELECT site_id, timestamp, event_type, detail FROM events ORDER BY timestamp DESC LIMIT 50")
    events = [{"site_id": r[0], "timestamp": r[1], "type": r[2], "detail": r[3]} for r in c.fetchall()]
    conn.close()
    
    s["events"] = events
    s["btc_address"] = BTC_ADDRESS
    
    # Load BTC stats from blockchain monitor DB
    try:
        btc_conn = sqlite3.connect("/Users/drjones/ccsite-manager/btc_chain.db")
        bc = btc_conn.cursor()
        bc.execute("SELECT COUNT(*), COALESCE(SUM(amount_btc),0), COALESCE(SUM(amount_sats),0) FROM transactions")
        btc_count, btc_total, btc_sats = bc.fetchone()
        bc.execute("SELECT COUNT(*), COALESCE(SUM(amount_btc),0) FROM transactions WHERE first_seen > ?", (time.time() - 86400,))
        btc_day_count, btc_day_btc = bc.fetchone()
        bc.execute("SELECT txid, amount_btc, amount_sats, timestamp FROM transactions ORDER BY timestamp DESC LIMIT 5")
        btc_recent = [{"txid": r[0][:16]+"...", "btc": r[1], "sats": r[2], "ts": r[3]} for r in bc.fetchall()]
        btc_conn.close()
        s["btc_stats"] = {
            "total_txs": btc_count, "total_btc": round(btc_total or 0, 8),
            "total_sats": btc_sats or 0, "total_usd": round((btc_total or 0) * 68000, 2),
            "day_txs": btc_day_count, "day_btc": round(btc_day_btc or 0, 8),
            "day_usd": round((btc_day_btc or 0) * 68000, 2), "recent": btc_recent,
        }
    except:
        s["btc_stats"] = {"total_txs": 0, "total_btc": 0, "total_usd": 0, "day_btc": 0, "recent": []}
    
    # Revenue history for chart (last 24h: per-site latest value per hour, summed)
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    since = time.time() - 86400
    c.execute("""SELECT site_id, timestamp, revenue FROM snapshots
                 WHERE timestamp > ? ORDER BY timestamp ASC""", (since,))
    per_site_hour = {}
    for sid, ts, rev in c.fetchall():
        per_site_hour[(sid, int(ts // 3600))] = rev  # later rows overwrite -> latest wins
    hourly = defaultdict(float)
    for (sid, hr), rev in per_site_hour.items():
        hourly[hr] += rev
    history = [{"ts": hr * 3600, "revenue": round(rev, 2)} for hr, rev in sorted(hourly.items())]
    conn.close()
    s["history"] = history
    
    # BTCPay history
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute("""SELECT timestamp, sync_pct FROM btcpay_snapshots 
                 WHERE timestamp > ? ORDER BY timestamp ASC LIMIT 100""", (since,))
    btcpay_history = [{"ts": r[0], "sync_pct": r[1]} for r in c.fetchall()]
    conn.close()
    s["btcpay_history"] = btcpay_history
    
    return jsonify(s)

@app.route('/api/history')
def api_history():
    """Get historical data for charts."""
    hours = int(request.args.get('hours', 24))
    since = time.time() - (hours * 3600)
    
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute("""SELECT site_id, timestamp, users, status FROM snapshots 
                 WHERE timestamp > ? ORDER BY timestamp ASC""", (since,))
    rows = c.fetchall()
    conn.close()

    # Group by site
    history = defaultdict(list)
    for row in rows:
        history[row[0]].append({
            "ts": row[1],
            "users": row[2],
            "status": row[3],
        })
    
    # Convert to serializable format
    result = {}
    for sid, points in history.items():
        # Downsample to ~100 points max
        step = max(1, len(points) // 100)
        result[str(sid)] = points[::step]
    
    return jsonify(result)

@app.route('/api/orders')
def api_orders():
    """Cross-site live order feed, newest first."""
    limit = int(request.args.get('limit', 40))
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute("""SELECT site_id, product, amount, qty, status, email, code, date
                 FROM site_orders ORDER BY date DESC LIMIT ?""", (limit,))
    rows = [{"site_id": r[0], "site": SITES.get(r[0], {}).get("name", "?"),
             "emoji": SITES.get(r[0], {}).get("emoji", ""),
             "product": r[1], "amount": r[2], "qty": r[3], "status": r[4],
             "email": r[5], "code": r[6], "date": r[7]} for r in c.fetchall()]
    conn.close()
    return jsonify(rows)

@app.route('/api/signups')
def api_signups():
    """Cross-site signup feed, newest first, plus membership breakdown."""
    limit = int(request.args.get('limit', 40))
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute("""SELECT site_id, username, email, membership, balance, orders, joined
                 FROM site_users ORDER BY joined DESC LIMIT ?""", (limit,))
    rows = [{"site_id": r[0], "site": SITES.get(r[0], {}).get("name", "?"),
             "emoji": SITES.get(r[0], {}).get("emoji", ""),
             "username": r[1], "email": r[2], "membership": r[3],
             "balance": r[4], "orders": r[5], "joined": r[6]} for r in c.fetchall()]
    c.execute("SELECT membership, COUNT(*) FROM site_users GROUP BY membership")
    tier_labels = {"0": "free", "1": "pro", "2": "elite", 0: "free", 1: "pro", 2: "elite"}
    tiers = {tier_labels.get(r[0], r[0] or "free"): r[1] for r in c.fetchall()}
    c.execute("SELECT site_id, COUNT(*) FROM site_users GROUP BY site_id")
    per_site = {str(r[0]): r[1] for r in c.fetchall()}
    conn.close()
    return jsonify({"recent": rows, "tiers": tiers, "per_site": per_site})

@app.route('/api/leaderboard')
def api_leaderboard():
    """Per-site ranked performance table."""
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    board = []
    for sid, info in SITES.items():
        c.execute("SELECT COUNT(*), COALESCE(SUM(amount*qty),0), COALESCE(AVG(amount*qty),0) FROM site_orders WHERE site_id=?", (sid,))
        n_orders, rev, aov = c.fetchone()
        c.execute("SELECT COUNT(*) FROM site_orders WHERE site_id=? AND status='pending'", (sid,))
        pending = c.fetchone()[0]
        c.execute("SELECT COUNT(*) FROM site_users WHERE site_id=?", (sid,))
        n_users = c.fetchone()[0]
        c.execute("SELECT COUNT(*) FROM site_orders WHERE site_id=? AND date > datetime('now','-1 day')", (sid,))
        orders_24h = c.fetchone()[0]
        board.append({"site_id": sid, "site": info["name"], "emoji": info["emoji"],
                      "orders": n_orders, "revenue": round(rev, 2), "aov": round(aov, 2),
                      "pending": pending, "users": n_users, "orders_24h": orders_24h})
    conn.close()
    board.sort(key=lambda x: x["revenue"], reverse=True)
    return jsonify(board)

@app.route('/api/top-products')
def api_top_products():
    """Best-selling products across the fleet by order count and revenue."""
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute("""SELECT product, COUNT(*), SUM(amount*qty), COUNT(DISTINCT site_id)
                 FROM site_orders GROUP BY product ORDER BY COUNT(*) DESC, SUM(amount*qty) DESC LIMIT 15""")
    rows = [{"product": r[0], "orders": r[1], "revenue": round(r[2] or 0, 2),
             "sites": r[3]} for r in c.fetchall()]
    conn.close()
    return jsonify(rows)

@app.route('/api/activity')
def api_activity():
    """Hourly buckets for the last 48h: orders, revenue, signups. Plus 14-day daily."""
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    hourly = {}
    c.execute("""SELECT substr(date,1,13), COUNT(*), SUM(amount*qty) FROM site_orders
                 WHERE date > datetime('now','-2 day') GROUP BY substr(date,1,13)""")
    for r in c.fetchall():
        hourly[r[0]] = {"orders": r[1], "revenue": round(r[2] or 0, 2)}
    c.execute("""SELECT substr(joined,1,13), COUNT(*) FROM site_users
                 WHERE joined > datetime('now','-2 day') GROUP BY substr(joined,1,13)""")
    for r in c.fetchall():
        hourly.setdefault(r[0], {})["signups"] = r[1]
    daily = {}
    c.execute("""SELECT substr(date,1,10), COUNT(*), SUM(amount*qty) FROM site_orders
                 WHERE date > datetime('now','-14 day') GROUP BY substr(date,1,10)""")
    for r in c.fetchall():
        daily[r[0]] = {"orders": r[1], "revenue": round(r[2] or 0, 2)}
    c.execute("""SELECT substr(joined,1,10), COUNT(*) FROM site_users
                 WHERE joined > datetime('now','-14 day') GROUP BY substr(joined,1,10)""")
    for r in c.fetchall():
        daily.setdefault(r[0], {})["signups"] = r[1]
    conn.close()
    return jsonify({"hourly": hourly, "daily": daily})

@app.route('/api/funnel')
def api_funnel():
    """Fleet conversion funnel + headline numbers."""
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute("SELECT COUNT(*) FROM site_users")
    users = c.fetchone()[0]
    c.execute("SELECT COUNT(*) FROM site_users WHERE orders > 0")
    buyers = c.fetchone()[0]
    c.execute("SELECT COUNT(*), COALESCE(SUM(amount*qty),0) FROM site_orders")
    orders, revenue = c.fetchone()
    c.execute("SELECT COUNT(*) FROM site_orders WHERE status='pending'")
    pending = c.fetchone()[0]
    c.execute("SELECT COUNT(*) FROM site_orders WHERE status='delivered'")
    delivered = c.fetchone()[0]
    c.execute("SELECT COALESCE(SUM(amount),0) FROM site_txs WHERE type='deposit'")
    deposits = c.fetchone()[0]
    c.execute("SELECT COUNT(DISTINCT email) FROM site_orders")
    unique_customers = c.fetchone()[0]
    conn.close()
    return jsonify({
        "users": users, "buyers": buyers, "orders": orders,
        "revenue": round(revenue, 2), "pending": pending, "delivered": delivered,
        "deposits": round(deposits, 2), "unique_customers": unique_customers,
        "conversion": round(100.0 * buyers / users, 1) if users else 0,
        "aov": round(revenue / orders, 2) if orders else 0,
    })

if __name__ == '__main__':
    init_db()
    poll_all()
    t = threading.Thread(target=poller_loop, daemon=True)
    t.start()
    # Start watchdog
    if WATCHDOG_AVAILABLE:
        import watchdog
        wt = threading.Thread(target=watchdog.watchdog_loop, daemon=True)
        wt.start()
        watchdog.run_all_checks()
        print(f"  /watchdog — Self-healing health monitor")
    print(f"Dashboard starting on http://localhost:5050")
    print(f"  /       — Fleet dashboard")
    print(f"  /master — Master control panel")
    print(f"  /btcpay — BTCPay management")
    app.run(host='0.0.0.0', port=5050, debug=False)
