"""Standalone license server — FastAPI on port 8450.

Run:
    uvicorn mesh.licensing.server:app --host 0.0.0.0 --port 8450
"""
from __future__ import annotations

import json
import logging
import os
import sqlite3
import time
from collections import defaultdict
from contextlib import asynccontextmanager
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any, Dict, List, Optional

from fastapi import Depends, FastAPI, Header, HTTPException, Request
from fastapi.responses import JSONResponse

from .keygen import generate_license_key
from .models import (
    LicenseCreate,
    LicenseInfo,
    LicenseRecord,
    LicenseTier,
    TelemetryPayload,
    UsageReport,
    TIER_DEFAULTS,
)

logger = logging.getLogger("mesh.licensing.server")

# ── Configuration ────────────────────────────────────────────────────────────

ADMIN_API_KEY = os.environ.get("MESH_LICENSE_ADMIN_KEY", "mesh-admin-secret")
DB_PATH = os.environ.get("MESH_LICENSE_DB", "data/licenses.db")

# Rate limiting: 10 req/min per license key on heartbeat
_heartbeat_timestamps: Dict[str, List[float]] = defaultdict(list)
HEARTBEAT_RATE_LIMIT = 10
HEARTBEAT_WINDOW_S = 60


# ── Database ─────────────────────────────────────────────────────────────────


class LicenseDB:
    """SQLite-backed license storage."""

    def __init__(self, db_path: str):
        self.db_path = db_path
        Path(db_path).parent.mkdir(parents=True, exist_ok=True)
        self.conn = sqlite3.connect(db_path, check_same_thread=False)
        self.conn.row_factory = sqlite3.Row
        self._init_tables()

    def _init_tables(self):
        self.conn.executescript("""
            CREATE TABLE IF NOT EXISTS licenses (
                license_id INTEGER PRIMARY KEY AUTOINCREMENT,
                customer_name TEXT NOT NULL,
                customer_email TEXT NOT NULL,
                license_key TEXT UNIQUE NOT NULL,
                tier TEXT NOT NULL DEFAULT 'community',
                max_nodes INTEGER NOT NULL DEFAULT 5,
                features TEXT NOT NULL DEFAULT '[]',
                issued_at TEXT NOT NULL,
                expires_at TEXT,
                active INTEGER NOT NULL DEFAULT 1
            );
            CREATE INDEX IF NOT EXISTS idx_license_key ON licenses(license_key);

            CREATE TABLE IF NOT EXISTS heartbeats (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                license_key TEXT NOT NULL,
                timestamp TEXT NOT NULL,
                node_count INTEGER DEFAULT 0,
                hardware_summary TEXT DEFAULT '{}',
                mesh_version TEXT DEFAULT '',
                uptime_hours REAL DEFAULT 0,
                avg_cpu_pct REAL DEFAULT 0,
                avg_memory_pct REAL DEFAULT 0,
                total_jobs_run INTEGER DEFAULT 0,
                total_retrains INTEGER DEFAULT 0,
                FOREIGN KEY (license_key) REFERENCES licenses(license_key)
            );
            CREATE INDEX IF NOT EXISTS idx_hb_key ON heartbeats(license_key);
        """)
        self.conn.commit()

    def create_license(self, record: LicenseRecord) -> LicenseRecord:
        cur = self.conn.execute(
            """INSERT INTO licenses
               (customer_name, customer_email, license_key, tier, max_nodes,
                features, issued_at, expires_at, active)
               VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
            (
                record.customer_name,
                record.customer_email,
                record.license_key,
                record.tier.value,
                record.max_nodes,
                json.dumps(record.features),
                record.issued_at.isoformat(),
                record.expires_at.isoformat() if record.expires_at else None,
                1 if record.active else 0,
            ),
        )
        self.conn.commit()
        record.license_id = cur.lastrowid
        return record

    def get_license(self, key: str) -> Optional[LicenseRecord]:
        row = self.conn.execute(
            "SELECT * FROM licenses WHERE license_key = ?", (key,)
        ).fetchone()
        if not row:
            return None
        return self._row_to_record(row)

    def list_licenses(self) -> List[LicenseRecord]:
        rows = self.conn.execute(
            "SELECT * FROM licenses ORDER BY issued_at DESC"
        ).fetchall()
        return [self._row_to_record(r) for r in rows]

    def deactivate_license(self, key: str) -> bool:
        cur = self.conn.execute(
            "UPDATE licenses SET active = 0 WHERE license_key = ?", (key,)
        )
        self.conn.commit()
        return cur.rowcount > 0

    def record_heartbeat(self, key: str, payload: TelemetryPayload):
        self.conn.execute(
            """INSERT INTO heartbeats
               (license_key, timestamp, node_count, hardware_summary,
                mesh_version, uptime_hours, avg_cpu_pct, avg_memory_pct,
                total_jobs_run, total_retrains)
               VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
            (
                key,
                datetime.utcnow().isoformat(),
                payload.node_count,
                json.dumps(payload.hardware_summary),
                payload.mesh_version,
                payload.uptime_hours,
                payload.avg_cpu_pct,
                payload.avg_memory_pct,
                payload.total_jobs_run,
                payload.total_retrains,
            ),
        )
        self.conn.commit()

    def get_usage(self, key: str) -> UsageReport:
        row = self.conn.execute(
            """SELECT
                 COUNT(*) as total_hb,
                 MAX(node_count) as peak_nodes,
                 MAX(timestamp) as last_hb
               FROM heartbeats WHERE license_key = ?""",
            (key,),
        ).fetchone()

        # Daily active nodes: distinct days with heartbeats in last 30 days
        cutoff = (datetime.utcnow() - timedelta(days=30)).isoformat()
        daily_row = self.conn.execute(
            """SELECT COUNT(DISTINCT DATE(timestamp)) as active_days
               FROM heartbeats
               WHERE license_key = ? AND timestamp >= ?""",
            (key, cutoff),
        ).fetchone()

        # Recent telemetry
        history_rows = self.conn.execute(
            """SELECT timestamp, node_count, mesh_version, uptime_hours,
                      avg_cpu_pct, avg_memory_pct, total_jobs_run, total_retrains
               FROM heartbeats WHERE license_key = ?
               ORDER BY timestamp DESC LIMIT 100""",
            (key,),
        ).fetchall()

        last_hb = None
        if row["last_hb"]:
            try:
                last_hb = datetime.fromisoformat(row["last_hb"])
            except (ValueError, TypeError):
                pass

        return UsageReport(
            license_key=key,
            daily_active_nodes=daily_row["active_days"] if daily_row else 0,
            peak_nodes=row["peak_nodes"] or 0,
            total_heartbeats=row["total_hb"] or 0,
            last_heartbeat=last_hb,
            telemetry_history=[dict(r) for r in history_rows],
        )

    def get_all_telemetry(self, limit: int = 500) -> List[Dict[str, Any]]:
        rows = self.conn.execute(
            """SELECT h.*, l.customer_name, l.tier
               FROM heartbeats h
               JOIN licenses l ON h.license_key = l.license_key
               ORDER BY h.timestamp DESC LIMIT ?""",
            (limit,),
        ).fetchall()
        return [dict(r) for r in rows]

    def _row_to_record(self, row: sqlite3.Row) -> LicenseRecord:
        features = json.loads(row["features"]) if row["features"] else []
        expires_at = None
        if row["expires_at"]:
            try:
                expires_at = datetime.fromisoformat(row["expires_at"])
            except (ValueError, TypeError):
                pass
        return LicenseRecord(
            license_id=row["license_id"],
            customer_name=row["customer_name"],
            customer_email=row["customer_email"],
            license_key=row["license_key"],
            tier=LicenseTier(row["tier"]),
            max_nodes=row["max_nodes"],
            features=features,
            issued_at=datetime.fromisoformat(row["issued_at"]),
            expires_at=expires_at,
            active=bool(row["active"]),
        )

    def close(self):
        self.conn.close()


# ── Helpers ──────────────────────────────────────────────────────────────────


def _require_admin(x_admin_key: str = Header(None)):
    if x_admin_key != ADMIN_API_KEY:
        raise HTTPException(status_code=403, detail="Invalid admin key")


def _check_heartbeat_rate(key: str):
    now = time.monotonic()
    stamps = _heartbeat_timestamps[key]
    # Purge old entries
    _heartbeat_timestamps[key] = [t for t in stamps if now - t < HEARTBEAT_WINDOW_S]
    if len(_heartbeat_timestamps[key]) >= HEARTBEAT_RATE_LIMIT:
        raise HTTPException(status_code=429, detail="Heartbeat rate limit exceeded")
    _heartbeat_timestamps[key].append(now)


def _record_to_info(record: LicenseRecord) -> LicenseInfo:
    now = datetime.utcnow()
    expired = record.expires_at is not None and record.expires_at < now
    return LicenseInfo(
        license_key=record.license_key,
        tier=record.tier,
        customer_name=record.customer_name,
        customer_email=record.customer_email,
        max_nodes=record.max_nodes,
        features=record.features,
        issued_at=record.issued_at,
        expires_at=record.expires_at,
        valid=record.active and not expired,
    )


# ── Application ──────────────────────────────────────────────────────────────

_db: Optional[LicenseDB] = None


@asynccontextmanager
async def lifespan(app: FastAPI):
    global _db
    _db = LicenseDB(DB_PATH)
    logger.info("License server started — DB at %s", DB_PATH)
    yield
    _db.close()
    logger.info("License server stopped")


app = FastAPI(
    title="Mesh Optimizer License Server",
    version="1.0.0",
    lifespan=lifespan,
)


def _get_db() -> LicenseDB:
    if _db is None:
        raise HTTPException(status_code=503, detail="Database not initialized")
    return _db


# ── Endpoints ────────────────────────────────────────────────────────────────


@app.post("/licenses", response_model=LicenseRecord)
def create_license(
    body: LicenseCreate,
    _: None = Depends(_require_admin),
    db: LicenseDB = Depends(_get_db),
):
    """Create a new license (admin only)."""
    tier_defaults = TIER_DEFAULTS[body.tier]
    now = datetime.utcnow()
    record = LicenseRecord(
        customer_name=body.customer_name,
        customer_email=body.customer_email,
        license_key=generate_license_key(),
        tier=body.tier,
        max_nodes=body.max_nodes if body.max_nodes is not None else tier_defaults["max_nodes"],
        features=body.features if body.features is not None else tier_defaults["features"],
        issued_at=now,
        expires_at=now + timedelta(days=30 * body.duration_months),
        active=True,
    )
    return db.create_license(record)


@app.get("/licenses/{key}", response_model=LicenseInfo)
def validate_license(key: str, db: LicenseDB = Depends(_get_db)):
    """Validate a license key and return its info."""
    record = db.get_license(key)
    if not record:
        raise HTTPException(status_code=404, detail="License not found")
    return _record_to_info(record)


@app.post("/licenses/{key}/heartbeat")
def license_heartbeat(
    key: str,
    body: TelemetryPayload,
    db: LicenseDB = Depends(_get_db),
):
    """Receive node heartbeat with telemetry."""
    _check_heartbeat_rate(key)
    record = db.get_license(key)
    if not record:
        raise HTTPException(status_code=404, detail="License not found")
    if not record.active:
        raise HTTPException(status_code=403, detail="License deactivated")
    db.record_heartbeat(key, body)
    return {"status": "ok"}


@app.get("/licenses/{key}/usage", response_model=UsageReport)
def license_usage(key: str, db: LicenseDB = Depends(_get_db)):
    """Get usage statistics for a license."""
    record = db.get_license(key)
    if not record:
        raise HTTPException(status_code=404, detail="License not found")
    return db.get_usage(key)


@app.post("/licenses/{key}/deactivate")
def deactivate_license(
    key: str,
    _: None = Depends(_require_admin),
    db: LicenseDB = Depends(_get_db),
):
    """Deactivate a license (admin only)."""
    if not db.deactivate_license(key):
        raise HTTPException(status_code=404, detail="License not found")
    return {"status": "ok", "message": f"License {key} deactivated"}


@app.get("/admin/licenses", response_model=List[LicenseRecord])
def list_all_licenses(
    _: None = Depends(_require_admin),
    db: LicenseDB = Depends(_get_db),
):
    """List all licenses (admin only)."""
    return db.list_licenses()


@app.get("/admin/telemetry")
def aggregated_telemetry(
    limit: int = 500,
    _: None = Depends(_require_admin),
    db: LicenseDB = Depends(_get_db),
):
    """Aggregated telemetry across all licenses (admin only)."""
    return db.get_all_telemetry(limit=limit)


@app.get("/health")
def health():
    return {"status": "ok", "service": "mesh-license-server"}


def main():
    """CLI entry point for mesh-license-server."""
    import argparse
    import uvicorn

    parser = argparse.ArgumentParser(description="Mesh License Server")
    parser.add_argument("--host", default="0.0.0.0")
    parser.add_argument("--port", type=int, default=8450)
    parser.add_argument("--db", default=None, help="License database path")
    args = parser.parse_args()

    if args.db:
        global DB_PATH
        DB_PATH = args.db

    uvicorn.run(app, host=args.host, port=args.port)


if __name__ == "__main__":
    main()
