import * as express from 'express';
import { Application, Request, Response, NextFunction } from 'express';
import * as cors from 'cors';
import { createServer } from 'http';
import { Server as SocketIOServer } from 'socket.io';
import { logger } from '../logging';
import { PluginManager } from '../../plugins/base/manager';
import { Result } from '../../shared/types/result';
import { ResponseCode } from '../../shared/constants/enums';
import * as swaggerUi from 'swagger-ui-express';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const swaggerJSDoc = require('swagger-jsdoc');

export interface ServerManagerConfig {
  port: number;
  host: string;
  cors: boolean;
  compression: boolean;
  security: boolean;
  socketio?: {
    enabled: boolean;
    cors?: {
      origin: string | string[];
      methods: string[];
    };
  };
}

export interface SocketEvent {
  event: string;
  handler: (socket: any, data: any) => void;
  description?: string;
}

export class ServerManager {
  private app: Application;
  private config: ServerManagerConfig;
  private pluginManager: PluginManager;
  private server: any = null;
  private io: SocketIOServer | null = null;
  private socketEvents: Map<string, SocketEvent> = new Map();

  constructor(config: ServerManagerConfig, pluginManager: PluginManager) {
    this.config = config;
    this.pluginManager = pluginManager;
    this.app = express();
    this.setupMiddleware();
    this.setupSwagger();
    this.setupSocketIO();
  }

  private setupMiddleware(): void {
    // Security middleware - simplified for now
    if (this.config.security) {
      this.app.use((req: Request, res: Response, next: NextFunction) => {
        res.setHeader('X-Content-Type-Options', 'nosniff');
        res.setHeader('X-Frame-Options', 'DENY');
        res.setHeader('X-XSS-Protection', '1; mode=block');
        next();
      });
    }

    // CORS middleware
    if (this.config.cors) {
      this.app.use(cors());
    }

    // Compression middleware - simplified for now
    if (this.config.compression) {
      this.app.use((req: Request, res: Response, next: NextFunction) => {
        // Basic compression logic would go here
        next();
      });
    }

    // Body parsing middleware
    this.app.use(express.json({ limit: '10mb' }));
    this.app.use(express.urlencoded({ extended: true, limit: '10mb' }));

    // Request logging
    this.app.use((req: Request, res: Response, next: NextFunction) => {
      logger.info(`${req.method} ${req.path}`, {
        ip: req.ip,
        userAgent: req.get('User-Agent')
      });
      next();
    });

    // Health check endpoint
    this.app.get('/health', (req: Request, res: Response) => {
      const result = Result.success({ 
        status: 'ok', 
        timestamp: new Date().toISOString(),
        uptime: process.uptime()
      });
      res.json(result);
    });
  }

  private setupSwagger(): void {
    const swaggerOptions = {
      definition: {
        openapi: '3.0.0',
        info: {
          title: 'Diagramers API',
          version: '1.0.0',
          description: 'API documentation for Diagramers',
        },
      },
      apis: ['./src/modules/**/*.ts'], // Scan all module files for JSDoc comments
    };
    const swaggerSpec = swaggerJSDoc(swaggerOptions);
    
    // Serve Swagger UI static files
    this.app.use('/api-docs', express.static(require('swagger-ui-dist').absolutePath()));
    
    // Serve the Swagger UI HTML
    this.app.get('/api-docs', (req: Request, res: Response) => {
      const html = `
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="utf-8" />
            <meta name="viewport" content="width=device-width, initial-scale=1" />
            <meta name="description" content="SwaggerUI" />
            <title>SwaggerUI</title>
            <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@4.5.0/swagger-ui.css" />
        </head>
        <body>
            <div id="swagger-ui"></div>
            <script src="https://unpkg.com/swagger-ui-dist@4.5.0/swagger-ui-bundle.js" crossorigin></script>
            <script src="https://unpkg.com/swagger-ui-dist@4.5.0/swagger-ui-standalone-preset.js" crossorigin></script>
            <script>
                window.onload = () => {
                    const ui = SwaggerUIBundle({
                        url: '/api-docs/swagger.json',
                        dom_id: '#swagger-ui',
                        deepLinking: true,
                        presets: [
                            SwaggerUIBundle.presets.apis,
                            SwaggerUIStandalonePreset
                        ],
                        plugins: [
                            SwaggerUIBundle.plugins.DownloadUrl
                        ],
                        layout: "StandaloneLayout"
                    });
                };
            </script>
        </body>
        </html>
      `;
      res.send(html);
    });
    
    // Serve the OpenAPI spec as JSON
    this.app.get('/api-docs/swagger.json', (req: Request, res: Response) => {
      res.setHeader('Content-Type', 'application/json');
      res.send(swaggerSpec);
    });
  }

  private setupSocketIO(): void {
    if (this.config.socketio?.enabled) {
      this.server = createServer(this.app);
      this.io = new SocketIOServer(this.server, {
        cors: this.config.socketio.cors || {
          origin: "*",
          methods: ["GET", "POST"]
        }
      });

      // Setup Socket.IO event handlers
      this.setupSocketEvents();
      
      logger.info('[ServerManager] Socket.IO enabled');
    }
  }

  private setupSocketEvents(): void {
    if (!this.io) return;

    // Connection event
    this.io.on('connection', (socket) => {
      logger.info(`[Socket.IO] Client connected: ${socket.id}`);

      // Register all custom events
      for (const [eventName, eventHandler] of this.socketEvents) {
        socket.on(eventName, (data) => {
          try {
            eventHandler.handler(socket, data);
          } catch (error) {
            logger.error(`[Socket.IO] Error handling event ${eventName}:`, error);
            socket.emit('error', { message: 'Internal server error' });
          }
        });
      }

      // Disconnect event
      socket.on('disconnect', () => {
        logger.info(`[Socket.IO] Client disconnected: ${socket.id}`);
      });

      // Error event
      socket.on('error', (error) => {
        logger.error(`[Socket.IO] Socket error for ${socket.id}:`, error);
      });
    });
  }

  /**
   * Register a Socket.IO event handler
   */
  registerSocketEvent(event: SocketEvent): void {
    this.socketEvents.set(event.event, event);
    logger.info(`[Socket.IO] Registered event: ${event.event} - ${event.description || 'No description'}`);
  }

  /**
   * Emit an event to all connected clients
   */
  emitToAll(event: string, data: any): void {
    if (this.io) {
      this.io.emit(event, data);
      logger.debug(`[Socket.IO] Emitted to all: ${event}`);
    }
  }

  /**
   * Emit an event to a specific room
   */
  emitToRoom(room: string, event: string, data: any): void {
    if (this.io) {
      this.io.to(room).emit(event, data);
      logger.debug(`[Socket.IO] Emitted to room ${room}: ${event}`);
    }
  }

  /**
   * Get Socket.IO server instance
   */
  getSocketIO(): SocketIOServer | null {
    return this.io;
  }

  async initialize(): Promise<void> {
    try {
      logger.info('[ServerManager] Initializing server...');
      
      // Start plugins
      await this.pluginManager.startPlugins();
      
      // Register plugin routes
      this.registerPluginRoutes();
      
      // Error handling middleware
      this.setupErrorHandling();
      
      logger.info('[ServerManager] Server initialized successfully');
    } catch (error) {
      logger.error('[ServerManager] Failed to initialize server:', error);
      throw error;
    }
  }

  private registerPluginRoutes(): void {
    // Register user routes
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const userRoutes = require('../../modules/users/routes/user.routes').default;
    this.app.use('/api', userRoutes);

    // Plugin routes will be registered through a different mechanism
    // For now, we'll add a basic API info endpoint
    this.app.get('/api/plugins', (req: Request, res: Response) => {
      const plugins = this.pluginManager.getPlugins();
      const pluginInfo = plugins.map(plugin => {
        const metadata = plugin.getMetadata();
        return {
          name: metadata.name,
          version: metadata.version,
          description: metadata.description,
          enabled: plugin.isEnabled()
        };
      });
      const result = Result.success({ plugins: pluginInfo });
      res.json(result);
    });

    // Socket.IO info endpoint
    if (this.config.socketio?.enabled) {
      this.app.get('/api/socket-info', (req: Request, res: Response) => {
        const events = Array.from(this.socketEvents.values()).map(event => ({
          event: event.event,
          description: event.description
        }));
        const result = Result.success({ 
          enabled: true,
          events: events
        });
        res.json(result);
      });
    }
  }

  private setupErrorHandling(): void {
    // 404 handler
    this.app.use((req: Request, res: Response) => {
      const result = Result.notFound(`Route ${req.method} ${req.path} not found`);
      res.status(404).json(result);
    });

    // Global error handler
    this.app.use((error: any, req: Request, res: Response, next: NextFunction) => {
      logger.error('[ServerManager] Unhandled error:', error);
      const result = Result.error('Internal Server Error');
      res.status(500).json(result);
    });
  }

  async start(): Promise<void> {
    return new Promise((resolve, reject) => {
      try {
        const serverToUse = this.server || this.app;
        const port = this.config.port;
        const host = this.config.host;

        serverToUse.listen(port, host, () => {
          logger.info(`[ServerManager] Server started on ${host}:${port}`);
          if (this.config.socketio?.enabled) {
            logger.info(`[ServerManager] Socket.IO available at ws://${host}:${port}`);
          }
          resolve();
        });

        serverToUse.on('error', (error: any) => {
          logger.error('[ServerManager] Server error:', error);
          reject(error);
        });
      } catch (error) {
        reject(error);
      }
    });
  }

  async stop(): Promise<void> {
    return new Promise((resolve, reject) => {
      const serverToUse = this.server || this.app;
      
      if (serverToUse) {
        serverToUse.close((error: any) => {
          if (error) {
            logger.error('[ServerManager] Error stopping server:', error);
            reject(error);
          } else {
            logger.info('[ServerManager] Server stopped');
            resolve();
          }
        });
      } else {
        resolve();
      }
    });
  }

  getApp(): Application {
    return this.app;
  }
} 