// [TSM.ID].[11031972] -- All Rights Reserved. Proprietary & Confidential.
// XCU Omni-Relay v2: API Gateway + WebSocket Sync Engine
// Port 4000 — Sovereign XCU API Layer (connects to xcu_iam PostgreSQL)

require('dotenv').config();
const { WebSocketServer } = require('ws');
const http = require('http');
const { Pool } = require('pg');
const fs = require('fs');
const { exec } = require('child_process');

const PORT = process.env.PORT || 4000;

// Connect to XCU's own PostgreSQL (xcu_iam), NOT jumpadb
const pool = new Pool({
  host: process.env.XCU_DB_HOST || '127.0.0.1',
  port: process.env.XCU_DB_PORT || 5432,
  database: 'xcu_iam',
  user: process.env.XCU_DB_USER || 'postgres',
  password: process.env.XCU_DB_PASSWORD || '',
});

pool.on('error', (err) => {
  console.error('[XCU API] PostgreSQL pool error:', err.message);
});

// JUMPA DB pool for ONE-WAY PUSH bridge
const jumpaPool = new Pool({
  host: process.env.JUMPA_DB_HOST || '127.0.0.1',
  port: process.env.JUMPA_DB_PORT || 5432,
  database: 'jumpadb',
  user: process.env.JUMPA_DB_USER || 'jumpa_admin',
  password: process.env.JUMPA_DB_PASSWORD || 'JumpaS3cur3!@#',
});

// ═══════════════════════════════════════
// HTTP REST API
// ═══════════════════════════════════════
const parseBody = (req) => new Promise((resolve, reject) => {
  let body = '';
  req.on('data', chunk => body += chunk);
  req.on('end', () => {
    try { resolve(body ? JSON.parse(body) : {}); }
    catch (e) { reject(e); }
  });
});

const sendJson = (res, status, data) => {
  res.writeHead(status, {
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization',
  });
  res.end(JSON.stringify(data));
};

const server = http.createServer(async (req, res) => {
  // CORS preflight
  if (req.method === 'OPTIONS') {
    res.writeHead(204, {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    });
    return res.end();
  }

  const url = new URL(req.url, `http://localhost:${PORT}`);
  const path = url.pathname;

  try {
    // ─── GET /api/v1/modules ───
    if (path === '/api/v1/modules' && req.method === 'GET') {
      const result = await pool.query(
        'SELECT id, name, group_id, group_name, sort_order, is_active FROM modules WHERE is_active = true ORDER BY sort_order'
      );

      // Group by group_id for frontend consumption
      const registry = {};
      for (const row of result.rows) {
        if (!registry[row.group_id]) {
          registry[row.group_id] = {
            name: row.group_name,
            modules: []
          };
        }
        registry[row.group_id].modules.push({
          id: row.id,
          name: `${row.name} (Modul ${row.sort_order})`
        });
      }

      return sendJson(res, 200, registry);
    }

    // ─── POST /api/v1/modules ───
    if (path === '/api/v1/modules' && req.method === 'POST') {
      const body = await parseBody(req);
      const { id, name, group_id, group_name, sort_order } = body;
      if (!id || !name || !group_id || !group_name) {
        return sendJson(res, 400, { error: 'Missing required fields: id, name, group_id, group_name' });
      }
      await pool.query(
        'INSERT INTO modules (id, name, group_id, group_name, sort_order) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (id) DO UPDATE SET name = $2, group_id = $3, group_name = $4, sort_order = $5',
        [id, name, group_id, group_name, sort_order || 0]
      );
      // Also insert into module_prices if not exists
      await pool.query(
        'INSERT INTO module_prices (module_id, price) VALUES ($1, 1500000) ON CONFLICT (module_id) DO NOTHING',
        [id]
      );
      broadcastModuleUpdate();
      return sendJson(res, 201, { status: 'MODULE_CREATED', id });
    }

    // ─── DELETE /api/v1/modules/:id ───
    if (path.startsWith('/api/v1/modules/') && req.method === 'DELETE') {
      const moduleId = path.split('/').pop();
      await pool.query('UPDATE modules SET is_active = false WHERE id = $1', [moduleId]);
      broadcastModuleUpdate();
      return sendJson(res, 200, { status: 'MODULE_DEACTIVATED', id: moduleId });
    }

    // ─── GET /api/v1/tenants ───
    if (path === '/api/v1/tenants' && req.method === 'GET') {
      const result = await pool.query(
        'SELECT tenant_key, tenant_name, base_tier_id, custom_modules, custom_tier_name, status, created_at FROM tenant_allocations ORDER BY created_at DESC'
      );
      return sendJson(res, 200, result.rows);
    }

    // ─── POST /api/v1/tenants ───
    if (path === '/api/v1/tenants' && req.method === 'POST') {
      const body = await parseBody(req);
      const { tenant_key, tenant_name, base_tier_id, custom_modules, custom_tier_name } = body;
      if (!tenant_key || !tenant_name) {
        return sendJson(res, 400, { error: 'Missing required fields: tenant_key, tenant_name' });
      }
      await pool.query(
        `INSERT INTO tenant_allocations (tenant_key, tenant_name, base_tier_id, custom_modules, custom_tier_name)
         VALUES ($1, $2, $3, $4, $5)
         ON CONFLICT (tenant_key) DO UPDATE SET tenant_name = $2, base_tier_id = $3, custom_modules = $4, custom_tier_name = $5, updated_at = now()`,
        [tenant_key, tenant_name, base_tier_id || null, custom_modules || '{}', custom_tier_name || '']
      );
      return sendJson(res, 201, { status: 'TENANT_PROVISIONED', tenant_key });
    }

    // ─── PUT /api/v1/tenants/:key ───
    if (path.startsWith('/api/v1/tenants/') && req.method === 'PUT') {
      const tenantKey = decodeURIComponent(path.split('/api/v1/tenants/')[1]);
      const body = await parseBody(req);
      const { tenant_name, base_tier_id, custom_modules, custom_tier_name, status } = body;
      await pool.query(
        `UPDATE tenant_allocations SET
          tenant_name = COALESCE($2, tenant_name),
          base_tier_id = $3,
          custom_modules = COALESCE($4, custom_modules),
          custom_tier_name = COALESCE($5, custom_tier_name),
          status = COALESCE($6, status),
          updated_at = now()
         WHERE tenant_key = $1`,
        [tenantKey, tenant_name, base_tier_id || null, custom_modules || '{}', custom_tier_name || '', status]
      );
      return sendJson(res, 200, { status: 'TENANT_UPDATED', tenant_key: tenantKey });
    }

    // ─── DELETE /api/v1/tenants/:key ───
    if (path.startsWith('/api/v1/tenants/') && req.method === 'DELETE') {
      const tenantKey = decodeURIComponent(path.split('/api/v1/tenants/')[1]);
      await pool.query('DELETE FROM tenant_allocations WHERE tenant_key = $1', [tenantKey]);
      return sendJson(res, 200, { status: 'TENANT_REVOKED', tenant_key: tenantKey });
    }

    // ─── GET /api/v1/tiers ───
    if (path === '/api/v1/tiers' && req.method === 'GET') {
      const result = await pool.query('SELECT * FROM tiers WHERE is_active = true ORDER BY price');
      return sendJson(res, 200, result.rows);
    }

    // ─── GET /api/v1/module-prices ───
    if (path === '/api/v1/module-prices' && req.method === 'GET') {
      const result = await pool.query('SELECT module_id, price FROM module_prices ORDER BY module_id');
      const prices = {};
      for (const row of result.rows) {
        prices[row.module_id] = Number(row.price);
      }
      return sendJson(res, 200, prices);
    }

    // ─── POST /api/v1/bridge/sync ── ONE-WAY PUSH to JUMPA ───
    if (path === '/api/v1/bridge/sync' && req.method === 'POST') {
      const body = await parseBody(req);
      const { tenant_key } = body;

      if (!tenant_key) {
        return sendJson(res, 400, { error: 'Missing tenant_key' });
      }

      // 1. Get tenant's allocated modules
      const tenantResult = await pool.query(
        'SELECT base_tier_id, custom_modules FROM tenant_allocations WHERE tenant_key = $1',
        [tenant_key]
      );

      if (tenantResult.rows.length === 0) {
        return sendJson(res, 404, { error: 'Tenant not found' });
      }

      const tenant = tenantResult.rows[0];
      let activeModules = tenant.custom_modules || [];

      // If tenant uses a base tier, get modules from tier
      if (tenant.base_tier_id) {
        const tierResult = await pool.query('SELECT modules FROM tiers WHERE id = $1', [tenant.base_tier_id]);
        if (tierResult.rows.length > 0) {
          activeModules = tierResult.rows[0].modules;
        }
      }

      // 2. Get bridge mappings for these modules
      const bridgeResult = await pool.query(
        'SELECT xcu_module_id, jumpa_feature_key FROM module_feature_bridge WHERE xcu_module_id = ANY($1)',
        [activeModules]
      );

      const activeFeatureKeys = bridgeResult.rows.map(r => r.jumpa_feature_key);

      // 3. ONE-WAY PUSH: Update jumpadb.system_features
      // Activate features that are in the bridge mapping
      if (activeFeatureKeys.length > 0) {
        await jumpaPool.query(
          `UPDATE system_features SET default_state = 'GRANTED' WHERE key = ANY($1)`,
          [activeFeatureKeys]
        );
      }

      // Get ALL bridge keys to know which to disable
      const allBridgeResult = await pool.query(
        'SELECT DISTINCT jumpa_feature_key FROM module_feature_bridge'
      );
      const allBridgeKeys = allBridgeResult.rows.map(r => r.jumpa_feature_key);
      const disabledKeys = allBridgeKeys.filter(k => !activeFeatureKeys.includes(k));

      if (disabledKeys.length > 0) {
        await jumpaPool.query(
          `UPDATE system_features SET default_state = 'UPSELL' WHERE key = ANY($1)`,
          [disabledKeys]
        );
      }

      console.log(`[XCU BRIDGE] ONE-WAY PUSH to JUMPA: ${activeFeatureKeys.length} GRANTED, ${disabledKeys.length} UPSELL`);

      return sendJson(res, 200, {
        status: 'BRIDGE_SYNC_COMPLETE',
        granted: activeFeatureKeys.length,
        disabled: disabledKeys.length,
        tenant_key
      });
    }

    // ─── GET /api/v1/bridge/mappings ───
    if (path === '/api/v1/bridge/mappings' && req.method === 'GET') {
      const result = await pool.query(
        'SELECT xcu_module_id, jumpa_feature_key FROM module_feature_bridge ORDER BY xcu_module_id'
      );
      return sendJson(res, 200, result.rows);
    }

    // ─── POST /api/v1/bridge/mappings ───
    if (path === '/api/v1/bridge/mappings' && req.method === 'POST') {
      const body = await parseBody(req);
      const { xcu_module_id, jumpa_feature_key } = body;
      if (!xcu_module_id || !jumpa_feature_key) {
        return sendJson(res, 400, { error: 'Missing xcu_module_id or jumpa_feature_key' });
      }
      await pool.query(
        'INSERT INTO module_feature_bridge (xcu_module_id, jumpa_feature_key) VALUES ($1, $2) ON CONFLICT DO NOTHING',
        [xcu_module_id, jumpa_feature_key]
      );
      return sendJson(res, 201, { status: 'MAPPING_CREATED', xcu_module_id, jumpa_feature_key });
    }

    // ═══════════════════════════════════════
    // MONITORING API (Phase 1: Tenant Monitor)
    // ═══════════════════════════════════════

    // ─── GET /api/v1/monitor/health ── PM2 service status ───
    if (path === '/api/v1/monitor/health' && req.method === 'GET') {
      return new Promise((resolve) => {
        exec('pm2 jlist 2>/dev/null', (err, stdout) => {
          if (err) return resolve(sendJson(res, 200, { services: [], error: err.message }));
          try {
            const list = JSON.parse(stdout);
            const services = list.map(p => ({
              name: p.name,
              pid: p.pid,
              status: p.pm2_env.status,
              uptime: p.pm2_env.pm_uptime,
              restarts: p.pm2_env.restart_time,
              memory: p.monit ? p.monit.memory : 0,
              cpu: p.monit ? p.monit.cpu : 0,
            }));
            resolve(sendJson(res, 200, { services, ts: Date.now() }));
          } catch (e) {
            resolve(sendJson(res, 200, { services: [], error: e.message }));
          }
        });
      });
    }

    // ─── GET /api/v1/monitor/traffic ── Nginx access log summary ───
    if (path === '/api/v1/monitor/traffic' && req.method === 'GET') {
      const search = url.searchParams.get('q') || '';
      const since = url.searchParams.get('since') || '';
      return new Promise((resolve) => {
        let cmd = 'tail -200 /var/log/nginx/access.log 2>/dev/null';
        exec(cmd, (err, stdout) => {
          if (err) return resolve(sendJson(res, 200, { entries: [], stats: {} }));
          let lines = stdout.trim().split('\n').filter(Boolean);
          
          // Filter by search query
          if (search) {
            const q = search.toLowerCase();
            lines = lines.filter(l => l.toLowerCase().includes(q));
          }
          // Filter by datetime
          if (since) {
            lines = lines.filter(l => {
              const m = l.match(/\[([^\]]+)\]/);
              return m ? new Date(m[1].replace(':', ' ').replace(/\//g, '-')) >= new Date(since) : true;
            });
          }
          
          // Stats
          const stats = { total: lines.length, '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0 };
          lines.forEach(l => {
            const m = l.match(/"\s(\d{3})\s/);
            if (m) {
              const code = parseInt(m[1]);
              if (code < 300) stats['2xx']++;
              else if (code < 400) stats['3xx']++;
              else if (code < 500) stats['4xx']++;
              else stats['5xx']++;
            }
          });
          
          // Return last 10
          const entries = lines.slice(-10).reverse();
          resolve(sendJson(res, 200, { entries, stats, ts: Date.now() }));
        });
      });
    }

    // ─── GET /api/v1/monitor/logs/:service ── PM2 logs ───
    if (path.startsWith('/api/v1/monitor/logs/') && req.method === 'GET') {
      const service = path.split('/api/v1/monitor/logs/')[1];
      const search = url.searchParams.get('q') || '';
      const lines = parseInt(url.searchParams.get('lines')) || 10;
      return new Promise((resolve) => {
        exec(`pm2 logs ${service} --nostream --lines ${Math.min(lines, 100)} 2>/dev/null`, (err, stdout) => {
          if (err) return resolve(sendJson(res, 200, { logs: [], error: err.message }));
          let logLines = stdout.trim().split('\n').filter(Boolean);
          if (search) {
            const q = search.toLowerCase();
            logLines = logLines.filter(l => l.toLowerCase().includes(q));
          }
          resolve(sendJson(res, 200, { logs: logLines.slice(-10), service, ts: Date.now() }));
        });
      });
    }

    // ─── GET /api/v1/monitor/intrusions ── Detect illegal access ───
    if (path === '/api/v1/monitor/intrusions' && req.method === 'GET') {
      return new Promise((resolve) => {
        const cmds = [
          // 1. Failed auth from Gatekeeper
          'grep -i "Auth Failed\\|Invalid Key\\|TOKEN_EXPIRED" /root/.pm2/logs/xcu-gatekeeper-error.log 2>/dev/null | tail -20',
          // 2. 401/403 from Nginx
          'grep -E "\\\" (401|403) " /var/log/nginx/access.log 2>/dev/null | tail -20',
          // 3. Suspicious patterns (scanning/probing)
          'grep -E "\\\" (404) .*(/admin|/wp-|/phpmyadmin|/.env|/config)" /var/log/nginx/access.log 2>/dev/null | tail -10'
        ];
        
        exec(cmds.join(' && echo "---SEPARATOR---" && '), { maxBuffer: 1024 * 1024 }, (err, stdout) => {
          const sections = (stdout || '').split('---SEPARATOR---');
          const intrusions = [];
          
          // Parse failed auth
          (sections[0] || '').trim().split('\n').filter(Boolean).forEach(line => {
            intrusions.push({ type: 'AUTH_FAILED', severity: 'HIGH', raw: line.substring(0, 200), ts: Date.now() });
          });
          
          // Parse 401/403
          (sections[1] || '').trim().split('\n').filter(Boolean).forEach(line => {
            const ipMatch = line.match(/^(\d+\.\d+\.\d+\.\d+)/);
            const pathMatch = line.match(/"[A-Z]+ ([^ ]+)/);
            const isCrossTenant = pathMatch && (pathMatch[1].includes('/xcu-api') || pathMatch[1].includes('/ws/gatekeeper'));
            intrusions.push({
              type: isCrossTenant ? 'CROSS_TENANT_JUMP' : 'UNAUTHORIZED',
              severity: isCrossTenant ? 'CRITICAL' : 'MEDIUM',
              ip: ipMatch ? ipMatch[1] : 'unknown',
              target: pathMatch ? pathMatch[1] : 'unknown',
              raw: line.substring(0, 200),
              ts: Date.now()
            });
          });
          
          // Parse probes
          (sections[2] || '').trim().split('\n').filter(Boolean).forEach(line => {
            const ipMatch = line.match(/^(\d+\.\d+\.\d+\.\d+)/);
            intrusions.push({
              type: 'API_PROBE',
              severity: 'LOW',
              ip: ipMatch ? ipMatch[1] : 'unknown',
              raw: line.substring(0, 200),
              ts: Date.now()
            });
          });
          
          // Brute force detection: same IP > 3 failures
          const ipCounts = {};
          intrusions.filter(i => i.type === 'AUTH_FAILED' || i.type === 'UNAUTHORIZED').forEach(i => {
            if (i.ip) { ipCounts[i.ip] = (ipCounts[i.ip] || 0) + 1; }
          });
          const bruteForceIPs = Object.entries(ipCounts).filter(([_, c]) => c > 3).map(([ip]) => ip);
          
          resolve(sendJson(res, 200, { 
            intrusions: intrusions.slice(-30), 
            bruteForceIPs,
            total: intrusions.length,
            ts: Date.now() 
          }));
        });
      });
    }

    // ─── POST /api/v1/monitor/block-ip ── Manual IP block (default OFF) ───
    if (path === '/api/v1/monitor/block-ip' && req.method === 'POST') {
      const body = await parseBody(req);
      const { ip, action } = body; // action: 'block' or 'unblock'
      if (!ip) return sendJson(res, 400, { error: 'Missing IP' });
      return new Promise((resolve) => {
        // Always remove first to prevent duplicates, then add if blocking
        const cleanCmd = `while iptables -D INPUT -s ${ip} -j DROP 2>/dev/null; do :; done`;
        const cmd = action === 'unblock'
          ? `${cleanCmd}; echo "UNBLOCKED ${ip}"`
          : `${cleanCmd}; iptables -A INPUT -s ${ip} -j DROP; echo "BLOCKED ${ip}"`;
        exec(cmd, (err, stdout) => {
          resolve(sendJson(res, 200, { status: stdout.trim(), ip, action }));
        });
      });
    }

    // ─── GET /api/v1/monitor/blocked ── List blocked IPs ───
    if (path === '/api/v1/monitor/blocked' && req.method === 'GET') {
      return new Promise((resolve) => {
        exec('iptables -L INPUT -n --line-numbers 2>/dev/null | grep DROP', (err, stdout) => {
          const blocked = (stdout || '').trim().split('\n').filter(Boolean).map(line => {
            const parts = line.trim().split(/\s+/);
            return { num: parts[0], ip: parts[4] || 'unknown', target: parts[1] || 'DROP' };
          }).filter(b => b.ip !== 'unknown' && b.ip !== '0.0.0.0/0');
          resolve(sendJson(res, 200, { blocked, total: blocked.length, ts: Date.now() }));
        });
      });
    }

    // ─── Health check ───
    if (path === '/' || path === '/health') {
      return sendJson(res, 200, {
        service: 'XCU Omni-Relay API Gateway v2',
        status: 'ONLINE',
        sovereign: '[TSM.ID].[11031972]',
        port: PORT,
        database: 'xcu_iam (PostgreSQL)',
        bridge: 'ONE-WAY PUSH → JUMPA'
      });
    }

    // 404
    sendJson(res, 404, { error: 'NOT_FOUND' });

  } catch (err) {
    console.error('[XCU API] Error:', err.message);
    sendJson(res, 500, { error: err.message });
  }
});

// ═══════════════════════════════════════
// WebSocket Sync (for Command Center live updates)
// ═══════════════════════════════════════
const wss = new WebSocketServer({ server });

const broadcastModuleUpdate = async () => {
  try {
    const result = await pool.query(
      'SELECT id, name, group_id, group_name, sort_order, is_active FROM modules WHERE is_active = true ORDER BY sort_order'
    );
    const registry = {};
    for (const row of result.rows) {
      if (!registry[row.group_id]) {
        registry[row.group_id] = { name: row.group_name, modules: [] };
      }
      registry[row.group_id].modules.push({
        id: row.id,
        name: `${row.name} (Modul ${row.sort_order})`
      });
    }
    const msg = JSON.stringify({ type: 'SYNC_MODULES', payload: registry });
    wss.clients.forEach(client => {
      if (client.readyState === 1) client.send(msg);
    });
  } catch (err) {
    console.error('[XCU WS] Broadcast error:', err.message);
  }
};

wss.on('connection', async (ws) => {
  console.log('[XCU API] Command Center Connected.');
  // Send current module registry immediately
  await broadcastModuleUpdate();
  
  ws.on('message', async (message) => {
    try {
      const data = JSON.parse(message);
      // Legacy support: handle UPDATE_PRICING from old Command Center
      if (data.type === 'UPDATE_PRICING') {
        console.log('[XCU API] Received legacy pricing update from Command Center.');
        // Persist tiers to DB if provided
        if (data.payload.tiers) {
          for (const tier of data.payload.tiers) {
            await pool.query(
              `UPDATE tiers SET name = $2, price = $3, description = $4, modules = $5 WHERE id = $1`,
              [tier.id, tier.name, tier.price, tier.description || '', tier.modules || []]
            );
          }
        }
        // Persist module prices if provided
        if (data.payload.modulePrices) {
          for (const [modId, price] of Object.entries(data.payload.modulePrices)) {
            await pool.query(
              'UPDATE module_prices SET price = $2, updated_at = now() WHERE module_id = $1',
              [modId, Number(price)]
            );
          }
        }
        // Broadcast to all connected clients
        const broadcastData = JSON.stringify({ type: 'SYNC_STATE', payload: data.payload });
        wss.clients.forEach(client => {
          if (client.readyState === 1) client.send(broadcastData);
        });
      }
    } catch (err) {
      console.error('[XCU API] WS message error:', err.message);
    }
  });

  ws.on('close', () => {
    console.log('[XCU API] Command Center Disconnected.');
  });
});

// ═══════════════════════════════════════
server.listen(PORT, () => {
  console.log(`[XCU API GATEWAY] Listening on http://localhost:${PORT}`);
  console.log(`[XCU API GATEWAY] Database: xcu_iam (PostgreSQL - INDEPENDENT)`);
  console.log(`[XCU API GATEWAY] Bridge: ONE-WAY PUSH → JUMPA`);
  console.log(`[XCU API GATEWAY] Sovereign: [TSM.ID].[11031972]`);
});
