/**
 * Enhanced Multi-Cloud Orchestration Portal
 * TypeScript/Node.js Express server
 */

import express, { Request, Response, NextFunction } from 'express';
import cors from 'cors';
import path from 'path';
import fs from 'fs';
import http from 'http';
import { Server as SocketIOServer } from 'socket.io';
import { ConfigManager } from './config';
import { DatabaseManager } from './database';
import { Environment, DeploymentRequest, Deployment, Alert, Cost } from './types';
import { requireAdmin, createSession, getClientIp, AuthRequest } from './middleware/auth';
import { MonitoringService } from './services/monitoring';
import { appendAudit } from './services/central-audit';

const app = express();
app.use(cors());
app.use(express.json());

// Serve static files from client build (production) or static directory
const isProduction = process.env.NODE_ENV === 'production';
if (isProduction) {
  app.use(express.static(path.join(__dirname, '../client')));
} else {
  app.use(express.static(path.join(__dirname, '../static')));
}

// Set up view engine (EJS for templates - fallback)
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, '../templates'));

// Initialize managers
const config = new ConfigManager();
const db = new DatabaseManager(config.getDbFile());
const monitoring = new MonitoringService(db);

// Helper function to group environments by provider
function groupByProvider(environments: Environment[]): Record<string, Environment[]> {
  const grouped: Record<string, Environment[]> = {};
  for (const env of environments) {
    const provider = env.provider || 'unknown';
    if (!grouped[provider]) {
      grouped[provider] = [];
    }
    grouped[provider].push(env);
  }
  return grouped;
}

// ============================================
// ROUTES
// ============================================

// Main dashboard - serve Vue/React SPA in production, EJS template in development
app.get('/', (_req: Request, res: Response): void => {
  if (isProduction) {
    // Serve the built Vue/React app
    res.sendFile(path.join(__dirname, '../client/index.html'));
  } else {
    // Fallback to EJS template for development
    const environments = config.loadEnvironments();
    const byProvider = groupByProvider(environments);

    const envStatuses: Record<string, any> = {};
    for (const env of environments) {
      if (env.enabled) {
        envStatuses[env.name] = config.getDeploymentStatus(env.name, db);
      }
    }

    const alerts = db.getAlerts({ unacknowledged_only: true });
    const recentDeployments = db.getDeployments({ limit: 10 });

    const totalEnvironments = environments.length;
    const enabledCount = environments.filter(e => e.enabled).length;
    const totalProviders = Object.keys(byProvider).length;

    res.render('dashboard', {
      environments,
      by_provider: byProvider,
      env_statuses: envStatuses,
      alerts,
      recent_deployments: recentDeployments,
      total_environments: totalEnvironments,
      enabled_count: enabledCount,
      total_providers: totalProviders
    });
  }
});

// API: Get all environments
app.get('/api/environments', (_req: Request, res: Response) => {
  const environments = config.loadEnvironments();
  res.json(environments);
});

// API: Get specific environment
app.get('/api/environments/:name', (req: Request, res: Response): void => {
  const { name } = req.params;
  const env = config.getEnvironmentByName(name);

  if (!env) {
    res.status(404).json({ error: 'Environment not found' });
    return;
  }

  const status = config.getDeploymentStatus(name, db);
  const metrics = db.getMetrics(name, 24);
  const alerts = db.getAlerts({ environment: name, unacknowledged_only: true });
  const costs = db.getCosts({ environment: name, days: 30 });

  res.json({
    config: env,
    status,
    metrics,
    alerts,
    costs
  });
});

// API: Deploy to environment
app.post('/api/environments/:name/deploy', (req: Request, res: Response): void => {
  const { name } = req.params;
  const body: DeploymentRequest = req.body || {};

  const env = config.getEnvironmentByName(name);
  if (!env) {
    res.status(404).json({ error: 'Environment not found' });
    return;
  }

  if (!env.enabled) {
    res.status(400).json({ error: 'Environment is disabled' });
    return;
  }

  const strategy = body.strategy || 'blue-green';
  const version = body.version || 'latest';
  const now = new Date();
  const deploymentId = `${name}-${now.toISOString().replace(/[-:T.]/g, '').slice(0, 14)}`;
  const logFile = path.join(config.getDeploymentLogDir(), `${deploymentId}.log`);

  // Log deployment request
  const logContent = [
    `Deployment requested for ${name} at ${now.toISOString()}`,
    `Strategy: ${strategy}`,
    `Version: ${version}`,
    `Environment config: ${JSON.stringify(env, null, 2)}`
  ].join('\n');

  fs.writeFileSync(logFile, logContent);

  // Store in database
  const deployment: Deployment = {
    id: deploymentId,
    environment: name,
    status: 'queued',
    started_at: now.toISOString(),
    triggered_by: body.triggered_by || 'api',
    strategy,
    version,
    logs_path: logFile
  };

  db.createDeployment(deployment);

  res.json({
    deployment_id: deploymentId,
    status: 'queued',
    environment: name,
    strategy,
    version,
    message: 'Deployment queued successfully'
  });
});

// API: Get environment status
app.get('/api/environments/:name/status', (req: Request, res: Response): void => {
  const { name } = req.params;
  const status = config.getDeploymentStatus(name, db);
  res.json(status);
});

// API: Get environment metrics
app.get('/api/environments/:name/metrics', (req: Request, res: Response): void => {
  const { name } = req.params;
  const hours = parseInt(req.query.hours as string) || 24;
  const metrics = db.getMetrics(name, hours);
  res.json(metrics);
});

// API: Get environment alerts
app.get('/api/environments/:name/alerts', (req: Request, res: Response) => {
  const { name } = req.params;
  const unacknowledgedOnly = req.query.unacknowledged_only === 'true';
  const alerts = db.getAlerts({ environment: name, unacknowledged_only: unacknowledgedOnly });
  res.json(alerts);
});

// API: Acknowledge alert
app.post('/api/alerts/:id/acknowledge', (req: Request, res: Response) => {
  const alertId = parseInt(req.params.id);
  db.acknowledgeAlert(alertId);
  res.json({ message: 'Alert acknowledged' });
});

// API: Get costs
app.get('/api/costs', (req: Request, res: Response): void => {
  const environment = req.query.environment as string | undefined;
  const days = parseInt(req.query.days as string) || 30;
  const costs = db.getCosts({ environment, days });
  res.json(costs);
});

// API: Get deployments
app.get('/api/deployments', (req: Request, res: Response): void => {
  const environment = req.query.environment as string | undefined;
  const status = req.query.status as string | undefined;
  const limit = parseInt(req.query.limit as string) || 50;
  const deployments = db.getDeployments({ environment, status, limit });
  res.json(deployments);
});

// API: Get deployment logs
app.get('/api/deployments/:id/logs', (req: Request, res: Response): void => {
  const { id } = req.params;
  const logsPath = db.getDeploymentLogsPath(id);

  if (!logsPath || !fs.existsSync(logsPath)) {
    res.status(404).json({ error: 'Logs not found' });
    return;
  }

  const logs = fs.readFileSync(logsPath, 'utf-8');
  res.json({ logs });
});

// Environment detail page - serve SPA in production
app.get('/environment/:name', (req: Request, res: Response): void => {
  if (isProduction) {
    res.sendFile(path.join(__dirname, '../client/index.html'));
  } else {
    const { name } = req.params;
    const env = config.getEnvironmentByName(name);

    if (!env) {
      res.status(404).send('Environment not found');
      return;
    }

    const status = config.getDeploymentStatus(name, db);
    const metrics = db.getMetrics(name, 168); // 7 days
    const alerts = db.getAlerts({ environment: name });
    const costs = db.getCosts({ environment: name, days: 30 });
    const deployments = db.getDeployments({ environment: name, limit: 20 });

    res.render('environment_detail', {
      environment: env,
      status,
      metrics,
      alerts,
      costs,
      deployments
    });
  }
});

// Health dashboard - serve SPA in production
app.get('/dashboard/health', (_req: Request, res: Response): void => {
  if (isProduction) {
    res.sendFile(path.join(__dirname, '../client/index.html'));
  } else {
    const environments = config.loadEnvironments();
    const healthData: any[] = [];

    for (const env of environments) {
      if (env.enabled) {
        const status = config.getDeploymentStatus(env.name, db);
        healthData.push({
          name: env.name,
          provider: env.provider,
          region: env.region,
          status,
          health: status.cluster_health
        });
      }
    }

    res.render('health_dashboard', { health_data: healthData });
  }
});

// Cost dashboard - serve SPA in production
app.get('/dashboard/costs', (_req: Request, res: Response): void => {
  if (isProduction) {
    res.sendFile(path.join(__dirname, '../client/index.html'));
  } else {
    const costs = db.getCosts({ days: 90 });

    // Aggregate by provider
    const byProvider: Record<string, number> = {};
    let totalCost = 0;

    for (const cost of costs) {
      const provider = cost.provider;
      if (!byProvider[provider]) {
        byProvider[provider] = 0;
      }
      byProvider[provider] += cost.cost;
      totalCost += cost.cost;
    }

    res.render('cost_dashboard', {
      costs,
      by_provider: byProvider,
      total_cost: totalCost
    });
  }
});

// Seed sample data
function seedSampleData(): void {
  if (db.getMetricsCount() > 0) {
    return; // Data already seeded
  }

  const environments = config.loadEnvironments();
  const now = new Date();

  for (const env of environments.slice(0, 3)) {
    const envName = env.name;

    // Generate sample metrics (24 hours)
    for (let i = 0; i < 24; i++) {
      const timestamp = new Date(now.getTime() - (24 - i) * 60 * 60 * 1000).toISOString();
      db.insertMetric(envName, 'cpu_usage', Math.random() * 60 + 20, timestamp);
      db.insertMetric(envName, 'memory_usage', Math.random() * 55 + 30, timestamp);
    }

    // Generate sample alerts (30% chance)
    if (Math.random() > 0.7) {
      const alert: Omit<Alert, 'id'> = {
        environment: envName,
        severity: Math.random() > 0.5 ? 'warning' : 'error',
        message: `Sample alert for ${envName}`,
        timestamp: now.toISOString()
      };
      db.createAlert(alert);
    }

    // Generate sample costs (30 days)
    for (let i = 0; i < 30; i++) {
      const periodStart = new Date(now.getTime() - (30 - i) * 24 * 60 * 60 * 1000).toISOString();
      const periodEnd = new Date(now.getTime() - (29 - i) * 24 * 60 * 60 * 1000).toISOString();
      const cost: Cost = {
        environment: envName,
        provider: env.provider || 'azure',
        cost: Math.random() * 490 + 10,
        currency: 'USD',
        period_start: periodStart,
        period_end: periodEnd,
        resource_type: 'compute'
      };
      db.insertCost(cost);
    }
  }
}

// ============================================
// ADMIN API ROUTES
// ============================================

// Admin login (simple for now - enhance with proper auth later)
app.post('/api/admin/login', (req: Request, res: Response): void => {
  const { username, password } = req.body;
  
  // Simple hardcoded admin for now (replace with proper auth)
  if (username === 'admin' && (password === process.env.ADMIN_PASSWORD || password === 'admin')) {
    const token = createSession(username);
    res.json({ token, username });
  } else {
    res.status(401).json({ error: 'Invalid credentials' });
  }
});

// Get all service configurations
app.get('/api/admin/services', requireAdmin, (_req: AuthRequest, res: Response): void => {
  try {
    const services = db.getAllServiceConfigs();
    res.json(services);
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch services' });
  }
});

// Get specific service configuration
app.get('/api/admin/services/:name', requireAdmin, (req: AuthRequest, res: Response): void => {
  try {
    const { name } = req.params;
    const service = db.getServiceConfig(name);
    if (!service) {
      res.status(404).json({ error: 'Service not found' });
      return;
    }
    res.json({ service_name: name, ...service });
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch service' });
  }
});

// Update service configuration
app.put('/api/admin/services/:name', requireAdmin, (req: AuthRequest, res: Response): void => {
  try {
    const { name } = req.params;
    const { enabled, config } = req.body;
    const adminUser = req.adminUser || 'unknown';
    const ipAddress = getClientIp(req);

    db.setServiceConfig(name, enabled !== false, config || null, adminUser);
    db.logAdminAction(adminUser, 'update_service', 'service', name, JSON.stringify({ enabled, config }), ipAddress);
    appendAudit({ employeeId: adminUser, action: 'update_service', permission: 'admin:action', resourceType: 'service', resourceId: name, ipAddress, userAgent: req.headers['user-agent'] as string }).catch(() => {});

    // Broadcast real-time update
    broadcastAdminUpdate('service-updated', { service_name: name, enabled, updated_by: adminUser });

    res.json({ success: true, message: `Service ${name} ${enabled ? 'enabled' : 'disabled'}` });
  } catch (error) {
    res.status(500).json({ error: 'Failed to update service' });
  }
});

// Get all provider configurations
app.get('/api/admin/providers', requireAdmin, (_req: AuthRequest, res: Response): void => {
  try {
    const providers = db.getAllProviderConfigs();
    res.json(providers);
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch providers' });
  }
});

// Get specific provider configuration
app.get('/api/admin/providers/:name', requireAdmin, (req: AuthRequest, res: Response): void => {
  try {
    const { name } = req.params;
    const provider = db.getProviderConfig(name);
    if (!provider) {
      res.status(404).json({ error: 'Provider not found' });
      return;
    }
    res.json({ provider_name: name, ...provider });
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch provider' });
  }
});

// Update provider configuration
app.put('/api/admin/providers/:name', requireAdmin, (req: AuthRequest, res: Response): void => {
  try {
    const { name } = req.params;
    const { enabled, config } = req.body;
    const adminUser = req.adminUser || 'unknown';
    const ipAddress = getClientIp(req);

    db.setProviderConfig(name, enabled !== false, config || null, adminUser);
    db.logAdminAction(adminUser, 'update_provider', 'provider', name, JSON.stringify({ enabled, config }), ipAddress);
    appendAudit({ employeeId: adminUser, action: 'update_provider', permission: 'admin:action', resourceType: 'provider', resourceId: name, ipAddress, userAgent: req.headers['user-agent'] as string }).catch(() => {});

    // Broadcast real-time update
    broadcastAdminUpdate('provider-updated', { provider_name: name, enabled, updated_by: adminUser });

    res.json({ success: true, message: `Provider ${name} ${enabled ? 'enabled' : 'disabled'}` });
  } catch (error) {
    res.status(500).json({ error: 'Failed to update provider' });
  }
});

// Get audit logs
app.get('/api/admin/audit-logs', requireAdmin, (req: AuthRequest, res: Response): void => {
  try {
    const limit = parseInt(req.query.limit as string) || 100;
    const logs = db.getAuditLogs(limit);
    res.json(logs);
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch audit logs' });
  }
});

// Toggle environment enabled/disabled (updates YAML via config manager)
app.put('/api/admin/environments/:name/toggle', requireAdmin, (req: AuthRequest, res: Response): void => {
  try {
    const { name } = req.params;
    const { enabled } = req.body;
    const adminUser = req.adminUser || 'unknown';
    const ipAddress = getClientIp(req);

    const env = config.getEnvironmentByName(name);
    if (!env) {
      res.status(404).json({ error: 'Environment not found' });
      return;
    }

    // Update environment in YAML file
    config.updateEnvironmentEnabled(name, enabled !== false);
    db.logAdminAction(adminUser, 'toggle_environment', 'environment', name, JSON.stringify({ enabled }), ipAddress);
    appendAudit({ employeeId: adminUser, action: 'toggle_environment', permission: 'admin:action', resourceType: 'environment', resourceId: name, metadata: { enabled }, ipAddress, userAgent: req.headers['user-agent'] as string }).catch(() => {});

    // Broadcast real-time update
    broadcastAdminUpdate('environment-updated', { environment_name: name, enabled, updated_by: adminUser });

    res.json({ success: true, message: `Environment ${name} ${enabled ? 'enabled' : 'disabled'}` });
  } catch (error) {
    res.status(500).json({ error: 'Failed to toggle environment' });
  }
});

// ============================================
// MONITORING API ROUTES
// ============================================

// Get monitoring dashboard data
app.get('/api/monitoring/dashboard', (_req: Request, res: Response): void => {
  try {
    const besuHealth = db.getServiceHealthSummary('besu');
    const cactiHealth = db.getServiceHealthSummary('cacti');
    const fireflyHealth = db.getServiceHealthSummary('firefly');
    const chainlinkHealth = db.getServiceHealthSummary('chainlink-ccip');

    res.json({
      besu: besuHealth,
      cacti: cactiHealth,
      firefly: fireflyHealth,
      chainlinkCcip: chainlinkHealth,
      timestamp: new Date().toISOString()
    });
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch monitoring data' });
  }
});

// Get Besu metrics
app.get('/api/monitoring/besu', (req: Request, res: Response): void => {
  try {
    const environment = req.query.environment as string;
    const serviceName = req.query.service as string || 'besu-validator-1';
    
    if (environment) {
      monitoring.collectBesuMetrics(environment, serviceName).then(metrics => {
        res.json(metrics);
      }).catch(() => {
        res.status(500).json({ error: 'Failed to collect Besu metrics' });
      });
    } else {
      const metrics = db.getServiceMetrics({ serviceType: 'besu', hours: 1 });
      res.json(metrics);
    }
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch Besu metrics' });
  }
});

// Get Cacti metrics
app.get('/api/monitoring/cacti', (req: Request, res: Response): void => {
  try {
    const environment = req.query.environment as string;
    const serviceName = req.query.service as string || 'cacti-1';
    
    if (environment) {
      monitoring.collectCactiMetrics(environment, serviceName).then(metrics => {
        res.json(metrics);
      }).catch(() => {
        res.status(500).json({ error: 'Failed to collect Cacti metrics' });
      });
    } else {
      const metrics = db.getServiceMetrics({ serviceType: 'cacti', hours: 1 });
      res.json(metrics);
    }
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch Cacti metrics' });
  }
});

// Get Firefly metrics
app.get('/api/monitoring/firefly', (req: Request, res: Response): void => {
  try {
    const environment = req.query.environment as string;
    const serviceName = req.query.service as string || 'firefly-1';
    
    if (environment) {
      monitoring.collectFireflyMetrics(environment, serviceName).then(metrics => {
        res.json(metrics);
      }).catch(() => {
        res.status(500).json({ error: 'Failed to collect Firefly metrics' });
      });
    } else {
      const metrics = db.getServiceMetrics({ serviceType: 'firefly', hours: 1 });
      res.json(metrics);
    }
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch Firefly metrics' });
  }
});

// Get Chainlink CCIP metrics
app.get('/api/monitoring/chainlink-ccip', (req: Request, res: Response): void => {
  try {
    const environment = req.query.environment as string;
    const serviceName = req.query.service as string || 'chainlink-ccip-1';
    
    if (environment) {
      monitoring.collectChainlinkCCIPMetrics(environment, serviceName).then(metrics => {
        res.json(metrics);
      }).catch(() => {
        res.status(500).json({ error: 'Failed to collect Chainlink CCIP metrics' });
      });
    } else {
      const metrics = db.getServiceMetrics({ serviceType: 'chainlink-ccip', hours: 1 });
      res.json(metrics);
    }
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch Chainlink CCIP metrics' });
  }
});

// Get service status
app.get('/api/monitoring/services/:type/:name/status', (req: Request, res: Response): void => {
  try {
    const { type, name } = req.params;
    const status = db.getServiceStatus(name, type);
    
    if (!status) {
      res.status(404).json({ error: 'Service status not found' });
      return;
    }
    
    res.json(status);
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch service status' });
  }
});

// Collect all metrics for an environment
app.post('/api/monitoring/environments/:name/collect', async (req: Request, res: Response): Promise<void> => {
  try {
    const { name } = req.params;
    const results = await monitoring.collectAllMetrics(name);
    
    // Broadcast update via WebSocket
    broadcastAdminUpdate('monitoring-updated', { environment: name, services: results });
    
    res.json({ success: true, data: results });
  } catch (error) {
    res.status(500).json({ error: 'Failed to collect metrics' });
  }
});

// Error handling middleware
app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
  console.error('Error:', err);
  res.status(500).json({ error: 'Internal server error' });
});

// Start server
const PORT = process.env.PORT || 5000;
const HOST = process.env.HOST || '0.0.0.0';

// Create HTTP server for Socket.IO
const server = http.createServer(app);

// Initialize Socket.IO
const io = new SocketIOServer(server, {
  cors: {
    origin: '*',
    methods: ['GET', 'POST']
  }
});

// Socket.IO connection handling
io.on('connection', (socket: import('socket.io').Socket) => {
  console.log('Client connected:', socket.id);

  // Join admin room for real-time updates
  socket.on('join-admin', () => {
    socket.join('admin');
    console.log('Client joined admin room:', socket.id);
  });

  // Handle disconnection
  socket.on('disconnect', () => {
    console.log('Client disconnected:', socket.id);
  });
});

// Helper function to broadcast admin updates
function broadcastAdminUpdate(type: string, data: any): void {
  io.to('admin').emit('admin-update', { type, data, timestamp: new Date().toISOString() });
}

// Seed sample data on startup
seedSampleData();

server.listen(PORT, () => {
  console.log('🚀 Enhanced Multi-Cloud Orchestration Portal starting...');
  console.log(`📊 Access dashboard at: http://${HOST}:${PORT}`);
  console.log(`🔍 Health dashboard at: http://${HOST}:${PORT}/dashboard/health`);
  console.log(`💰 Cost dashboard at: http://${HOST}:${PORT}/dashboard/costs`);
  console.log(`🔐 Admin panel at: http://${HOST}:${PORT}/admin`);
  console.log(`🔍 Monitoring dashboard at: http://${HOST}:${PORT}/monitoring`);
  console.log(`🔌 WebSocket server ready on port ${PORT}`);
});

// Graceful shutdown
process.on('SIGINT', () => {
  console.log('\nShutting down gracefully...');
  db.close();
  process.exit(0);
});

process.on('SIGTERM', () => {
  console.log('\nShutting down gracefully...');
  db.close();
  process.exit(0);
});

