import type {NextFunction, Request, Response} from 'express';
import {prisma} from '../database/prisma.js';
import {logger} from '../utils/logger.js';

/**
 * Check if request logging is enabled via environment variable
 * Set REQUEST_LOGGING=false to disable database logging entirely
 */
const REQUEST_LOGGING_ENABLED = process.env.REQUEST_LOGGING !== 'false';

/**
 * Endpoints to exclude from database logging
 *
 * Guidelines for what to skip:
 * - Health checks and monitoring endpoints (called by load balancers every few seconds)
 * - User session/auth endpoints (called on every page load in the dashboard)
 * - Real-time polling endpoints (called repeatedly for live updates)
 * - Static asset requests (should be served by CDN anyway)
 * - Internal/admin-only endpoints that don't need audit trails
 *
 * What TO log (valuable for analytics/debugging):
 * - Public API endpoints (/v1/send, /v1/track) - track customer usage
 * - Resource CRUD operations (contacts, templates, campaigns) - audit trail
 * - Authentication attempts (login, signup) - security monitoring
 * - Payment/billing operations - compliance
 * - Failed requests (always logged regardless of endpoint)
 */
const SKIP_LOGGING_PATHS = [
  // Health/status checks (called by load balancers constantly)
  '/health',
  '/',

  // User session endpoints (called on every dashboard page load)
  '/users/@me',
  '/users/@me/projects',
  '/users/me',
  '/users/me/projects',

  // Project switching (called frequently when user switches projects)
  '/users/@me/projects/:id',

  // Real-time polling/stats endpoints
  '/queue/stats',

  // Feature flags/config (called on app initialization)
  '/config',
  '/config/features',
  '/oauth-config',

  // Field introspection (called when building forms/filters)
  '/contacts/fields',
  '/events/names',
];

/**
 * Path patterns to exclude (regex)
 * For more complex matching like "/assets/*" or "*.json"
 */
const SKIP_LOGGING_PATTERNS = [
  /^\/assets\//i, // Static assets
  /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot)$/i, // Static files
];

/**
 * Check if a request should be logged to the database
 */
function shouldLogRequest(req: Request): boolean {
  const path = req.path;

  // Check exact path matches
  if (SKIP_LOGGING_PATHS.includes(path)) {
    return false;
  }

  // Check pattern matches
  if (SKIP_LOGGING_PATTERNS.some(pattern => pattern.test(path))) {
    return false;
  }

  // Log everything else
  return true;
}

/**
 * Middleware to log API requests to the database for historical tracking and analytics
 * This runs asynchronously and does not block the response
 *
 * IMPORTANT: Only logs important endpoints to avoid noise and reduce database load
 *
 * Configuration:
 * - Set REQUEST_LOGGING=false to disable entirely
 * - Modify SKIP_LOGGING_PATHS to exclude specific endpoints
 * - Modify SKIP_LOGGING_PATTERNS to exclude path patterns
 */
export const databaseRequestLogger = (req: Request, res: Response, next: NextFunction) => {
  // Check if logging is enabled
  if (!REQUEST_LOGGING_ENABLED) {
    return next();
  }

  // Skip logging for excluded endpoints
  if (!shouldLogRequest(req)) {
    return next();
  }

  const startTime = Date.now();

  // Capture the original res.json to log after response
  const originalJson = res.json.bind(res);
  res.json = function (body: unknown) {
    // Call original json method first
    const result = originalJson(body);

    // Log to database asynchronously (don't await, don't block response)
    void logRequestToDatabase(req, res, startTime, body);

    return result;
  };

  next();
};

/**
 * Log the request to the database
 * This runs asynchronously and failures are logged but don't affect the response
 */
async function logRequestToDatabase(
  req: Request,
  res: Response,
  startTime: number,
  responseBody: unknown,
): Promise<void> {
  try {
    const requestId = res.locals.requestId as string | undefined;
    const auth = res.locals.auth as {type?: string; userId?: string; projectId?: string} | undefined;

    const duration = Date.now() - startTime;
    const statusCode = res.statusCode;

    // Extract error information if this is an error response
    let errorCode: string | undefined;
    let errorMessage: string | undefined;

    if (
      statusCode >= 400 &&
      responseBody &&
      typeof responseBody === 'object' &&
      'error' in responseBody &&
      responseBody.error &&
      typeof responseBody.error === 'object'
    ) {
      const error = responseBody.error as {code?: string; message?: string};
      errorCode = error.code;
      errorMessage = error.message;
    }

    // Calculate request/response sizes (approximate)
    const requestSize = req.headers['content-length'] ? parseInt(req.headers['content-length'], 10) : undefined;
    const responseSize = JSON.stringify(responseBody).length;

    // Insert into database
    await prisma.apiRequest.create({
      data: {
        id: requestId || crypto.randomUUID(), // Use request ID as primary key
        method: req.method,
        path: req.path,
        statusCode,
        duration,
        projectId: auth?.projectId || null,
        userId: auth?.userId || null,
        authType: auth?.type || null,
        ip: req.ip || req.socket.remoteAddress || null,
        userAgent: req.get('user-agent') || null,
        errorCode: errorCode || null,
        errorMessage: errorMessage || null,
        requestSize: requestSize || null,
        responseSize,
      },
    });

    // Log success for debugging (only in development)
    if (process.env.NODE_ENV === 'development') {
      logger.debug('Request logged to database', {requestId}, res);
    }
  } catch (error) {
    // Log the error but don't throw - we don't want logging failures to affect the API
    logger.error('Failed to log request to database', error, {
      path: req.path,
      method: req.method,
    });
  }
}
