#!/usr/bin/env python3
"""
License server — HTTP API for license activation, validation, and management.

Endpoints:
  POST /activate       Client activation (license key + hardware ID)
  POST /validate       Periodic online validation
  GET  /health         Health check
  GET  /admin/licenses List all licenses (admin, requires X-Admin-Token)
  POST /admin/revoke   Revoke a license (admin)

Usage:
  python3 license_server/server.py --host 127.0.0.1 --port 8788
"""
from __future__ import annotations

import hashlib
import hmac
import json
import os
import sys
from datetime import datetime, timezone
from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path
from typing import Any

ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))

from license_server.license_store import (
    activate,
    check_online,
    get_license,
    init_db,
    list_licenses,
    revoke_license,
)
from license_server.config import SIGNING_SECRET, PRODUCT_ID

_ADMIN_TOKEN = os.environ.get("GAT_ADMIN_TOKEN", "")
if not _ADMIN_TOKEN:
    print("[license_server] WARNING: GAT_ADMIN_TOKEN not set — using insecure default.",
          file=sys.stderr)
    _ADMIN_TOKEN = "change-me-admin-token-change-me"
ADMIN_TOKEN = _ADMIN_TOKEN


def _sign(payload: dict[str, Any]) -> str:
    """HMAC-SHA256 sign a payload."""
    data = json.dumps(payload, sort_keys=True, ensure_ascii=False).encode()
    sig = hmac.new(SIGNING_SECRET.encode(), data, hashlib.sha256).hexdigest()
    return sig


def _json_response(handler: BaseHTTPRequestHandler, code: int, payload: dict[str, Any]) -> None:
    body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
    handler.send_response(code)
    handler.send_header("Content-Type", "application/json; charset=utf-8")
    handler.send_header("Content-Length", str(len(body)))
    handler.end_headers()
    handler.wfile.write(body)


def _read_json_body(handler: BaseHTTPRequestHandler) -> dict[str, Any]:
    length = int(handler.headers.get("Content-Length", "0"))
    if length == 0:
        return {}
    data = handler.rfile.read(length)
    return json.loads(data.decode("utf-8"))


class LicenseHandler(BaseHTTPRequestHandler):

    def do_GET(self) -> None:
        if self.path == "/health":
            _json_response(self, 200, {"ok": True, "product": PRODUCT_ID})
        elif self.path.startswith("/admin/licenses"):
            self._handle_admin_list()
        else:
            _json_response(self, 404, {"ok": False, "error": "NOT_FOUND"})

    def do_POST(self) -> None:
        if self.path == "/activate":
            self._handle_activate()
        elif self.path == "/validate":
            self._handle_validate()
        elif self.path == "/admin/revoke":
            self._handle_admin_revoke()
        else:
            _json_response(self, 404, {"ok": False, "error": "NOT_FOUND"})

    def _admin_auth(self) -> bool:
        token = self.headers.get("X-Admin-Token", "")
        return token == ADMIN_TOKEN

    def _handle_activate(self) -> None:
        try:
            body = _read_json_body(self)
        except json.JSONDecodeError:
            _json_response(self, 400, {"ok": False, "error": "INVALID_JSON"})
            return

        license_key = str(body.get("license_key", "")).strip()
        hardware_id = str(body.get("hardware_id", "")).strip()
        machine_name = str(body.get("machine_name", "")).strip()

        if not license_key or not hardware_id:
            _json_response(self, 400, {"ok": False, "error": "MISSING_FIELDS",
                                       "message": "license_key and hardware_id are required."})
            return

        result = activate(license_key, hardware_id, machine_name)
        if result["ok"]:
            # Sign the license payload for offline validation
            payload = result["license"]
            payload["hardware_id"] = hardware_id
            payload["activated_at"] = datetime.now(timezone.utc).isoformat()
            result["signed_license"] = {
                "payload": payload,
                "signature": _sign(payload),
            }
            _json_response(self, 200, result)
        else:
            code = 409 if result["error"] in ("LICENSE_EXPIRED", "LICENSE_REVOKED",
                                               "MACHINE_LIMIT_REACHED") else 404
            _json_response(self, code, result)

    def _handle_validate(self) -> None:
        try:
            body = _read_json_body(self)
        except json.JSONDecodeError:
            _json_response(self, 400, {"ok": False, "error": "INVALID_JSON"})
            return

        license_key = str(body.get("license_key", "")).strip()
        hardware_id = str(body.get("hardware_id", "")).strip()

        if not license_key or not hardware_id:
            _json_response(self, 400, {"ok": False, "error": "MISSING_FIELDS"})
            return

        result = check_online(license_key, hardware_id)
        if result["ok"]:
            result["signature"] = _sign(result["license"])
        _json_response(self, 200 if result["ok"] else 403, result)

    def _handle_admin_list(self) -> None:
        if not self._admin_auth():
            _json_response(self, 403, {"ok": False, "error": "UNAUTHORIZED"})
            return
        licenses = list_licenses(include_revoked=True)
        _json_response(self, 200, {"ok": True, "licenses": licenses})

    def _handle_admin_revoke(self) -> None:
        if not self._admin_auth():
            _json_response(self, 403, {"ok": False, "error": "UNAUTHORIZED"})
            return
        try:
            body = _read_json_body(self)
        except json.JSONDecodeError:
            _json_response(self, 400, {"ok": False, "error": "INVALID_JSON"})
            return

        license_key = str(body.get("license_key", "")).strip()
        reason = str(body.get("reason", "")).strip()
        if not license_key:
            _json_response(self, 400, {"ok": False, "error": "MISSING_FIELDS"})
            return

        ok = revoke_license(license_key, reason)
        if ok:
            _json_response(self, 200, {"ok": True, "message": f"Revoked: {license_key}"})
        else:
            _json_response(self, 404, {"ok": False, "error": "NOT_FOUND"})


def main() -> None:
    import argparse
    parser = argparse.ArgumentParser(description="Gold AutoTrade License Server")
    parser.add_argument("--host", default="127.0.0.1")
    parser.add_argument("--port", type=int, default=8788)
    args = parser.parse_args()

    init_db()
    server = HTTPServer((args.host, args.port), LicenseHandler)
    print(f"License server listening on {args.host}:{args.port}")
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\nShutting down.")
        server.server_close()


if __name__ == "__main__":
    main()
