#!/usr/bin/env python3
"""
FES Bridge Microservice - stdlib-only HTTP API on port 3005.

Uses ONLY Python stdlib (http.server) - no Flask, no external deps.
6 endpoints: /health, /fes/process, /fes/dry-run, /gallery/index, /wcp/catalog, /primitives/<name>

Run: python3 fes_bridge_microservice.py
Port: 3005
"""
import json
import os
import sys
import subprocess
import logging
import tempfile
from pathlib import Path
from datetime import datetime, timezone
from urllib.parse import urlparse, parse_qs
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
    stream=sys.stdout,
)
log = logging.getLogger('fes-bridge-microservice')

# Paths
FES_BRIDGE_DIR = os.path.dirname(os.path.abspath(__file__))
FES_BRIDGE_PY = os.path.join(FES_BRIDGE_DIR, 'fes_bridge.py')
WCP_CATALOG = '/workspace/freshvibe-cms/wcp/wcp-catalog.json'
GALLERY_DIR = os.path.expanduser('~/avidtech6/fv-module-gallery/cms-widgets/')
GALLERY_INDEX = os.path.join(GALLERY_DIR, 'index.json')
PORT = 3005


def load_wcp_catalog():
    """Load the WCP catalog. Returns (catalog_dict, error_string)."""
    if not Path(WCP_CATALOG).exists():
        return None, f"WCP catalog not found: {WCP_CATALOG}"
    try:
        with open(WCP_CATALOG) as f:
            return json.load(f), None
    except Exception as e:
        return None, f"WCP catalog parse error: {e}"


def call_fes_bridge(*args, stdin_data=None, timeout=300):
    """Call fes_bridge.py as a subprocess. Returns (stdout, stderr, returncode)."""
    cmd = [sys.executable, FES_BRIDGE_PY] + list(args)
    log.info(f"Calling: {' '.join(cmd)} (stdin={bool(stdin_data)}, timeout={timeout})")
    try:
        result = subprocess.run(
            cmd, capture_output=True, text=True,
            input=stdin_data, timeout=timeout,
        )
        return result.stdout, result.stderr, result.returncode
    except subprocess.TimeoutExpired:
        return '', f"Timeout after {timeout}s", -1
    except Exception as e:
        return '', str(e), -1


class FESBridgeHandler(BaseHTTPRequestHandler):
    """HTTP request handler for fes-bridge microservice."""

    def log_message(self, format, *args):
        # Override to use Python logging
        log.info(f"{self.client_address[0]} - {format % args}")

    def _send_json(self, status, data):
        body = json.dumps(data, indent=2).encode('utf-8')
        self.send_response(status)
        self.send_header('Content-Type', 'application/json; charset=utf-8')
        self.send_header('Content-Length', str(len(body)))
        # CORS
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
        self.send_header('Access-Control-Allow-Headers', 'Content-Type')
        self.end_headers()
        self.wfile.write(body)

    def _read_body(self):
        """Read and parse JSON body. Returns (data, error)."""
        content_length = self.headers.get('Content-Length', '')
        if not content_length:
            return None, "Content-Length header required"
        try:
            length = int(content_length)
        except:
            return None, f"Invalid Content-Length: {content_length}"
        if length > 50 * 1024 * 1024:  # 50 MB cap
            return None, f"Body too large: {length} bytes (max 50MB)"
        try:
            raw = self.rfile.read(length)
            data = json.loads(raw)
            return data, None
        except json.JSONDecodeError as e:
            return None, f"Invalid JSON: {e}"
        except Exception as e:
            return None, str(e)

    def do_OPTIONS(self):
        self._send_json(200, {"ok": True})

    def do_GET(self):
        url = urlparse(self.path)
        path = url.path.rstrip('/')
        qs = parse_qs(url.query)

        if path == '' or path == '/':
            return self._send_json(200, {
                "service": "fes-bridge-microservice",
                "version": "1.0.0",
                "endpoints": [
                    "GET  /health",
                    "POST /fes/process",
                    "POST /fes/dry-run",
                    "GET  /gallery/index",
                    "GET  /wcp/catalog",
                    "GET  /primitives/<name>",
                ],
            })

        if path == '/health':
            return self._handle_health()

        if path == '/gallery/index':
            return self._handle_gallery_index()

        if path == '/wcp/catalog':
            return self._handle_wcp_catalog()

        if path.startswith('/primitives/'):
            name = path[len('/primitives/'):]
            return self._handle_primitive(name)

        return self._send_json(404, {"error": f"Not found: {path}"})

    def do_POST(self):
        url = urlparse(self.path)
        path = url.path.rstrip('/')

        if path == '/fes/process':
            return self._handle_fes_process()

        if path == '/fes/dry-run':
            return self._handle_fes_dry_run()

        return self._send_json(404, {"error": f"Not found: {path}"})

    def _handle_health(self):
        catalog, err = load_wcp_catalog()
        if err:
            return self._send_json(500, {"status": "error", "error": err})
        n_primitives = len(catalog.get('primitives', {}))
        wcp_version = catalog.get('version', 'unknown')
        fes_bridge_ok = Path(FES_BRIDGE_PY).exists()
        output_dir_exists = Path(GALLERY_DIR).exists()
        return self._send_json(200, {
            "status": "ok" if fes_bridge_ok and output_dir_exists else "degraded",
            "primitives": n_primitives,
            "wcp_version": wcp_version,
            "fes_bridge_path": FES_BRIDGE_PY,
            "fes_bridge_exists": fes_bridge_ok,
            "output_dir": GALLERY_DIR,
            "output_dir_exists": output_dir_exists,
            "generated": catalog.get('generated', 'unknown'),
            "service_started": datetime.now(timezone.utc).isoformat(),
        })

    def _handle_fes_process(self):
        data, err = self._read_body()
        if err:
            return self._send_json(400, {"status": "error", "error": err})
        if 'widgets' not in data:
            return self._send_json(400, {"status": "error", "error": "Body must have 'widgets' key"})
        widgets = data['widgets']
        if not isinstance(widgets, list):
            return self._send_json(400, {"status": "error", "error": "'widgets' must be a list"})
        output_dir = data.get('output_dir') or GALLERY_DIR

        with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
            json.dump(widgets, f)
            widgets_file = f.name

        try:
            stdout, stderr, returncode = call_fes_bridge(
                'run', widgets_file, output_dir, timeout=600,
            )
            return self._send_json(
                200 if returncode == 0 else 500,
                {
                    "status": "success" if returncode == 0 else "error",
                    "returncode": returncode,
                    "stdout_tail": (stdout or '')[-2000:],
                    "stderr_tail": (stderr or '')[-1000:],
                    "widget_count": len(widgets),
                    "output_dir": output_dir,
                },
            )
        finally:
            try:
                os.unlink(widgets_file)
            except:
                pass

    def _handle_fes_dry_run(self):
        data, err = self._read_body()
        if err:
            return self._send_json(400, {"status": "error", "error": err})
        if 'widgets' not in data:
            return self._send_json(400, {"status": "error", "error": "Body must have 'widgets' key"})
        widgets = data['widgets']
        if not isinstance(widgets, list):
            return self._send_json(400, {"status": "error", "error": "'widgets' must be a list"})

        with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
            json.dump(widgets, f)
            widgets_file = f.name

        try:
            stdout, stderr, returncode = call_fes_bridge(
                'dry-run', widgets_file, timeout=60,
            )
            return self._send_json(200, {
                "valid": returncode == 0,
                "returncode": returncode,
                "stdout_tail": (stdout or '')[-2000:],
                "stderr_tail": (stderr or '')[-1000:],
                "widget_count": len(widgets),
            })
        finally:
            try:
                os.unlink(widgets_file)
            except:
                pass

    def _handle_gallery_index(self):
        if not Path(GALLERY_INDEX).exists():
            return self._send_json(404, {
                "status": "no_index",
                "message": f"index.json not found at {GALLERY_INDEX}. Run /fes/process first.",
                "path": GALLERY_INDEX,
            })
        try:
            with open(GALLERY_INDEX) as f:
                data = json.load(f)
            return self._send_json(200, data)
        except Exception as e:
            return self._send_json(500, {"status": "error", "error": str(e)})

    def _handle_wcp_catalog(self):
        catalog, err = load_wcp_catalog()
        if err:
            return self._send_json(500, {"status": "error", "error": err})
        return self._send_json(200, catalog)

    def _handle_primitive(self, name):
        catalog, err = load_wcp_catalog()
        if err:
            return self._send_json(500, {"status": "error", "error": err})
        primitives = catalog.get('primitives', {})
        if name in primitives:
            return self._send_json(200, primitives[name])
        name_lower = name.lower()
        for k, v in primitives.items():
            if k.lower() == name_lower:
                return self._send_json(200, v)
        for k, v in primitives.items():
            if v.get('name', '').lower() == name_lower:
                return self._send_json(200, v)
        return self._send_json(404, {
            "status": "not_found",
            "message": f"Primitive '{name}' not found",
            "available": list(primitives.keys()),
        })


def main():
    log.info(f"Starting fes-bridge-microservice on port {PORT}")
    log.info(f"  fes_bridge.py: {FES_BRIDGE_PY}")
    log.info(f"  WCP catalog: {WCP_CATALOG}")
    log.info(f"  Gallery dir: {GALLERY_DIR}")
    server = ThreadingHTTPServer(('0.0.0.0', PORT), FESBridgeHandler)
    log.info(f"Listening on http://0.0.0.0:{PORT}")
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        log.info("Shutting down")
        server.shutdown()


if __name__ == '__main__':
    main()
