import { randomUUID } from 'crypto';

import { logger } from '@goodgo/logger';
import { Request, Response, NextFunction } from 'express';

/**
 * EN: Correlation ID header name
 * VI: Tên header cho Correlation ID
 */
export const CORRELATION_ID_HEADER = 'x-correlation-id';
export const REQUEST_ID_HEADER = 'x-request-id';

/**
 * EN: Extended Request interface with correlation ID
 * VI: Interface Request mở rộng với correlation ID
 */
declare global {
   
  namespace Express {
    interface Request {
      correlationId: string;
      requestId: string;
    }
  }
}

/**
 * EN: Correlation ID middleware - generates and propagates correlation IDs
 * VI: Middleware Correlation ID - tạo và truyền correlation IDs
 */
export const correlationMiddleware = (
  options: {
    headerName?: string;
    generateId?: () => string;
    skipPaths?: string[];
  } = {}
) => {
  const {
    headerName = CORRELATION_ID_HEADER,
    generateId = randomUUID,
    skipPaths = ['/health', '/metrics', '/favicon.ico'],
  } = options;

  return (req: Request, res: Response, next: NextFunction) => {
    // EN: Skip correlation ID for certain paths
    // VI: Bỏ qua correlation ID cho một số paths
    if (skipPaths.some(path => req.path.startsWith(path))) {
      req.correlationId = '';
      req.requestId = '';
      return next();
    }

    // EN: Get correlation ID from header or generate new one
    // VI: Lấy correlation ID từ header hoặc tạo mới
    const correlationId = req.headers[headerName.toLowerCase()] as string || generateId();

    // EN: Generate unique request ID for this specific request
    // VI: Tạo request ID duy nhất cho request này
    const requestId = generateId();

    // EN: Attach to request object
    // VI: Gắn vào request object
    req.correlationId = correlationId;
    req.requestId = requestId;

    // EN: Add correlation ID to response headers
    // VI: Thêm correlation ID vào response headers
    res.setHeader(headerName, correlationId);
    res.setHeader(REQUEST_ID_HEADER, requestId);

    // EN: Add to logger context
    // VI: Thêm vào logger context
    logger.info('Request started / Request bắt đầu', {
      correlationId,
      requestId,
      method: req.method,
      url: req.url,
      userAgent: req.get('User-Agent'),
      ip: req.ip,
    });

    // EN: Store original end method
    // VI: Lưu original end method
    const originalEnd = res.end;
    const originalJson = res.json;
    const originalSend = res.send;

    // EN: Override response methods to log completion
    // VI: Override response methods để log completion
    const logCompletion = () => {
      logger.info('Request completed / Request hoàn thành', {
        correlationId,
        requestId,
        method: req.method,
        url: req.url,
        statusCode: res.statusCode,
        duration: Date.now() - (req as any).startTime,
      });
    };

    // EN: Track request start time
    // VI: Theo dõi thời gian bắt đầu request
    (req as any).startTime = Date.now();

    // EN: Override end method
    // VI: Override end method
    // EN: Type assertion needed due to Express's complex overloaded signatures
    // VI: Cần type assertion do Express có signatures phức tạp
    res.end = function(this: Response, chunk?: any, encoding?: BufferEncoding, cb?: () => void): Response {
      logCompletion();
      return originalEnd.call(this, chunk, encoding as BufferEncoding, cb);
    } as typeof res.end;

    // EN: Override json method
    // VI: Override json method
    res.json = function(body?: any) {
      logCompletion();
      return originalJson.call(this, body);
    };

    // EN: Override send method
    // VI: Override send method
    res.send = function(body?: any) {
      logCompletion();
      return originalSend.call(this, body);
    };

    next();
  };
};

/**
 * EN: Get correlation ID from request
 * VI: Lấy correlation ID từ request
 */
export const getCorrelationId = (req: Request): string => {
  return req.correlationId || '';
};

/**
 * EN: Get request ID from request
 * VI: Lấy request ID từ request
 */
export const getRequestId = (req: Request): string => {
  return req.requestId || '';
};

/**
 * EN: Create child logger with correlation context
 * VI: Tạo child logger với correlation context
 */
export const createCorrelationLogger = (req: Request): ReturnType<typeof logger.child> => {
  return logger.child({
    correlationId: req.correlationId,
    requestId: req.requestId,
  });
};

/**
 * EN: Middleware to add correlation ID to outgoing HTTP requests
 * VI: Middleware để thêm correlation ID vào outgoing HTTP requests
 */
export const correlationHttpClient = (correlationId?: string) => {
  return {
    headers: correlationId ? {
      [CORRELATION_ID_HEADER]: correlationId,
    } : {},
  };
};

/**
 * EN: Generate correlation ID
 * VI: Tạo correlation ID
 */
export const generateCorrelationId = (): string => {
  return randomUUID();
};

/**
 * EN: Middleware to validate correlation ID format
 * VI: Middleware để validate correlation ID format
 */
export const validateCorrelationId = (
  options: {
    required?: boolean;
    headerName?: string;
    uuidOnly?: boolean;
  } = {}
): ((req: Request, res: Response, next: NextFunction) => void) => {
  const {
    required = false,
    headerName = CORRELATION_ID_HEADER,
    uuidOnly = false,
  } = options;

  return (req: Request, res: Response, next: NextFunction): void => {
    const correlationId = req.headers[headerName.toLowerCase()] as string;

    if (required && !correlationId) {
      logger.warn(`Missing required correlation ID header: ${headerName}`, {
        path: req.path,
        method: req.method,
      });

      res.status(400).json({
        success: false,
        error: {
          code: 'MISSING_CORRELATION_ID',
          message: `Missing required header: ${headerName}`,
        },
        timestamp: new Date().toISOString(),
      });
      return;
    }

    if (correlationId && uuidOnly) {
      // EN: Basic UUID v4 validation
      // VI: Validation UUID v4 cơ bản
      const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
      if (!uuidRegex.test(correlationId)) {
        logger.warn(`Invalid correlation ID format: ${correlationId}`, {
          path: req.path,
          method: req.method,
        });

        res.status(400).json({
          success: false,
          error: {
            code: 'INVALID_CORRELATION_ID',
            message: `Invalid ${headerName} format`,
          },
          timestamp: new Date().toISOString(),
        });
        return;
      }
    }

    next();
  };
};