/**
 * @fileoverview OrdoJS CLI - Development Server
 *
 * Main development server implementation with lifecycle management.
 */

import { readdir } from 'fs/promises';
import http from 'http';
import type { AddressInfo } from 'net';
import path from 'path';
import { fileExists, readFile } from '../utils/fs.js';
import { logger } from '../utils/index.js';
import { PortManager } from './port-manager.js';
import { ProcessManager } from './process-manager.js';

/**
 * Development server options
 */
export interface DevServerOptions {
  /** Directory to serve */
  dir: string;
  /** Host to listen on */
  host: string;
  /** Port to listen on */
  port: string | number;
  /** Enable hot module replacement */
  hmr: boolean;
}

/**
 * Server status enum
 */
export enum ServerStatus {
  STOPPED = 'stopped',
  STARTING = 'starting',
  RUNNING = 'running',
  STOPPING = 'stopping',
  ERROR = 'error',
  RESTARTING = 'restarting'
}

/**
 * Server state interface for preserving state during restarts
 */
export interface ServerState {
  /** Connected clients */
  connectedClients?: string[];
  /** Active file watchers */
  activeWatchers?: string[];
  /** Compilation cache */
  compilationCache?: Record<string, unknown>;
  /** Custom state data */
  [key: string]: unknown;
}

/**
 * OrdoJSDevServer class for managing the development server lifecycle
 */
export class OrdoJSDevServer {
  private options: DevServerOptions;
  private server: http.Server | null;
  private portManager: PortManager;
  private processManager: ProcessManager;
  private status: ServerStatus;
  private actualPort: number;
  private serverState: ServerState;
  private hmrServer: any | null; // Placeholder for HMR server instance
  private cssWatcher: any | null; // Placeholder for CSS watcher instance

  /**
   * Create a new OrdoJSDevServer instance
   *
   * @param options - Server options
   */
  constructor(options: DevServerOptions) {
    this.options = {
      ...options,
      dir: options.dir || '.',
      host: options.host || 'localhost',
      port: options.port || 3000,
      hmr: options.hmr !== false
    };

    this.server = null;
    this.portManager = new PortManager(Number(this.options.port));
    this.processManager = new ProcessManager();
    this.status = ServerStatus.STOPPED;
    this.actualPort = 0;
    this.serverState = {};
    this.hmrServer = null;
    this.cssWatcher = null;
  }

  /**
   * Get the current server status
   */
  getStatus(): ServerStatus {
    return this.status;
  }

  /**
   * Get the actual port the server is running on
   */
  getPort(): number {
    return this.actualPort;
  }

  /**
   * Get the current server state
   *
   * @returns The current server state
   */
  getServerState(): ServerState {
    return { ...this.serverState };
  }

  /**
   * Start the development server
   */
  async start(): Promise<void> {
    if (this.status === ServerStatus.RUNNING) {
      logger.info('Server is already running');
      return;
    }

    if (this.status === ServerStatus.STARTING) {
      logger.info('Server is already starting');
      return;
    }

    this.status = ServerStatus.STARTING;
    logger.info('Starting development server...');

    try {
      // Validate the directory exists
      const dirExists = await fileExists(this.options.dir);
      if (!dirExists) {
        throw new Error(`Directory not found: ${this.options.dir}`);
      }

      // Allocate a port
      this.actualPort = await this.portManager.allocatePort(
        'dev-server',
        Number(this.options.port)
      );

      // Create HTTP server
      this.server = http.createServer(this.requestHandler.bind(this));

      // Start the server
      await this.startServer();

      // Set up HMR if enabled
      if (this.options.hmr) {
        await this.setupHMR();
      }

      this.status = ServerStatus.RUNNING;
      logger.success(`Development server running at http://${this.options.host}:${this.actualPort}`);
    } catch (error) {
      this.status = ServerStatus.ERROR;
      logger.error(`Failed to start server: ${error instanceof Error ? error.message : String(error)}`);
      throw error;
    }
  }

  /**
   * Stop the development server
   */
  async stop(): Promise<void> {
    if (this.status === ServerStatus.STOPPED) {
      logger.info('Server is already stopped');
      return;
    }

    if (this.status === ServerStatus.STOPPING) {
      logger.info('Server is already stopping');
      return;
    }

    this.status = ServerStatus.STOPPING;
    logger.info('Stopping development server...');

    try {
      // Stop the HTTP server
      if (this.server) {
        await this.stopServer();
      }

      // Clean up resources
      if (this.actualPort) {
        this.portManager.releasePort(this.actualPort);
      }

      // Clean up child processes
      await this.processManager.cleanup();

      this.status = ServerStatus.STOPPED;
      logger.success('Development server stopped');
    } catch (error) {
      this.status = ServerStatus.ERROR;
      logger.error(`Failed to stop server: ${error instanceof Error ? error.message : String(error)}`);
      throw error;
    }
  }

  /**
   * Restart the development server
   */
  async restart(): Promise<void> {
    if (this.status === ServerStatus.RESTARTING) {
      logger.info('Server is already restarting');
      return;
    }

    this.status = ServerStatus.RESTARTING;
    logger.info('Restarting development server...');

    try {
      // Preserve the server state before stopping
      const preservedState = { ...this.serverState };

      // Stop the server but keep the state
      await this.stop();

      // Restore the preserved state
      this.serverState = preservedState;

      // Start the server with the preserved state
      await this.start();

      logger.success('Development server restarted successfully');
    } catch (error) {
      this.status = ServerStatus.ERROR;
      logger.error(`Failed to restart server: ${error instanceof Error ? error.message : String(error)}`);
      throw error;
    }
  }

  /**
   * Start the HTTP server
   */
  private startServer(): Promise<void> {
    return new Promise<void>((resolve, reject) => {
      if (!this.server) {
        reject(new Error('Server not initialized'));
        return;
      }

      // Handle server errors
      this.server.once('error', (error) => {
        const e = error as NodeJS.ErrnoException;
        if (e.code === 'EADDRINUSE') {
          reject(new Error(`Port ${this.actualPort} is already in use`));
        } else {
          reject(error);
        }
      });

      // Start listening
      this.server.listen(this.actualPort, this.options.host, () => {
        const address = this.server?.address() as AddressInfo;
        this.actualPort = address.port;
        resolve();
      });
    });
  }

  /**
   * Stop the HTTP server
   */
  private stopServer(): Promise<void> {
    return new Promise<void>((resolve, reject) => {
      if (!this.server) {
        resolve();
        return;
      }

      this.server.close((error) => {
        if (error) {
          reject(error);
        } else {
          this.server = null;
          resolve();
        }
      });
    });
  }

  /**
   * HTTP request handler
   */
  private async requestHandler(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
    const url = req.url || '/';
    logger.debug(`${req.method} ${url}`);

    try {
      // Handle different file types
      if (url.endsWith('.ordo')) {
        await this.handleOrdoFile(req, res, url);
      } else if (url === '/' || url === '/index.html') {
        await this.handleIndex(req, res);
      } else {
        await this.handleStaticFile(req, res, url);
      }
    } catch (error) {
      logger.error(`Request handler error: ${error instanceof Error ? error.message : String(error)}`);
      res.writeHead(500, { 'Content-Type': 'text/plain' });
      res.end('Internal Server Error');
    }
  }

  /**
   * Handle OrdoJS file compilation
   */
  private async handleOrdoFile(req: http.IncomingMessage, res: http.ServerResponse, url: string): Promise<void> {
    const filePath = path.join(this.options.dir, url);

    try {
      // Read the OrdoJS file
      const source = await readFile(filePath);

      // Compile the OrdoJS file to HTML/JS
      const html = this.compileOrdoToHTML(source, path.basename(filePath, '.ordo'));

      res.writeHead(200, { 'Content-Type': 'text/html' });
      res.end(html);
    } catch (error) {
      logger.error(`Failed to compile OrdoJS file: ${error instanceof Error ? error.message : String(error)}`);
      res.writeHead(500, { 'Content-Type': 'text/html' });
      res.end(`
        <!DOCTYPE html>
        <html>
          <head><title>Compilation Error</title></head>
          <body>
            <h1>OrdoJS Compilation Error</h1>
            <p>Failed to compile ${url}: ${error instanceof Error ? error.message : String(error)}</p>
          </body>
        </html>
      `);
    }
  }

  /**
   * Handle index page
   */
  private async handleIndex(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
    // First try to serve public/index.html
    const indexHtmlPath = path.join(this.options.dir, 'public/index.html');

    try {
      const exists = await fileExists(indexHtmlPath);
      if (exists) {
        // Serve the index.html file
        const content = await readFile(indexHtmlPath);
        res.writeHead(200, { 'Content-Type': 'text/html' });
        res.end(content);
        return;
      }
    } catch (error) {
      // Continue to fallback
    }

    // Fallback: Look for app.ordo in the current directory
    const appOrdoPath = path.join(this.options.dir, 'src/app.ordo');

    try {
      const exists = await fileExists(appOrdoPath);
      if (exists) {
        // Redirect to the app.ordo file
        res.writeHead(302, { 'Location': '/src/app.ordo' });
        res.end();
      } else {
        // Show directory listing or default page
        await this.showDirectoryListing(req, res);
      }
    } catch (error) {
      await this.showDirectoryListing(req, res);
    }
  }

  /**
   * Handle static files
   */
  private async handleStaticFile(req: http.IncomingMessage, res: http.ServerResponse, url: string): Promise<void> {
    const filePath = path.join(this.options.dir, url);

    try {
      const exists = await fileExists(filePath);
      if (!exists) {
        res.writeHead(404, { 'Content-Type': 'text/plain' });
        res.end('File not found');
        return;
      }

      const content = await readFile(filePath);
      const ext = path.extname(filePath);

      let contentType = 'text/plain';
      switch (ext) {
        case '.html': contentType = 'text/html'; break;
        case '.css': contentType = 'text/css'; break;
        case '.js': contentType = 'application/javascript'; break;
        case '.json': contentType = 'application/json'; break;
        case '.png': contentType = 'image/png'; break;
        case '.jpg':
        case '.jpeg': contentType = 'image/jpeg'; break;
        case '.gif': contentType = 'image/gif'; break;
        case '.svg': contentType = 'image/svg+xml'; break;
      }

      res.writeHead(200, { 'Content-Type': contentType });
      res.end(content);
    } catch (error) {
      res.writeHead(500, { 'Content-Type': 'text/plain' });
      res.end('Internal Server Error');
    }
  }

  /**
   * Show directory listing
   */
  private async showDirectoryListing(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
    try {
      const files = await readdir(this.options.dir);
      const ordoFiles = files.filter(file => file.endsWith('.ordo'));

      res.writeHead(200, { 'Content-Type': 'text/html' });
      res.end(`
        <!DOCTYPE html>
        <html>
          <head>
            <title>OrdoJS Development Server</title>
            <style>
              body { font-family: system-ui, sans-serif; max-width: 800px; margin: 0 auto; padding: 2rem; }
              h1 { color: #0066cc; }
              .file-list { list-style: none; padding: 0; }
              .file-list li { margin: 0.5rem 0; }
              .file-list a { color: #0066cc; text-decoration: none; }
              .file-list a:hover { text-decoration: underline; }
            </style>
          </head>
          <body>
            <h1>OrdoJS Development Server</h1>
            <p>Server is running at <code>http://${this.options.host}:${this.actualPort}</code></p>
            <p>Serving directory: <code>${path.resolve(this.options.dir)}</code></p>
            ${ordoFiles.length > 0 ? `
              <h2>Available OrdoJS Files:</h2>
              <ul class="file-list">
                ${ordoFiles.map(file => `<li><a href="/${file}">${file}</a></li>`).join('')}
              </ul>
            ` : '<p>No .ordo files found in the current directory.</p>'}
          </body>
        </html>
      `);
    } catch (error) {
      res.writeHead(500, { 'Content-Type': 'text/plain' });
      res.end('Failed to read directory');
    }
  }

  /**
   * Compile OrdoJS to HTML
   */
  private compileOrdoToHTML(source: string, componentName: string): string {
    try {
      // Simple compilation for now - just wrap the content in HTML
      // This is a basic implementation that will be enhanced
      return `
        <!DOCTYPE html>
        <html lang="en">
        <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>${componentName} - OrdoJS</title>
          <style>
            body { font-family: system-ui, sans-serif; margin: 0; padding: 20px; }
            .component { border: 1px solid #ddd; padding: 20px; margin: 20px 0; border-radius: 8px; }
            .source { background: #f5f5f5; padding: 15px; border-radius: 5px; margin: 20px 0; }
            pre { margin: 0; white-space: pre-wrap; }
          </style>
        </head>
        <body>
          <h1>${componentName}</h1>
          <div class="component">
            <h2>Compiled Component</h2>
            <p>This is a placeholder for the compiled OrdoJS component.</p>
            <p>The full compilation pipeline will be implemented in upcoming tasks.</p>
          </div>
          <div class="source">
            <h3>Source Code:</h3>
            <pre><code>${source.replace(/</g, '&lt;').replace(/>/g, '&gt;')}</code></pre>
          </div>
          <script>
            console.log('OrdoJS component: ${componentName}');
            console.log('Source:', \`${source.replace(/`/g, '\\`')}\`);
          </script>
        </body>
        </html>
      `;
    } catch (error) {
      return `
        <!DOCTYPE html>
        <html>
        <head><title>Compilation Error</title></head>
        <body>
          <h1>OrdoJS Compilation Error</h1>
          <p>Failed to compile component: ${error instanceof Error ? error.message : String(error)}</p>
        </body>
        </html>
      `;
    }
  }

  /**
   * Set up Hot Module Replacement
   */
  private async setupHMR(): Promise<void> {
    try {
      // Import HMR modules dynamically
      const { HMRServer } = await import('./hmr.js');

      this.hmrServer = new HMRServer({
        port: this.actualPort + 1,
        host: this.options.host
      });

      await this.hmrServer.start();
      logger.info('HMR server started');

      // Set up file watching for CSS files
      await this.setupCSSWatching();

    } catch (error) {
      logger.warn(`HMR setup failed: ${error instanceof Error ? error.message : String(error)}`);
      logger.info('Continuing without HMR...');
    }
  }

  /**
   * Set up CSS file watching for hot reload
   */
  private async setupCSSWatching(): Promise<void> {
    try {
      const { FileWatcher } = await import('./file-watcher.js');

      this.cssWatcher = new FileWatcher({
        dir: this.options.dir,
        patterns: ['**/*.css', '**/*.scss', '**/*.sass', '**/*.less'],
        ignore: ['node_modules/**', 'dist/**', 'build/**']
      });

      this.cssWatcher.on('change', (event) => {
        this.handleCSSChange(event);
      });

      await this.cssWatcher.start();
      logger.info('CSS file watching enabled');

    } catch (error) {
      logger.warn(`CSS watching setup failed: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  /**
   * Handle CSS file changes for hot reload
   */
  private handleCSSChange(event: { file: string; type: 'change' | 'add' | 'unlink' }): void {
    if (!this.hmrServer) return;

    logger.info(`CSS file changed: ${event.file}`);

    // Send CSS update to connected clients
    this.hmrServer.broadcast({
      type: 'css-update',
      file: event.file,
      timestamp: Date.now()
    });
  }
}
