
import os
import sqlite3
import secrets
import hashlib
import hmac
import smtplib
from email.message import EmailMessage
from datetime import datetime, timezone
from typing import Optional, List

from fastapi import FastAPI, HTTPException, Header, Request, Query
from fastapi.responses import HTMLResponse, JSONResponse
from pydantic import BaseModel

DB_FILE = os.getenv("LICENSE_DB", "/opt/scorpio-license-server/licenses.db")
ADMIN_API_KEY = os.getenv("ADMIN_API_KEY", "")
APP_SHARED_SECRET = os.getenv("APP_SHARED_SECRET", "")
LEMON_WEBHOOK_SECRET = os.getenv("LEMON_WEBHOOK_SECRET", "")
GUMROAD_WEBHOOK_TOKEN = os.getenv("GUMROAD_WEBHOOK_TOKEN", "")
BASE_URL = os.getenv("BASE_URL", "https://license.scorpioevents.au")

SMTP_HOST = os.getenv("SMTP_HOST", "")
SMTP_PORT = int(os.getenv("SMTP_PORT", "587"))
SMTP_USERNAME = os.getenv("SMTP_USERNAME", "")
SMTP_PASSWORD = os.getenv("SMTP_PASSWORD", "")
SMTP_FROM = os.getenv("SMTP_FROM", "")
SMTP_USE_TLS = os.getenv("SMTP_USE_TLS", "true").lower() == "true"

DEFAULT_PRODUCT_NAME = os.getenv("DEFAULT_PRODUCT_NAME", "Scorpios Tracks Snort 2025 v2")
DEFAULT_MAX_ACTIVATIONS = int(os.getenv("DEFAULT_MAX_ACTIVATIONS", "3"))

app = FastAPI(title="Scorpio License Server", version="1.0.0")


def utc_now() -> str:
    return datetime.now(timezone.utc).isoformat()


def parse_iso8601(s: str | None):
    if not s:
        return None
    return datetime.fromisoformat(s)


def send_email(to_email: str, subject: str, body: str) -> bool:
    if not (SMTP_HOST and SMTP_FROM and to_email):
        return False
    msg = EmailMessage()
    msg["From"] = SMTP_FROM
    msg["To"] = to_email
    msg["Subject"] = subject
    msg.set_content(body)

    with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=30) as server:
        if SMTP_USE_TLS:
            server.starttls()
        if SMTP_USERNAME:
            server.login(SMTP_USERNAME, SMTP_PASSWORD)
        server.send_message(msg)
    return True


def db():
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    return conn


def init_db():
    conn = db()
    cur = conn.cursor()

    cur.execute("""
    CREATE TABLE IF NOT EXISTS licenses (
        license_key TEXT PRIMARY KEY,
        product_name TEXT NOT NULL,
        customer_email TEXT,
        customer_name TEXT,
        status TEXT NOT NULL DEFAULT 'active',
        max_activations INTEGER NOT NULL DEFAULT 3,
        source_platform TEXT,
        source_order_id TEXT,
        created_at TEXT NOT NULL,
        updated_at TEXT NOT NULL,
        expires_at TEXT
    )
    """)

    cur.execute("""
    CREATE TABLE IF NOT EXISTS activations (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        license_key TEXT NOT NULL,
        machine_id TEXT NOT NULL,
        device_name TEXT,
        activated_at TEXT NOT NULL,
        last_check_at TEXT NOT NULL,
        UNIQUE(license_key, machine_id)
    )
    """)

    cur.execute("""
    CREATE TABLE IF NOT EXISTS webhook_events (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        provider TEXT NOT NULL,
        event_name TEXT,
        external_id TEXT,
        received_at TEXT NOT NULL,
        raw_body TEXT NOT NULL,
        processed INTEGER NOT NULL DEFAULT 0
    )
    """)

    conn.commit()
    conn.close()


@app.on_event("startup")
def startup():
    init_db()


def count_activations(conn, license_key: str) -> int:
    cur = conn.cursor()
    cur.execute("SELECT COUNT(*) AS c FROM activations WHERE license_key = ?", (license_key,))
    return int(cur.fetchone()["c"])


def get_license(conn, license_key: str):
    cur = conn.cursor()
    cur.execute("""
        SELECT * FROM licenses WHERE license_key = ?
    """, (license_key,))
    return cur.fetchone()


def list_licenses(conn, status: str | None = None, limit: int = 200):
    cur = conn.cursor()
    if status:
        cur.execute("""
            SELECT l.*,
                   (SELECT COUNT(*) FROM activations a WHERE a.license_key = l.license_key) AS activation_count
            FROM licenses l
            WHERE l.status = ?
            ORDER BY l.created_at DESC
            LIMIT ?
        """, (status, limit))
    else:
        cur.execute("""
            SELECT l.*,
                   (SELECT COUNT(*) FROM activations a WHERE a.license_key = l.license_key) AS activation_count
            FROM licenses l
            ORDER BY l.created_at DESC
            LIMIT ?
        """, (limit,))
    return cur.fetchall()


def get_activations(conn, license_key: str):
    cur = conn.cursor()
    cur.execute("""
        SELECT id, machine_id, device_name, activated_at, last_check_at
        FROM activations
        WHERE license_key = ?
        ORDER BY activated_at ASC
    """, (license_key,))
    return cur.fetchall()


def require_admin(x_api_key: str | None):
    if not ADMIN_API_KEY:
        raise HTTPException(status_code=500, detail="ADMIN_API_KEY is not configured")
    if x_api_key != ADMIN_API_KEY:
        raise HTTPException(status_code=401, detail="Invalid admin API key")


def require_app_secret(x_app_secret: str | None):
    if not APP_SHARED_SECRET:
        raise HTTPException(status_code=500, detail="APP_SHARED_SECRET is not configured")
    if x_app_secret != APP_SHARED_SECRET:
        raise HTTPException(status_code=401, detail="Invalid app secret")


def validate_license_for_use(row):
    if row["status"] != "active":
        raise HTTPException(status_code=403, detail=f"License status is {row['status']}")
    expires_at = row["expires_at"]
    if expires_at:
        if parse_iso8601(expires_at) < datetime.now(timezone.utc):
            raise HTTPException(status_code=403, detail="License expired")


def issue_license_key() -> str:
    return "SCORPIO-" + secrets.token_hex(8).upper()


class IssueLicenseRequest(BaseModel):
    customer_email: Optional[str] = None
    customer_name: Optional[str] = None
    product_name: str = DEFAULT_PRODUCT_NAME
    max_activations: int = DEFAULT_MAX_ACTIVATIONS
    expires_at: Optional[str] = None
    source_platform: Optional[str] = None
    source_order_id: Optional[str] = None
    send_email_to_customer: bool = False


class ActivateRequest(BaseModel):
    license_key: str
    machine_id: str
    device_name: Optional[str] = None


class CheckRequest(BaseModel):
    license_key: str
    machine_id: str


class DeactivateRequest(BaseModel):
    license_key: str
    machine_id: str


class UpdateLicenseStatusRequest(BaseModel):
    license_key: str
    status: str


class ResetActivationsRequest(BaseModel):
    license_key: str


@app.get("/", response_class=JSONResponse)
def home():
    return {"status": "ok", "service": "Scorpio License Server", "time": utc_now()}


@app.get("/health")
def health():
    return {"ok": True, "time": utc_now()}


@app.get("/admin", response_class=HTMLResponse)
def admin_dashboard(
    x_api_key: str | None = Header(default=None),
    status: str | None = Query(default=None),
    limit: int = Query(default=200, le=1000),
):
    require_admin(x_api_key)
    conn = db()
    rows = list_licenses(conn, status=status, limit=limit)
    conn.close()

    def row_html(r):
        lic = r["license_key"]
        return f"""
        <tr>
            <td><code>{lic}</code></td>
            <td>{r['product_name'] or ''}</td>
            <td>{r['customer_email'] or ''}</td>
            <td>{r['status']}</td>
            <td>{r['activation_count']}/{r['max_activations']}</td>
            <td>{r['source_platform'] or ''}</td>
            <td>{r['source_order_id'] or ''}</td>
            <td>{r['created_at']}</td>
        </tr>
        """

    html = f"""
    <html>
    <head>
      <title>Scorpio License Admin</title>
      <style>
        body {{ font-family: Arial, sans-serif; margin: 24px; background: #0f172a; color: #e5e7eb; }}
        h1 {{ margin-bottom: 8px; }}
        .sub {{ color: #94a3b8; margin-bottom: 20px; }}
        table {{ width: 100%; border-collapse: collapse; background: #111827; }}
        th, td {{ border: 1px solid #334155; padding: 8px; font-size: 14px; vertical-align: top; }}
        th {{ background: #1f2937; text-align: left; }}
        code {{ color: #fbbf24; }}
      </style>
    </head>
    <body>
      <h1>Scorpio License Admin</h1>
      <div class="sub">View current license keys and activation counts.</div>
      <table>
        <thead>
          <tr>
            <th>License Key</th>
            <th>Product</th>
            <th>Email</th>
            <th>Status</th>
            <th>Activations</th>
            <th>Platform</th>
            <th>Order ID</th>
            <th>Created</th>
          </tr>
        </thead>
        <tbody>
          {''.join(row_html(r) for r in rows)}
        </tbody>
      </table>
    </body>
    </html>
    """
    return HTMLResponse(content=html)


@app.get("/admin/licenses")
def admin_list_licenses(
    x_api_key: str | None = Header(default=None),
    status: str | None = Query(default=None),
    limit: int = Query(default=200, le=1000),
):
    require_admin(x_api_key)
    conn = db()
    rows = list_licenses(conn, status=status, limit=limit)
    result = []
    for row in rows:
        result.append(dict(row))
    conn.close()
    return {"ok": True, "licenses": result}


@app.get("/admin/licenses/{license_key}")
def admin_get_license(license_key: str, x_api_key: str | None = Header(default=None)):
    require_admin(x_api_key)
    conn = db()
    row = get_license(conn, license_key)
    if not row:
        conn.close()
        raise HTTPException(status_code=404, detail="License not found")
    activations = [dict(x) for x in get_activations(conn, license_key)]
    out = dict(row)
    out["activations"] = activations
    out["activation_count"] = len(activations)
    conn.close()
    return {"ok": True, "license": out}


@app.post("/admin/issue")
def admin_issue_license(payload: IssueLicenseRequest, x_api_key: str | None = Header(default=None)):
    require_admin(x_api_key)
    key = issue_license_key()

    conn = db()
    cur = conn.cursor()
    now = utc_now()
    cur.execute("""
        INSERT INTO licenses (
            license_key, product_name, customer_email, customer_name, status,
            max_activations, source_platform, source_order_id, created_at, updated_at, expires_at
        ) VALUES (?, ?, ?, ?, 'active', ?, ?, ?, ?, ?, ?)
    """, (
        key,
        payload.product_name,
        payload.customer_email,
        payload.customer_name,
        payload.max_activations,
        payload.source_platform,
        payload.source_order_id,
        now,
        now,
        payload.expires_at
    ))
    conn.commit()
    conn.close()

    emailed = False
    if payload.send_email_to_customer and payload.customer_email:
        subject = f"Your license key for {payload.product_name}"
        body = (
            f"Hi {payload.customer_name or ''},\n\n"
            f"Thanks for your purchase.\n\n"
            f"Product: {payload.product_name}\n"
            f"License Key: {key}\n"
            f"Activations Allowed: {payload.max_activations}\n"
            f"License Server: {BASE_URL}\n\n"
            f"Keep this email for your records.\n"
        )
        try:
            emailed = send_email(payload.customer_email, subject, body)
        except Exception as e:
            emailed = False

    return {"ok": True, "license_key": key, "emailed": emailed, "max_activations": payload.max_activations}


@app.post("/admin/status")
def admin_update_license_status(
    payload: UpdateLicenseStatusRequest,
    x_api_key: str | None = Header(default=None),
):
    require_admin(x_api_key)
    if payload.status not in {"active", "suspended", "revoked"}:
        raise HTTPException(status_code=400, detail="Status must be active, suspended, or revoked")

    conn = db()
    cur = conn.cursor()
    cur.execute("""
        UPDATE licenses
        SET status = ?, updated_at = ?
        WHERE license_key = ?
    """, (payload.status, utc_now(), payload.license_key))
    if cur.rowcount == 0:
        conn.close()
        raise HTTPException(status_code=404, detail="License not found")
    conn.commit()
    conn.close()
    return {"ok": True, "license_key": payload.license_key, "status": payload.status}


@app.post("/admin/reset-activations")
def admin_reset_activations(
    payload: ResetActivationsRequest,
    x_api_key: str | None = Header(default=None),
):
    require_admin(x_api_key)
    conn = db()
    cur = conn.cursor()
    cur.execute("DELETE FROM activations WHERE license_key = ?", (payload.license_key,))
    deleted = cur.rowcount
    cur.execute("UPDATE licenses SET updated_at = ? WHERE license_key = ?", (utc_now(), payload.license_key))
    conn.commit()
    conn.close()
    return {"ok": True, "license_key": payload.license_key, "deleted_activations": deleted}


@app.post("/activate")
def activate(payload: ActivateRequest, x_app_secret: str | None = Header(default=None)):
    require_app_secret(x_app_secret)
    conn = db()
    row = get_license(conn, payload.license_key)
    if not row:
        conn.close()
        raise HTTPException(status_code=404, detail="License not found")

    validate_license_for_use(row)

    cur = conn.cursor()
    cur.execute("""
        SELECT id FROM activations WHERE license_key = ? AND machine_id = ?
    """, (payload.license_key, payload.machine_id))
    existing = cur.fetchone()

    if existing:
        cur.execute("""
            UPDATE activations
            SET last_check_at = ?, device_name = ?
            WHERE license_key = ? AND machine_id = ?
        """, (utc_now(), payload.device_name, payload.license_key, payload.machine_id))
        conn.commit()
        conn.close()
        return {
            "ok": True,
            "status": "already_activated",
            "product_name": row["product_name"],
            "machine_locked": True
        }

    total = count_activations(conn, payload.license_key)
    if total >= row["max_activations"]:
        conn.close()
        raise HTTPException(status_code=403, detail="Activation limit reached")

    now = utc_now()
    cur.execute("""
        INSERT INTO activations (
            license_key, machine_id, device_name, activated_at, last_check_at
        ) VALUES (?, ?, ?, ?, ?)
    """, (payload.license_key, payload.machine_id, payload.device_name, now, now))
    cur.execute("UPDATE licenses SET updated_at = ? WHERE license_key = ?", (now, payload.license_key))
    conn.commit()
    conn.close()

    return {
        "ok": True,
        "status": "activated",
        "product_name": row["product_name"],
        "machine_locked": True
    }


@app.post("/check")
def check_license(payload: CheckRequest, x_app_secret: str | None = Header(default=None)):
    require_app_secret(x_app_secret)
    conn = db()
    row = get_license(conn, payload.license_key)
    if not row:
        conn.close()
        raise HTTPException(status_code=404, detail="License not found")

    validate_license_for_use(row)

    cur = conn.cursor()
    cur.execute("""
        SELECT id FROM activations WHERE license_key = ? AND machine_id = ?
    """, (payload.license_key, payload.machine_id))
    existing = cur.fetchone()
    if not existing:
        conn.close()
        raise HTTPException(status_code=403, detail="Machine not activated for this license")

    cur.execute("""
        UPDATE activations
        SET last_check_at = ?
        WHERE license_key = ? AND machine_id = ?
    """, (utc_now(), payload.license_key, payload.machine_id))
    conn.commit()
    conn.close()
    return {"ok": True, "status": "valid", "product_name": row["product_name"]}


@app.post("/deactivate")
def deactivate(payload: DeactivateRequest, x_app_secret: str | None = Header(default=None)):
    require_app_secret(x_app_secret)
    conn = db()
    cur = conn.cursor()
    cur.execute("""
        DELETE FROM activations
        WHERE license_key = ? AND machine_id = ?
    """, (payload.license_key, payload.machine_id))
    deleted = cur.rowcount
    cur.execute("UPDATE licenses SET updated_at = ? WHERE license_key = ?", (utc_now(), payload.license_key))
    conn.commit()
    conn.close()

    if deleted == 0:
        raise HTTPException(status_code=404, detail="Activation not found")

    return {"ok": True, "status": "deactivated"}


def verify_lemon_signature(raw_body: bytes, signature_header: str | None) -> bool:
    if not LEMON_WEBHOOK_SECRET or not signature_header:
        return False
    expected = hmac.new(LEMON_WEBHOOK_SECRET.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature_header)


@app.post("/webhooks/lemon")
async def lemon_webhook(request: Request, x_signature: str | None = Header(default=None)):
    raw_body = await request.body()
    if not verify_lemon_signature(raw_body, x_signature):
        raise HTTPException(status_code=401, detail="Invalid Lemon Squeezy signature")

    payload = await request.json()
    event_name = request.headers.get("X-Event-Name") or payload.get("meta", {}).get("event_name")
    external_id = str(payload.get("data", {}).get("id", ""))

    conn = db()
    cur = conn.cursor()
    cur.execute("""
        INSERT INTO webhook_events (provider, event_name, external_id, received_at, raw_body, processed)
        VALUES (?, ?, ?, ?, ?, 0)
    """, ("lemon_squeezy", event_name, external_id, utc_now(), raw_body.decode("utf-8", errors="replace")))
    webhook_event_id = cur.lastrowid

    data = payload.get("data", {})
    attrs = data.get("attributes", {})
    meta = payload.get("meta", {})

    created_or_updated = False
    if event_name in {"order_created", "order_paid"}:
        email = attrs.get("user_email")
        name = attrs.get("user_name")
        order_id = attrs.get("identifier") or str(data.get("id") or "")
        cur.execute("SELECT license_key FROM licenses WHERE source_platform = ? AND source_order_id = ?", ("lemon_squeezy", order_id))
        existing = cur.fetchone()
        if not existing:
            key = issue_license_key()
            now = utc_now()
            cur.execute("""
                INSERT INTO licenses (
                    license_key, product_name, customer_email, customer_name, status,
                    max_activations, source_platform, source_order_id, created_at, updated_at, expires_at
                ) VALUES (?, ?, ?, ?, 'active', ?, ?, ?, ?, ?, ?)
            """, (
                key,
                DEFAULT_PRODUCT_NAME,
                email,
                name,
                DEFAULT_MAX_ACTIVATIONS,
                "lemon_squeezy",
                order_id,
                now,
                now,
                None
            ))
            created_or_updated = True
            try:
                if email:
                    send_email(
                        email,
                        f"Your license key for {DEFAULT_PRODUCT_NAME}",
                        f"Hi {name or ''},\n\nThanks for your purchase.\n\nLicense Key: {key}\nActivations Allowed: {DEFAULT_MAX_ACTIVATIONS}\nLicense Server: {BASE_URL}\n"
                    )
            except Exception:
                pass

    elif event_name in {"subscription_cancelled", "subscription_expired", "subscription_payment_failed"}:
        customer_email = attrs.get("user_email") or meta.get("custom_data", {}).get("user_email")
        if customer_email:
            cur.execute("""
                UPDATE licenses SET status = 'suspended', updated_at = ?
                WHERE customer_email = ?
            """, (utc_now(), customer_email))
            created_or_updated = cur.rowcount > 0
    elif event_name in {"subscription_resumed", "subscription_payment_success", "subscription_unpaused"}:
        customer_email = attrs.get("user_email") or meta.get("custom_data", {}).get("user_email")
        if customer_email:
            cur.execute("""
                UPDATE licenses SET status = 'active', updated_at = ?
                WHERE customer_email = ?
            """, (utc_now(), customer_email))
            created_or_updated = cur.rowcount > 0

    cur.execute("UPDATE webhook_events SET processed = 1 WHERE id = ?", (webhook_event_id,))
    conn.commit()
    conn.close()

    return {"ok": True, "event_name": event_name, "processed": created_or_updated}


@app.post("/webhooks/gumroad")
async def gumroad_webhook(request: Request):
    form = await request.form()
    token = str(form.get("token") or "")
    if not GUMROAD_WEBHOOK_TOKEN or token != GUMROAD_WEBHOOK_TOKEN:
        raise HTTPException(status_code=401, detail="Invalid Gumroad token")

    email = str(form.get("email") or "")
    full_name = str(form.get("full_name") or "")
    order_id = str(form.get("sale_id") or form.get("order_id") or "")
    refunded = str(form.get("refunded") or "").lower() == "true"
    test = str(form.get("test") or "").lower() == "true"

    conn = db()
    cur = conn.cursor()
    cur.execute("""
        INSERT INTO webhook_events (provider, event_name, external_id, received_at, raw_body, processed)
        VALUES (?, ?, ?, ?, ?, 0)
    """, ("gumroad", "sale", order_id, utc_now(), str(dict(form)), 0))
    webhook_event_id = cur.lastrowid

    if not refunded and not test and order_id:
        cur.execute("SELECT license_key FROM licenses WHERE source_platform = ? AND source_order_id = ?", ("gumroad", order_id))
        existing = cur.fetchone()
        if not existing:
            key = issue_license_key()
            now = utc_now()
            cur.execute("""
                INSERT INTO licenses (
                    license_key, product_name, customer_email, customer_name, status,
                    max_activations, source_platform, source_order_id, created_at, updated_at, expires_at
                ) VALUES (?, ?, ?, ?, 'active', ?, ?, ?, ?, ?, ?)
            """, (
                key,
                DEFAULT_PRODUCT_NAME,
                email,
                full_name,
                DEFAULT_MAX_ACTIVATIONS,
                "gumroad",
                order_id,
                now,
                now,
                None
            ))
            try:
                if email:
                    send_email(
                        email,
                        f"Your license key for {DEFAULT_PRODUCT_NAME}",
                        f"Hi {full_name or ''},\n\nThanks for your purchase.\n\nLicense Key: {key}\nActivations Allowed: {DEFAULT_MAX_ACTIVATIONS}\nLicense Server: {BASE_URL}\n"
                    )
            except Exception:
                pass

    cur.execute("UPDATE webhook_events SET processed = 1 WHERE id = ?", (webhook_event_id,))
    conn.commit()
    conn.close()
    return {"ok": True}
