/**
 * 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 { IRouteContext } from '../models/route-types.js';
import { createSocketTracker } from '../../../core/utils/socket-tracker.js';

/**
 * Minimal HTTP request parser for socket handlers.
 * Parses method, path, and optionally headers from a raw buffer.
 */
function parseHttpRequest(data: Buffer, extractHeaders: boolean = false): {
  method: string;
  path: string;
  headers: Record<string, string>;
  isComplete: boolean;
  body?: string;
} | null {
  const str = data.toString('utf8');
  const headerEnd = str.indexOf('\r\n\r\n');
  const isComplete = headerEnd !== -1;
  const headerSection = isComplete ? str.slice(0, headerEnd) : str;
  const lines = headerSection.split('\r\n');
  const requestLine = lines[0];
  if (!requestLine) return null;

  const parts = requestLine.split(' ');
  if (parts.length < 2) return null;

  const method = parts[0];
  const path = parts[1];

  // Quick check: valid HTTP method
  const validMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'CONNECT', 'TRACE'];
  if (!validMethods.includes(method)) return null;

  const headers: Record<string, string> = {};
  if (extractHeaders) {
    for (let i = 1; i < lines.length; i++) {
      const colonIdx = lines[i].indexOf(':');
      if (colonIdx > 0) {
        const name = lines[i].slice(0, colonIdx).trim().toLowerCase();
        const value = lines[i].slice(colonIdx + 1).trim();
        headers[name] = value;
      }
    }
  }

  const body = isComplete ? str.slice(headerEnd + 4) : undefined;

  return { method, path, headers, isComplete, body };
}

/**
 * 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
   */
  httpRedirect: (locationTemplate: string, statusCode: number = 301) => (socket: plugins.net.Socket, context: IRouteContext) => {
    const tracker = createSocketTracker(socket);

    const handleData = (data: Buffer) => {
      const parsed = parseHttpRequest(data);

      if (parsed) {
        const path = parsed.path || '/';
        const domain = context.domain || 'localhost';
        const port = context.port;

        const 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 {
        socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
      }

      socket.end();
      tracker.cleanup();
    };

    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
   */
  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 processData = (data: Buffer) => {
      if (requestParsed) return;

      const parsed = parseHttpRequest(data, true);

      if (!parsed || !parsed.isComplete) {
        return; // Not a complete HTTP request yet
      }

      requestParsed = true;
      socket.removeListener('data', processData);

      const req = {
        method: parsed.method,
        url: parsed.path,
        headers: parsed.headers,
        body: parsed.body || ''
      };

      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;

          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);
        responseTimer = setTimeout(() => {
          if (!ended) {
            res.send('');
          }
          responseTimer = null;
        }, 1000);
        tracker.addTimer(responseTimer);
      } catch (error) {
        if (!ended) {
          res.status(500);
          res.send('Internal Server Error');
        }
        tracker.safeDestroy(error instanceof Error ? error : new Error('Handler error'));
      }
    };

    tracker.addListener('data', processData);

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

    tracker.addListener('close', () => {
      if (responseTimer) {
        clearTimeout(responseTimer);
        responseTimer = null;
      }
      tracker.cleanup();
    });
  }
};
