/**
 * @fileoverview OrdoJS CLI - Development Server with File Watching
 *
 * Main development server implementation with lifecycle management and file watching.
 */

import http from 'http';
import { AddressInfo } from 'net';
import path from 'path';
import { fileExists } from '../utils/fs.js';
import { logger } from '../utils/index.js';
import { FileChangeEvent, FileWatcher } from './file-watcher.js';
import { generateHMRClientCode } from './hmr-client.js';
import { OrdoJSHMR } from './hmr.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;
  /** HMR port (defaults to main port + 1) */
  hmrPort?: number;
}

/**
 * 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 fileWatcher: FileWatcher | null;
  private hmr: OrdoJSHMR | null;
  private status: ServerStatus;
  private actualPort: number;
  private hmrPort: number;
  private serverState: ServerState;

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

    this.server = null;
    this.portManager = new PortManager(Number(this.options.port));
    this.processManager = new ProcessManager();
    this.fileWatcher = null;
    this.hmr = null;
    this.status = ServerStatus.STOPPED;
    this.actualPort = 0;
    this.hmrPort = this.options.hmrPort || (Number(this.options.port) + 1);
    this.serverState = {};
  }

  /**
   * 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 HMR port
   */
  getHMRPort(): number {
    return this.hmrPort;
  }

  /**
   * 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)
      );

      // Allocate HMR port
      this.hmrPort = await this.portManager.allocatePort(
        'hmr-server',
        this.hmrPort
      );

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

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

      // Set up file watching and HMR if enabled
      if (this.options.hmr) {
        await this.setupHMR();
        logger.info(`HMR enabled on port ${this.hmrPort}`);
      }

      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 HMR system
      if (this.hmr) {
        await this.hmr.stop();
        this.hmr = null;
      }

      // Stop file watching
      if (this.fileWatcher) {
        await this.fileWatcher.stop();
        this.fileWatcher = null;
      }

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

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

      if (this.hmrPort) {
        this.portManager.releasePort(this.hmrPort);
      }

      // 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;
    }
  }

  /**
   * Set up HMR system and file watching
   */
  private async setupHMR(): Promise<void> {
    // Set up file watcher
    this.fileWatcher = new FileWatcher({
      watchDir: this.options.dir,
              patterns: ['**/*.ordo', '**/*.ts', '**/*.js', '**/*.css', '**/*.html'],
      ignorePatterns: ['**/node_modules/**', '**/dist/**', '**/.git/**'],
      debounceMs: 100
    });

    // Set up HMR system
    this.hmr = new OrdoJSHMR({
      port: this.hmrPort,
      preserveState: true,
      verbose: false
    });

    // Connect file watcher to HMR system
    this.fileWatcher.on('change', async (event: FileChangeEvent) => {
      // Forward file changes to HMR system
      await this.hmr?.handleFileChange(event);

      // Also handle locally for logging
      this.handleFileChange(event);
    });

    this.fileWatcher.on('error', this.handleFileWatcherError.bind(this));

    // Start HMR system
    await this.hmr.start();

    // Start file watcher
    await this.fileWatcher.start();

    logger.success('HMR system and file watching started successfully');
  }

  /**
   * Handle file change events
   */
  private async handleFileChange(event: FileChangeEvent): Promise<void> {
    logger.info(`File ${event.type}: ${event.relativePath}`);

    try {
      // Get affected files
      const affectedFiles = this.fileWatcher?.getAffectedFiles(event.filePath) || [event.filePath];

      logger.debug(`Affected files: ${affectedFiles.map(f => path.relative(this.options.dir, f)).join(', ')}`);
    } catch (error) {
      logger.error(`Error handling file change: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  /**
   * Handle file watcher errors
   */
  private handleFileWatcherError(error: Error): void {
    logger.error(`File watcher error: ${error.message}`);

    // Attempt to restart file watching
    if (this.options.hmr && this.status === ServerStatus.RUNNING) {
      logger.info('Attempting to restart file watcher and HMR...');
      this.setupHMR().catch(restartError => {
        logger.error(`Failed to restart HMR: ${restartError instanceof Error ? restartError.message : String(restartError)}`);
      });
    }
  }

  /**
   * 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 requestHandler(req: http.IncomingMessage, res: http.ServerResponse): void {
    const url = req.url || '/';
    logger.debug(`${req.method} ${url}`);

    // Inject HMR client script for HTML requests
    if (this.options.hmr && (url === '/' || url.endsWith('.html'))) {
      res.writeHead(200, { 'Content-Type': 'text/html' });
      res.end(`
        <!DOCTYPE html>
        <html>
          <head>
            <title>OrdoJS Dev Server</title>
            <style>
              body { font-family: system-ui, sans-serif; max-width: 800px; margin: 0 auto; padding: 2rem; }
              h1 { color: #0066cc; }
              code { background: #f0f0f0; padding: 0.2rem 0.4rem; border-radius: 3px; }
            </style>
            <script>
              ${generateHMRClientCode(this.hmrPort)}
            </script>
          </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>
            <p>HMR: <code>${this.options.hmr ? 'enabled' : 'disabled'}</code></p>
            <p>File Watching: <code>${this.fileWatcher ? 'active' : 'inactive'}</code></p>
            <p>HMR WebSocket: <code>ws://${this.options.host}:${this.hmrPort}</code></p>
          </body>
        </html>
      `);
      return;
    }

    // Handle other requests
    res.writeHead(200, { 'Content-Type': 'text/html' });
    res.end(`
      <!DOCTYPE html>
      <html>
        <head>
          <title>OrdoJS Dev Server</title>
          <style>
            body { font-family: system-ui, sans-serif; max-width: 800px; margin: 0 auto; padding: 2rem; }
            h1 { color: #0066cc; }
            code { background: #f0f0f0; padding: 0.2rem 0.4rem; border-radius: 3px; }
          </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>
          <p>HMR: <code>${this.options.hmr ? 'enabled' : 'disabled'}</code></p>
          <p>File Watching: <code>${this.fileWatcher ? 'active' : 'inactive'}</code></p>
        </body>
      </html>
    `);
  }
}
