const { sequelize } = require('../config/database');
const logger = require('../config/logger');

// Metrics storage
const metrics = {
  requests: {
    total: 0,
    success: 0,
    error: 0,
    byEndpoint: {},
    byStatusCode: {},
  },
  responseTime: {
    total: 0,
    count: 0,
    min: Infinity,
    max: 0,
  },
  errors: [],
  startTime: Date.now(),
};

// Alert thresholds
const alertThresholds = {
  errorRatePercent: 5, // Alert if error rate exceeds 5%
  responseTimeMs: 500, // Alert if avg response time exceeds 500ms
  dbPoolUsagePercent: 80, // Alert if DB pool usage exceeds 80%
  memoryUsagePercent: 90, // Alert if memory usage exceeds 90%
};

/**
 * Record a request metric
 */
function recordRequest(endpoint, statusCode, responseTimeMs) {
  metrics.requests.total++;

  if (statusCode >= 200 && statusCode < 400) {
    metrics.requests.success++;
  } else {
    metrics.requests.error++;
  }

  // Track by endpoint
  if (!metrics.requests.byEndpoint[endpoint]) {
    metrics.requests.byEndpoint[endpoint] = { total: 0, success: 0, error: 0 };
  }
  metrics.requests.byEndpoint[endpoint].total++;
  if (statusCode >= 200 && statusCode < 400) {
    metrics.requests.byEndpoint[endpoint].success++;
  } else {
    metrics.requests.byEndpoint[endpoint].error++;
  }

  // Track by status code
  if (!metrics.requests.byStatusCode[statusCode]) {
    metrics.requests.byStatusCode[statusCode] = 0;
  }
  metrics.requests.byStatusCode[statusCode]++;

  // Track response time
  metrics.responseTime.total += responseTimeMs;
  metrics.responseTime.count++;
  metrics.responseTime.min = Math.min(metrics.responseTime.min, responseTimeMs);
  metrics.responseTime.max = Math.max(metrics.responseTime.max, responseTimeMs);
}

/**
 * Record an error for alerting
 */
function recordError(error, context = {}) {
  const errorRecord = {
    timestamp: new Date().toISOString(),
    message: error.message,
    stack: error.stack,
    context,
  };

  metrics.errors.push(errorRecord);

  // Keep only last 100 errors
  if (metrics.errors.length > 100) {
    metrics.errors.shift();
  }

  // Check if we need to trigger an alert
  checkAlerts();
}

/**
 * Check database connection health
 */
async function checkDatabaseHealth() {
  try {
    await sequelize.authenticate();
    const pool = sequelize.connectionManager.pool;

    return {
      status: 'healthy',
      pool: {
        size: pool ? pool.size : 0,
        available: pool ? pool.available : 0,
        pending: pool ? pool.pending : 0,
        max: sequelize.options.pool?.max || 10,
        min: sequelize.options.pool?.min || 0,
      },
    };
  } catch (error) {
    logger.error('Database health check failed:', error);
    return {
      status: 'unhealthy',
      error: error.message,
    };
  }
}

/**
 * Check Redis connection health
 */
async function checkRedisHealth() {
  try {
    // Try to get Redis client - may not be initialized in test environment
    const { getRedisClient } = require('../config/redis');
    const client = getRedisClient();

    const pingResult = await client.ping();
    const info = await client.info('clients');

    // Parse connected clients from info
    const connectedClientsMatch = info.match(/connected_clients:(\d+)/);
    const connectedClients = connectedClientsMatch ? parseInt(connectedClientsMatch[1], 10) : 0;

    return {
      status: pingResult === 'PONG' ? 'healthy' : 'unhealthy',
      ping: pingResult,
      connectedClients,
    };
  } catch (error) {
    // Redis might not be initialized in test environment
    if (error.message.includes('not initialized')) {
      return {
        status: 'not_initialized',
        message: 'Redis client not initialized',
      };
    }
    logger.error('Redis health check failed:', error);
    return {
      status: 'unhealthy',
      error: error.message,
    };
  }
}


/**
 * Get memory usage statistics
 */
function getMemoryUsage() {
  const memUsage = process.memoryUsage();
  return {
    heapUsed: memUsage.heapUsed,
    heapTotal: memUsage.heapTotal,
    external: memUsage.external,
    rss: memUsage.rss,
    heapUsedMB: Math.round(memUsage.heapUsed / 1024 / 1024 * 100) / 100,
    heapTotalMB: Math.round(memUsage.heapTotal / 1024 / 1024 * 100) / 100,
    rssMB: Math.round(memUsage.rss / 1024 / 1024 * 100) / 100,
  };
}

/**
 * Get all metrics
 */
function getMetrics() {
  const avgResponseTime =
    metrics.responseTime.count > 0
      ? Math.round(metrics.responseTime.total / metrics.responseTime.count)
      : 0;

  const errorRate =
    metrics.requests.total > 0
      ? Math.round((metrics.requests.error / metrics.requests.total) * 10000) / 100
      : 0;

  return {
    uptime: Math.round((Date.now() - metrics.startTime) / 1000),
    requests: {
      total: metrics.requests.total,
      success: metrics.requests.success,
      error: metrics.requests.error,
      errorRate: `${errorRate}%`,
      byStatusCode: metrics.requests.byStatusCode,
    },
    responseTime: {
      average: avgResponseTime,
      min: metrics.responseTime.min === Infinity ? 0 : metrics.responseTime.min,
      max: metrics.responseTime.max,
    },
    memory: getMemoryUsage(),
    recentErrors: metrics.errors.slice(-10),
  };
}

/**
 * Check alerts and log warnings
 */
function checkAlerts() {
  const currentMetrics = getMetrics();

  // Check error rate
  const errorRate = parseFloat(currentMetrics.requests.errorRate);
  if (errorRate > alertThresholds.errorRatePercent && metrics.requests.total > 100) {
    logger.warn(`ALERT: High error rate detected: ${errorRate}%`, {
      threshold: alertThresholds.errorRatePercent,
      current: errorRate,
    });
  }

  // Check response time
  if (currentMetrics.responseTime.average > alertThresholds.responseTimeMs) {
    logger.warn(`ALERT: High average response time: ${currentMetrics.responseTime.average}ms`, {
      threshold: alertThresholds.responseTimeMs,
      current: currentMetrics.responseTime.average,
    });
  }

  // Check memory usage
  const memUsage = currentMetrics.memory;
  const heapUsagePercent = (memUsage.heapUsed / memUsage.heapTotal) * 100;
  if (heapUsagePercent > alertThresholds.memoryUsagePercent) {
    logger.warn(`ALERT: High memory usage: ${heapUsagePercent.toFixed(2)}%`, {
      threshold: alertThresholds.memoryUsagePercent,
      current: heapUsagePercent,
    });
  }
}

/**
 * Check database pool usage and alert if needed
 */
async function checkDatabasePoolAlert() {
  const dbHealth = await checkDatabaseHealth();
  if (dbHealth.status === 'healthy' && dbHealth.pool) {
    const usagePercent = ((dbHealth.pool.size - dbHealth.pool.available) / dbHealth.pool.max) * 100;
    if (usagePercent > alertThresholds.dbPoolUsagePercent) {
      logger.warn(`ALERT: High database pool usage: ${usagePercent.toFixed(2)}%`, {
        threshold: alertThresholds.dbPoolUsagePercent,
        current: usagePercent,
        pool: dbHealth.pool,
      });
    }
  }
}

/**
 * Get comprehensive health status
 */
async function getHealthStatus() {
  const [dbHealth, redisHealth] = await Promise.all([
    checkDatabaseHealth(),
    checkRedisHealth(),
  ]);

  const overallStatus =
    dbHealth.status === 'healthy' &&
    (redisHealth.status === 'healthy' || redisHealth.status === 'not_initialized')
      ? 'healthy'
      : 'degraded';

  return {
    status: overallStatus,
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    version: process.env.npm_package_version || '1.0.0',
    nodeVersion: process.version,
    services: {
      database: dbHealth,
      redis: redisHealth,
    },
    memory: getMemoryUsage(),
  };
}

/**
 * Reset metrics (useful for testing)
 */
function resetMetrics() {
  metrics.requests = {
    total: 0,
    success: 0,
    error: 0,
    byEndpoint: {},
    byStatusCode: {},
  };
  metrics.responseTime = {
    total: 0,
    count: 0,
    min: Infinity,
    max: 0,
  };
  metrics.errors = [];
  metrics.startTime = Date.now();
}

module.exports = {
  recordRequest,
  recordError,
  checkDatabaseHealth,
  checkRedisHealth,
  getMemoryUsage,
  getMetrics,
  getHealthStatus,
  checkAlerts,
  checkDatabasePoolAlert,
  resetMetrics,
  alertThresholds,
};
