"""
BizNode Launcher
================
Primary entry point for BizNode dashboard.

  Run:     python launcher.py
  Compile: pyarmor gen launcher.py services/license_validator.py

Gate order (runs BEFORE any heavy import):
  1. Valid USB license       → start immediately (Pro)
  2. Registered + trial OK   → start (Trial mode)
  3. Not registered          → prompt registration → start if successful
  4. Trial expired (server)  → BLOCKED
"""

import sys
import os
import subprocess
import json

_ROOT = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, _ROOT)

_REGISTER_API  = "https://biznode.1bz.biz/api/register"
_TRIAL_API     = "https://biznode.1bz.biz/api/trial/status"
_REG_FILE      = os.path.join(_ROOT, ".registration")   # stores registration_id


# ── Dependency check ──────────────────────────────────────────────────────────

def _ensure_deps():
    req_file = os.path.join(_ROOT, "requirements.txt")
    if not os.path.exists(req_file):
        return
    probes = ["langgraph", "langchain_core", "qdrant_client", "apscheduler", "fastapi"]
    missing = [p for p in probes if not _importable(p)]
    if missing:
        print(f"[!] Missing packages: {', '.join(missing)}")
        print("[*] Installing — please wait...")
        r = subprocess.run(
            [sys.executable, "-m", "pip", "install", "-r", req_file, "--user", "-q"],
            capture_output=False,
        )
        if r.returncode == 0:
            print("[OK] Dependencies installed.\n")
        else:
            print("[!] Some packages failed. Continuing...\n")


def _importable(pkg: str) -> bool:
    try:
        __import__(pkg)
        return True
    except ImportError:
        return False


# ── Machine fingerprint ───────────────────────────────────────────────────────

def _machine_fingerprint() -> str:
    """
    SHA-256 hex fingerprint of volume serial + MAC + hostname.
    Must be exactly 64 hex chars to pass server validation.
    Matches the algorithm documented in the server-side PHP.
    """
    import hashlib
    import socket
    import uuid

    parts = [socket.gethostname(), str(uuid.getnode())]

    if sys.platform == "win32":
        try:
            r = subprocess.run(
                ["wmic", "os", "get", "SerialNumber", "/value"],
                capture_output=True, text=True, timeout=5,
            )
            for line in r.stdout.splitlines():
                if "SerialNumber=" in line:
                    parts.append(line.split("=", 1)[1].strip())
                    break
        except Exception:
            pass

    return hashlib.sha256("|".join(parts).encode()).hexdigest()   # 64 hex chars


# ── Registration storage ──────────────────────────────────────────────────────

def _load_registration() -> dict | None:
    """Load saved registration from .registration file. Returns None if not found."""
    if not os.path.exists(_REG_FILE):
        return None
    try:
        with open(_REG_FILE, encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return None


def _save_registration(data: dict):
    """Persist registration data to .registration file."""
    try:
        with open(_REG_FILE, "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2)
    except OSError as e:
        print(f"[!] Could not save registration: {e}")


# ── Server API calls ──────────────────────────────────────────────────────────

def _api_register(payload: dict) -> dict | None:
    """
    POST /api/register
    Returns parsed JSON response or None on network error.
    """
    try:
        import urllib.request
        import urllib.error
        body = json.dumps(payload).encode()
        req  = urllib.request.Request(
            _REGISTER_API,
            data=body,
            headers={"Content-Type": "application/json"},
            method="POST",
        )
        with urllib.request.urlopen(req, timeout=15) as resp:
            return json.loads(resp.read())
    except urllib.error.HTTPError as e:
        try:
            return json.loads(e.read())
        except Exception:
            return {"ok": False, "error": f"HTTP {e.code}"}
    except Exception as e:
        return None   # network unreachable


def _api_trial_status(registration_id: str, fingerprint: str) -> dict | None:
    """
    POST /api/trial/status
    Returns parsed JSON response or None on network error.
    """
    try:
        import urllib.request
        import urllib.error
        body = json.dumps({
            "registration_id":    registration_id,
            "machine_fingerprint": fingerprint,
        }).encode()
        req = urllib.request.Request(
            _TRIAL_API,
            data=body,
            headers={"Content-Type": "application/json"},
            method="POST",
        )
        with urllib.request.urlopen(req, timeout=10) as resp:
            return json.loads(resp.read())
    except urllib.error.HTTPError as e:
        try:
            return json.loads(e.read())
        except Exception:
            return {"ok": False, "error": f"HTTP {e.code}"}
    except Exception:
        return None   # network unreachable


# ── Registration prompt ───────────────────────────────────────────────────────

def _prompt_register(fingerprint: str) -> dict | None:
    """
    Show a terminal registration form, call /api/register, save result.
    Returns the registration data dict on success, None on failure.
    """
    print()
    print("=" * 60)
    print("  BIZNODE — Registration Required")
    print("=" * 60)
    print()
    print("  Already have a license key? Enter it below.")
    print("  Or press Enter to start a 10-day free trial.")
    print()

    try:
        license_key = input("  License key (BZN-XXXXX-XXXXX-XXXXX) or Enter for trial: ").strip()
    except (EOFError, KeyboardInterrupt):
        print("\n\n  Registration cancelled.")
        return None

    if license_key and license_key.startswith("BZN-"):
        from services.license_validator import validate_basic_license
        result = validate_basic_license(key=license_key, fingerprint=fingerprint)
        if result.valid:
            print(f"\n  [OK] License key activated! Tier: BASIC")
            print(f"  You can run 1 bot. Upgrade to Pro for multi-bot + plugins.\n")
            # Return a dict so the caller knows we're authenticated
            return {"registration_id": "BASIC_KEY", "days_remaining": result.days_remaining or 365}
        else:
            print(f"\n  [!] {result.message}")
            print("  Continuing to trial registration...\n")

    print("  Register once to start your 10-day free trial.")
    print("  Your details are stored securely at https://biznode.1bz.biz")
    print()

    try:
        business_name = input("  Business Name  : ").strip()
        owner_name    = input("  Your Name      : ").strip()
        email         = input("  Email Address  : ").strip()
        phone         = input("  Phone (optional): ").strip()
        city          = input("  City  (optional): ").strip()
        country       = input("  Country         : ").strip()
    except (EOFError, KeyboardInterrupt):
        print("\n\n  Registration cancelled.")
        return None

    if not business_name or not owner_name or not email:
        print("\n  [!] Business name, your name, and email are required.")
        return None

    print()
    print("  [*] Registering — please wait...")

    payload = {
        "business_name":      business_name,
        "owner_name":         owner_name,
        "email":              email,
        "phone":              phone,
        "city":               city,
        "country":            country,
        "machine_fingerprint": fingerprint,
    }

    resp = _api_register(payload)

    if resp is None:
        print("  [!] Cannot reach registration server.")
        print("      Check your internet connection and try again.")
        return None

    if not resp.get("ok"):
        error = resp.get("error", "Registration failed.")
        print(f"  [!] {error}")
        return None

    reg_data = {
        "registration_id":  resp["registration_id"],
        "trial_expires_at": resp.get("trial_expires_at", ""),
        "days_remaining":   resp.get("days_remaining", 10),
        "email":            email,
        "business_name":    business_name,
        "fingerprint":      fingerprint,
    }
    _save_registration(reg_data)

    print()
    print("  [OK] Registration successful!")
    print(f"  Registration ID : {reg_data['registration_id']}")
    print(f"  Trial expires   : {reg_data['trial_expires_at']} UTC")
    print(f"  Days remaining  : {reg_data['days_remaining']}")
    print()

    return reg_data


# ── License gate ──────────────────────────────────────────────────────────────

_usb_watchdog = None   # global reference to prevent garbage collection


def _on_usb_removed(reason: str):
    """Called by USBWatchdog when USB is removed at runtime."""
    import logging
    logging.getLogger("biznode.launcher").critical(f"USB removed: {reason}")


def _gate():
    """
    Enforcement gate — runs before ANY other import.

    USB valid               →  start (Pro) + start USB watchdog
    Basic key valid         →  start (Basic, 1 bot)
    Registered + trial OK   →  start (Trial)
    Not registered          →  prompt → start if successful
    Trial expired (server)  →  sys.exit(1)
    Converted (PRO, no USB) →  BLOCKED — USB is mandatory
    """
    global _usb_watchdog
    from services.license_validator import validate_license, validate_basic_license, USBWatchdog

    fingerprint = _machine_fingerprint()

    # ── 1. USB license — highest priority ─────────────────────────────────────
    result = validate_license()
    if result.valid:
        tag = " [DEV]" if result.dev_mode else ""
        print(f"[PRO]{tag} Licensed — Order: {result.order_id} | "
              f"Expires: {result.expiry} ({result.days_remaining} days left)")
        if result.usb_fingerprint:
            print(f"[PRO] USB hardware verified (fingerprint: {result.usb_fingerprint[:12]}...)")

        # Start continuous USB presence monitoring
        _usb_watchdog = USBWatchdog(interval=30, on_failure=_on_usb_removed)
        _usb_watchdog.start()
        print("[PRO] USB watchdog active — do not remove USB while running.")
        return

    # ── 1.5. Basic license key ────────────────────────────────────────────────
    basic_result = validate_basic_license(fingerprint=fingerprint)
    if basic_result.valid:
        print(f"[BASIC] Licensed — {basic_result.message}")
        if basic_result.days_remaining is not None:
            print(f"[BASIC] Expires: {basic_result.expiry} ({basic_result.days_remaining} days left)")
        print("[BASIC] 1 bot allowed. Upgrade to Pro for multi-bot + plugins.")
        return

    # ── 2. Check registration ─────────────────────────────────────────────────
    reg = _load_registration()

    if reg is None:
        # First run — must register
        reg = _prompt_register(fingerprint)
        if reg is None:
            _blocked("Registration required to start BizNode.")
        # Registration just completed — trial starts now, allow through
        print(f"[TRIAL] Trial started — {reg['days_remaining']} day(s) remaining.")
        return

    # ── 3. Registration exists — verify trial with server ─────────────────────
    print("[*] Checking trial status...")
    resp = _api_trial_status(reg["registration_id"], fingerprint)

    if resp is None:
        # Network unreachable — fall back to local registration file
        print("[!] Server unreachable — using local trial record.")
        _local_trial_fallback(reg)
        return

    if not resp.get("ok"):
        # Server returned error (e.g. fingerprint mismatch, not found)
        error = resp.get("error", "Trial check failed.")
        _blocked(error)

    status       = resp.get("status", "")
    days_remaining = resp.get("days_remaining", 0)

    if status in ("converted",):
        # PRO license holder confirmed by server — but USB MUST be inserted
        _blocked(
            "You are a licensed PRO user, but your BizNode USB must be inserted.\n"
            "  Insert your licensed USB drive and restart."
        )

    if resp.get("valid"):
        print(f"[TRIAL] {resp.get('message', f'Trial active — {days_remaining} day(s) remaining.')}")
        if days_remaining <= 3:
            print(f"[TRIAL] Purchase at https://biznode.1bz.biz to avoid interruption.")
        return

    # Trial expired or blocked on server
    _blocked(resp.get("message", "Trial has expired. Insert your BizNode Pro USB to continue."))


def _local_trial_fallback(reg: dict):
    """
    When server is unreachable, fall back to the locally stored trial expiry.
    Allows legitimate users to work offline without interruption.
    """
    from datetime import datetime
    try:
        expires    = datetime.fromisoformat(reg["trial_expires_at"].replace("Z", ""))
        now        = datetime.utcnow()
        days_left  = max(0, (expires - now).days)
        if now < expires:
            print(f"[TRIAL] Offline mode — {days_left} day(s) remaining (server unverified).")
            return
        else:
            _blocked("Trial has expired. Insert your BizNode Pro USB to continue.")
    except Exception:
        _blocked("Could not verify trial status. Check internet connection.")


def _blocked(reason: str):
    print()
    print("=" * 60)
    print("  BIZNODE — Trial Expired")
    print("=" * 60)
    print()
    print(f"  {reason}")
    print()
    print("  Insert your BizNode Pro USB to continue.")
    print()
    print("  Purchase : https://biznode.1bz.biz")
    print("  Support  : biznode@1bz.biz")
    print()
    print("=" * 60)
    print()
    sys.exit(1)


# ── Port helper ───────────────────────────────────────────────────────────────

def _get_port() -> int:
    env_file = os.path.join(_ROOT, ".env")
    if os.path.exists(env_file):
        with open(env_file, encoding="utf-8") as f:
            for line in f:
                line = line.strip()
                if line.startswith("DASHBOARD_PORT="):
                    try:
                        return int(line.split("=", 1)[1].strip())
                    except ValueError:
                        pass
    return int(os.environ.get("DASHBOARD_PORT", 7777))


# ── Entry point ───────────────────────────────────────────────────────────────

if __name__ == "__main__":
    _ensure_deps()

    # Validate BEFORE importing uvicorn / FastAPI / LangGraph / everything else
    _gate()

    import uvicorn

    port = _get_port()
    print("=" * 50)
    print("  BizNode Dashboard")
    print(f"  http://localhost:{port}")
    print("=" * 50)
    uvicorn.run(
        "ui.server:app",
        host="127.0.0.1",
        port=port,
        reload=False,
        log_level="info",
    )
