import { Request, Response, NextFunction } from 'express';
import client from 'prom-client';

import { getCorrelationId } from './correlation.middleware';

// EN: Create a Registry which registers the metrics
// VI: Tạo Registry để đăng ký các metrics
const register = client.register;

// EN: Collect default metrics
// VI: Thu thập các metrics mặc định
client.collectDefaultMetrics({ register });

// EN: Create histogram for HTTP request duration
// VI: Tạo histogram cho thời lượng request HTTP
const httpRequestDurationSeconds = new client.Histogram({
    name: 'http_request_duration_seconds',
    help: 'Duration of HTTP requests in seconds / Thời lượng request HTTP tính bằng giây',
    labelNames: ['method', 'route', 'status_code', 'correlation_id'],
    buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 2, 5, 10],
});

// EN: Create counter for total HTTP requests
// VI: Tạo counter cho tổng số request HTTP
const httpRequestsTotal = new client.Counter({
    name: 'http_requests_total',
    help: 'Total number of HTTP requests / Tổng số request HTTP',
    labelNames: ['method', 'route', 'status_code'],
});

// EN: Create gauge for active requests
// VI: Tạo gauge cho active requests
const activeRequests = new client.Gauge({
    name: 'http_active_requests',
    help: 'Number of active HTTP requests / Số lượng request HTTP đang hoạt động',
});

// EN: Create counter for HTTP request errors
// VI: Tạo counter cho lỗi HTTP request
const httpRequestErrors = new client.Counter({
    name: 'http_request_errors_total',
    help: 'Total number of HTTP request errors / Tổng số lỗi HTTP request',
    labelNames: ['method', 'route', 'error_type'],
});

// EN: Create histogram for request payload size
// VI: Tạo histogram cho kích thước payload request
const requestPayloadSize = new client.Histogram({
    name: 'http_request_payload_size_bytes',
    help: 'Size of HTTP request payloads in bytes / Kích thước payload request HTTP tính bằng bytes',
    labelNames: ['method', 'route'],
    buckets: [100, 1000, 10000, 100000, 1000000],
});

// EN: Create histogram for response payload size
// VI: Tạo histogram cho kích thước payload response
const responsePayloadSize = new client.Histogram({
    name: 'http_response_payload_size_bytes',
    help: 'Size of HTTP response payloads in bytes / Kích thước payload response HTTP tính bằng bytes',
    labelNames: ['method', 'route', 'status_code'],
    buckets: [100, 1000, 10000, 100000, 1000000],
});

/**
 * EN: Enhanced middleware to collect comprehensive HTTP metrics
 * VI: Middleware nâng cao để thu thập metrics HTTP toàn diện
 *
 * @param req - Express request
 * @param res - Express response
 * @param next - Next function
 */
export const metricsMiddleware = (req: Request, res: Response, next: NextFunction) => {
    // EN: Increment active requests
    // VI: Tăng active requests
    activeRequests.inc();

    // EN: Start timer
    // VI: Bắt đầu bấm giờ
    const start = process.hrtime.bigint();

    // EN: Track request payload size
    // VI: Theo dõi kích thước payload request
    const requestContentLength = parseInt(req.get('content-length') || '0', 10);
    if (requestContentLength > 0) {
        const route = req.route ? req.route.path : req.path;
        requestPayloadSize
            .labels(req.method, route)
            .observe(requestContentLength);
    }

    // EN: Store original response methods to intercept
    // VI: Lưu original response methods để intercept
    const originalWrite = res.write;
    let responseSize = 0;

    // EN: Override write method to track response size
    // VI: Override write method để track response size
    res.write = function(chunk: any, encodingOrCb?: BufferEncoding | ((error?: Error | null) => void), cb?: (error?: Error | null) => void): boolean {
        if (chunk && typeof chunk !== 'function') {
            responseSize += Buffer.isBuffer(chunk) ? chunk.length : Buffer.byteLength(String(chunk));
        }
        // EN: Handle different overloads of write method
        // VI: Xử lý các overloads khác nhau của write method
        if (typeof encodingOrCb === 'function') {
            return (originalWrite as any).call(this, chunk, encodingOrCb);
        }
        if (encodingOrCb !== undefined && cb !== undefined) {
            return (originalWrite as any).call(this, chunk, encodingOrCb, cb);
        }
        if (encodingOrCb !== undefined) {
            return (originalWrite as any).call(this, chunk, encodingOrCb);
        }
        return (originalWrite as any).call(this, chunk);
    };

    // EN: Listen for response finish event
    // VI: Lắng nghe sự kiện kết thúc response
    res.on('finish', () => {
        // EN: Decrement active requests
        // VI: Giảm active requests
        activeRequests.dec();

        // EN: Calculate duration
        // VI: Tính toán thời lượng
        const end = process.hrtime.bigint();
        const durationNanoseconds = end - start;
        const durationInSeconds = Number(durationNanoseconds) / 1e9;

        // EN: Normalize path to avoid high cardinality
        // VI: Chuẩn hóa path để tránh high cardinality
        const route = normalizeRoutePath(req);

        // EN: Get correlation ID for metrics
        // VI: Lấy correlation ID cho metrics
        const correlationId = getCorrelationId(req) || 'unknown';

        // EN: Record duration with correlation ID
        // VI: Ghi nhận thời lượng với correlation ID
        httpRequestDurationSeconds
            .labels(req.method, route, res.statusCode.toString(), correlationId)
            .observe(durationInSeconds);

        // EN: Increment request counter
        // VI: Tăng bộ đếm request
        httpRequestsTotal
            .labels(req.method, route, res.statusCode.toString())
            .inc();

        // EN: Record response payload size
        // VI: Ghi nhận kích thước payload response
        if (responseSize > 0) {
            responsePayloadSize
                .labels(req.method, route, res.statusCode.toString())
                .observe(responseSize);
        }

        // EN: Track errors
        // VI: Theo dõi lỗi
        if (res.statusCode >= 400) {
            const errorType = res.statusCode >= 500 ? 'server_error' : 'client_error';
            httpRequestErrors
                .labels(req.method, route, errorType)
                .inc();
        }
    });

    next();
};

/**
 * EN: Normalize route path to prevent high cardinality metrics
 * VI: Chuẩn hóa route path để ngăn high cardinality metrics
 */
function normalizeRoutePath(req: Request): string {
    // EN: If route is defined, use it (Express route pattern)
    // VI: Nếu route được định nghĩa, sử dụng nó (Express route pattern)
    if (req.route && req.route.path) {
        return req.route.path;
    }

    // EN: For API routes, normalize IDs
    // VI: Với API routes, normalize IDs
    let path = req.path;

    // EN: Replace UUIDs and numeric IDs with placeholders
    // VI: Thay thế UUIDs và numeric IDs bằng placeholders
    path = path.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, ':uuid');
    path = path.replace(/\d+/g, ':id');

    return path;
}
