/**
 * Socket Handler Functions
 *
 * This module provides pre-built socket handlers for common use cases
 * like echoing, proxying, HTTP responses, and redirects.
 */

import * as plugins from '../../../../plugins.js';
import type { IRouteConfig, TPortRange, IRouteContext } from '../../models/route-types.js';
import { ProtocolDetector } from '../../../../detection/index.js';
import { createSocketTracker } from '../../../../core/utils/socket-tracker.js';

/**
 * Pre-built socket handlers for common use cases
 */
export const SocketHandlers = {
  /**
   * Simple echo server handler
   */
  echo: (socket: plugins.net.Socket, context: IRouteContext) => {
    socket.write('ECHO SERVER READY\n');
    socket.on('data', data => socket.write(data));
  },

  /**
   * TCP proxy handler
   */
  proxy: (targetHost: string, targetPort: number) => (socket: plugins.net.Socket, context: IRouteContext) => {
    const target = plugins.net.connect(targetPort, targetHost);
    socket.pipe(target);
    target.pipe(socket);
    socket.on('close', () => target.destroy());
    target.on('close', () => socket.destroy());
    target.on('error', (err) => {
      console.error('Proxy target error:', err);
      socket.destroy();
    });
  },

  /**
   * Line-based protocol handler
   */
  lineProtocol: (handler: (line: string, socket: plugins.net.Socket) => void) => (socket: plugins.net.Socket, context: IRouteContext) => {
    let buffer = '';
    socket.on('data', (data) => {
      buffer += data.toString();
      const lines = buffer.split('\n');
      buffer = lines.pop() || '';
      lines.forEach(line => {
        if (line.trim()) {
          handler(line.trim(), socket);
        }
      });
    });
  },

  /**
   * Simple HTTP response handler (for testing)
   */
  httpResponse: (statusCode: number, body: string) => (socket: plugins.net.Socket, context: IRouteContext) => {
    const response = [
      `HTTP/1.1 ${statusCode} ${statusCode === 200 ? 'OK' : 'Error'}`,
      'Content-Type: text/plain',
      `Content-Length: ${body.length}`,
      'Connection: close',
      '',
      body
    ].join('\r\n');

    socket.write(response);
    socket.end();
  },

  /**
   * Block connection immediately
   */
  block: (message?: string) => (socket: plugins.net.Socket, context: IRouteContext) => {
    const finalMessage = message || `Connection blocked from ${context.clientIp}`;
    if (finalMessage) {
      socket.write(finalMessage);
    }
    socket.end();
  },

  /**
   * HTTP block response
   */
  httpBlock: (statusCode: number = 403, message?: string) => (socket: plugins.net.Socket, context: IRouteContext) => {
    const defaultMessage = `Access forbidden for ${context.domain || context.clientIp}`;
    const finalMessage = message || defaultMessage;

    const response = [
      `HTTP/1.1 ${statusCode} ${finalMessage}`,
      'Content-Type: text/plain',
      `Content-Length: ${finalMessage.length}`,
      'Connection: close',
      '',
      finalMessage
    ].join('\r\n');

    socket.write(response);
    socket.end();
  },

  /**
   * HTTP redirect handler
   * Uses the centralized detection module for HTTP parsing
   */
  httpRedirect: (locationTemplate: string, statusCode: number = 301) => (socket: plugins.net.Socket, context: IRouteContext) => {
    const tracker = createSocketTracker(socket);
    const connectionId = ProtocolDetector.createConnectionId({
      socketId: context.connectionId || `${Date.now()}-${Math.random()}`
    });

    const handleData = async (data: Buffer) => {
      // Use detection module for parsing
      const detectionResult = await ProtocolDetector.detectWithConnectionTracking(
        data,
        connectionId,
        { extractFullHeaders: false } // We only need method and path
      );

      if (detectionResult.protocol === 'http' && detectionResult.connectionInfo.path) {
        const method = detectionResult.connectionInfo.method || 'GET';
        const path = detectionResult.connectionInfo.path || '/';

        const domain = context.domain || 'localhost';
        const port = context.port;

        let finalLocation = locationTemplate
          .replace('{domain}', domain)
          .replace('{port}', String(port))
          .replace('{path}', path)
          .replace('{clientIp}', context.clientIp);

        const message = `Redirecting to ${finalLocation}`;
        const response = [
          `HTTP/1.1 ${statusCode} ${statusCode === 301 ? 'Moved Permanently' : 'Found'}`,
          `Location: ${finalLocation}`,
          'Content-Type: text/plain',
          `Content-Length: ${message.length}`,
          'Connection: close',
          '',
          message
        ].join('\r\n');

        socket.write(response);
      } else {
        // Not a valid HTTP request, close connection
        socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
      }

      socket.end();
      // Clean up detection state
      ProtocolDetector.cleanupConnections();
      // Clean up all tracked resources
      tracker.cleanup();
    };

    // Use tracker to manage the listener
    socket.once('data', handleData);

    tracker.addListener('error', (err) => {
      tracker.safeDestroy(err);
    });

    tracker.addListener('close', () => {
      tracker.cleanup();
    });
  },

  /**
   * HTTP server handler for ACME challenges and other HTTP needs
   * Uses the centralized detection module for HTTP parsing
   */
  httpServer: (handler: (req: { method: string; url: string; headers: Record<string, string>; body?: string }, res: { status: (code: number) => void; header: (name: string, value: string) => void; send: (data: string) => void; end: () => void }) => void) => (socket: plugins.net.Socket, context: IRouteContext) => {
    const tracker = createSocketTracker(socket);
    let requestParsed = false;
    let responseTimer: NodeJS.Timeout | null = null;
    const connectionId = ProtocolDetector.createConnectionId({
      socketId: context.connectionId || `${Date.now()}-${Math.random()}`
    });

    const processData = async (data: Buffer) => {
      if (requestParsed) return; // Only handle the first request

      // Use HttpDetector for parsing
      const detectionResult = await ProtocolDetector.detectWithConnectionTracking(
        data,
        connectionId,
        { extractFullHeaders: true }
      );

      if (detectionResult.protocol !== 'http' || !detectionResult.isComplete) {
        // Not a complete HTTP request yet
        return;
      }

      requestParsed = true;
      // Remove data listener after parsing request
      socket.removeListener('data', processData);
      const connInfo = detectionResult.connectionInfo;

      // Create request object from detection result
      const req = {
        method: connInfo.method || 'GET',
        url: connInfo.path || '/',
        headers: connInfo.headers || {},
        body: detectionResult.remainingBuffer?.toString() || ''
      };

      // Create response object
      let statusCode = 200;
      const responseHeaders: Record<string, string> = {};
      let ended = false;

      const res = {
        status: (code: number) => {
          statusCode = code;
        },
        header: (name: string, value: string) => {
          responseHeaders[name] = value;
        },
        send: (data: string) => {
          if (ended) return;
          ended = true;

          // Clear response timer since we're sending now
          if (responseTimer) {
            clearTimeout(responseTimer);
            responseTimer = null;
          }

          if (!responseHeaders['content-type']) {
            responseHeaders['content-type'] = 'text/plain';
          }
          responseHeaders['content-length'] = String(data.length);
          responseHeaders['connection'] = 'close';

          const statusText = statusCode === 200 ? 'OK' :
                           statusCode === 404 ? 'Not Found' :
                           statusCode === 500 ? 'Internal Server Error' : 'Response';

          let response = `HTTP/1.1 ${statusCode} ${statusText}\r\n`;
          for (const [name, value] of Object.entries(responseHeaders)) {
            response += `${name}: ${value}\r\n`;
          }
          response += '\r\n';
          response += data;

          socket.write(response);
          socket.end();
        },
        end: () => {
          if (ended) return;
          ended = true;
          socket.write('HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: close\r\n\r\n');
          socket.end();
        }
      };

      try {
        handler(req, res);
        // Ensure response is sent even if handler doesn't call send()
        responseTimer = setTimeout(() => {
          if (!ended) {
            res.send('');
          }
          responseTimer = null;
        }, 1000);
        // Track and unref the timer
        tracker.addTimer(responseTimer);
      } catch (error) {
        if (!ended) {
          res.status(500);
          res.send('Internal Server Error');
        }
        // Use safeDestroy for error cases
        tracker.safeDestroy(error instanceof Error ? error : new Error('Handler error'));
      }
    };

    // Use tracker to manage listeners
    tracker.addListener('data', processData);

    tracker.addListener('error', (err) => {
      if (!requestParsed) {
        tracker.safeDestroy(err);
      }
    });

    tracker.addListener('close', () => {
      // Clear any pending response timer
      if (responseTimer) {
        clearTimeout(responseTimer);
        responseTimer = null;
      }
      // Clean up detection state
      ProtocolDetector.cleanupConnections();
      // Clean up all tracked resources
      tracker.cleanup();
    });
  }
};

/**
 * Create a socket handler route configuration
 * @param domains Domain(s) to match
 * @param ports Port(s) to listen on
 * @param handler Socket handler function
 * @param options Additional route options
 * @returns Route configuration object
 */
export function createSocketHandlerRoute(
  domains: string | string[],
  ports: TPortRange,
  handler: (socket: plugins.net.Socket) => void | Promise<void>,
  options: {
    name?: string;
    priority?: number;
    path?: string;
  } = {}
): IRouteConfig {
  return {
    name: options.name || 'socket-handler-route',
    priority: options.priority !== undefined ? options.priority : 50,
    match: {
      domains,
      ports,
      ...(options.path && { path: options.path })
    },
    action: {
      type: 'socket-handler',
      socketHandler: handler
    }
  };
}
