/**
 * Anomaly Detection Service
 * Detects anomalies in resource metrics and behavior patterns
 */

import { Context } from '../types/context'
import { getDb } from '../db'

export interface Anomaly {
  id: string
  resourceId: string
  metricType: string
  severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'
  anomalyType: 'SPIKE' | 'DROP' | 'PATTERN' | 'THRESHOLD'
  value: number
  expectedValue?: number
  deviation: number
  timestamp: Date
  description: string
  recommendation?: string
}

export interface AnomalyDetectionConfig {
  resourceId: string
  metricType: string
  threshold?: number
  windowSize?: number // Time window in minutes
  sensitivity?: 'LOW' | 'MEDIUM' | 'HIGH'
}

/**
 * Detect anomalies in resource metrics using statistical methods
 */
export async function detectAnomalies(
  context: Context,
  config: AnomalyDetectionConfig
): Promise<Anomaly[]> {
  const db = getDb()
  const anomalies: Anomaly[] = []

  // Get historical metrics for the resource
  const windowSize = config.windowSize || 60 // Default 1 hour
  const endTime = new Date()
  const startTime = new Date(endTime.getTime() - windowSize * 60 * 1000)

  // Query metrics from database
  const metricsQuery = `
    SELECT timestamp, value, labels
    FROM metrics
    WHERE resource_id = $1
      AND metric_type = $2
      AND timestamp >= $3
      AND timestamp <= $4
    ORDER BY timestamp ASC
  `

  const result = await db.query(metricsQuery, [
    config.resourceId,
    config.metricType,
    startTime,
    endTime,
  ])

  if (result.rows.length < 10) {
    // Not enough data for anomaly detection
    return []
  }

  const values = result.rows.map((row) => parseFloat(row.value))
  const timestamps = result.rows.map((row) => new Date(row.timestamp))

  // Calculate statistical baseline
  const mean = values.reduce((a, b) => a + b, 0) / values.length
  const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length
  const stdDev = Math.sqrt(variance)

  // Determine threshold based on sensitivity
  const sensitivityMultiplier = {
    LOW: 3.0,
    MEDIUM: 2.5,
    HIGH: 2.0,
  }[config.sensitivity || 'MEDIUM']

  const threshold = config.threshold || mean + (sensitivityMultiplier * stdDev)
  const lowerThreshold = mean - (sensitivityMultiplier * stdDev)

  // Detect anomalies
  for (let i = 0; i < values.length; i++) {
    const value = values[i]
    const timestamp = timestamps[i]

    // Check for threshold violations
    if (value > threshold) {
      const deviation = ((value - mean) / stdDev) * 100
      const severity = determineSeverity(deviation, 'SPIKE')

      anomalies.push({
        id: `anomaly-${config.resourceId}-${timestamp.getTime()}`,
        resourceId: config.resourceId,
        metricType: config.metricType,
        severity,
        anomalyType: 'SPIKE',
        value,
        expectedValue: mean,
        deviation,
        timestamp,
        description: `${config.metricType} spike detected: ${value.toFixed(2)} (expected: ${mean.toFixed(2)})`,
        recommendation: getRecommendation(config.metricType, 'SPIKE', value, mean),
      })
    } else if (value < lowerThreshold) {
      const deviation = ((mean - value) / stdDev) * 100
      const severity = determineSeverity(deviation, 'DROP')

      anomalies.push({
        id: `anomaly-${config.resourceId}-${timestamp.getTime()}`,
        resourceId: config.resourceId,
        metricType: config.metricType,
        severity,
        anomalyType: 'DROP',
        value,
        expectedValue: mean,
        deviation,
        timestamp,
        description: `${config.metricType} drop detected: ${value.toFixed(2)} (expected: ${mean.toFixed(2)})`,
        recommendation: getRecommendation(config.metricType, 'DROP', value, mean),
      })
    }

    // Detect pattern anomalies (sudden changes)
    if (i > 0) {
      const previousValue = values[i - 1]
      const change = Math.abs(value - previousValue)
      const changePercent = (change / previousValue) * 100

      if (changePercent > 50) {
        // Significant change detected
        const deviation = changePercent
        const severity = determineSeverity(deviation, 'PATTERN')

        anomalies.push({
          id: `anomaly-pattern-${config.resourceId}-${timestamp.getTime()}`,
          resourceId: config.resourceId,
          metricType: config.metricType,
          severity,
          anomalyType: 'PATTERN',
          value,
          expectedValue: previousValue,
          deviation,
          timestamp,
          description: `Sudden ${config.metricType} change: ${changePercent.toFixed(2)}% change from ${previousValue.toFixed(2)} to ${value.toFixed(2)}`,
          recommendation: getRecommendation(config.metricType, 'PATTERN', value, previousValue),
        })
      }
    }
  }

  // Store anomalies in database
  if (anomalies.length > 0) {
    await storeAnomalies(context, anomalies)
  }

  return anomalies
}

/**
 * Detect anomalies across all resources
 */
export async function detectAnomaliesForAllResources(
  context: Context,
  metricTypes: string[] = ['CPU_USAGE', 'MEMORY_USAGE', 'ERROR_RATE']
): Promise<Anomaly[]> {
  const db = getDb()
  const allAnomalies: Anomaly[] = []

  // Get all active resources
  const resourcesQuery = `
    SELECT DISTINCT resource_id, metric_type
    FROM metrics
    WHERE timestamp >= NOW() - INTERVAL '1 hour'
  `

  const result = await db.query(resourcesQuery)

  const resourceMetricPairs = new Set<string>()
  for (const row of result.rows) {
    const key = `${row.resource_id}:${row.metric_type}`
    if (!resourceMetricPairs.has(key) && metricTypes.includes(row.metric_type)) {
      resourceMetricPairs.add(key)

      const anomalies = await detectAnomalies(context, {
        resourceId: row.resource_id,
        metricType: row.metric_type,
      })

      allAnomalies.push(...anomalies)
    }
  }

  return allAnomalies
}

/**
 * Store anomalies in database
 */
async function storeAnomalies(context: Context, anomalies: Anomaly[]): Promise<void> {
  const db = getDb()

  for (const anomaly of anomalies) {
    await db.query(
      `INSERT INTO anomalies (
        id, resource_id, metric_type, severity, anomaly_type,
        value, expected_value, deviation, timestamp, description, recommendation
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
      ON CONFLICT (id) DO UPDATE SET
        severity = EXCLUDED.severity,
        value = EXCLUDED.value,
        deviation = EXCLUDED.deviation,
        updated_at = NOW()`,
      [
        anomaly.id,
        anomaly.resourceId,
        anomaly.metricType,
        anomaly.severity,
        anomaly.anomalyType,
        anomaly.value,
        anomaly.expectedValue,
        anomaly.deviation,
        anomaly.timestamp,
        anomaly.description,
        anomaly.recommendation,
      ]
    )
  }
}

/**
 * Determine severity based on deviation
 */
function determineSeverity(deviation: number, type: string): Anomaly['severity'] {
  const absDeviation = Math.abs(deviation)

  if (absDeviation > 200) return 'CRITICAL'
  if (absDeviation > 100) return 'HIGH'
  if (absDeviation > 50) return 'MEDIUM'
  return 'LOW'
}

/**
 * Get recommendation based on anomaly type
 */
function getRecommendation(
  metricType: string,
  anomalyType: string,
  currentValue: number,
  expectedValue: number
): string {
  if (metricType === 'CPU_USAGE' && anomalyType === 'SPIKE') {
    return 'Consider scaling up resources or optimizing application performance'
  }
  if (metricType === 'MEMORY_USAGE' && anomalyType === 'SPIKE') {
    return 'Check for memory leaks or increase memory allocation'
  }
  if (metricType === 'ERROR_RATE' && anomalyType === 'SPIKE') {
    return 'Investigate application errors and check logs for issues'
  }
  if (metricType === 'NETWORK_THROUGHPUT' && anomalyType === 'SPIKE') {
    return 'Monitor network capacity and consider load balancing'
  }
  if (anomalyType === 'DROP') {
    return 'Verify resource is functioning correctly and check for connectivity issues'
  }
  return 'Review resource configuration and monitor for continued anomalies'
}

/**
 * Get recent anomalies for a resource
 */
export async function getAnomalies(
  context: Context,
  resourceId?: string,
  limit: number = 100
): Promise<Anomaly[]> {
  const db = getDb()

  let query = `
    SELECT * FROM anomalies
    WHERE 1=1
  `
  const params: any[] = []
  let paramCount = 1

  if (resourceId) {
    query += ` AND resource_id = $${paramCount}`
    params.push(resourceId)
    paramCount++
  }

  query += ` ORDER BY timestamp DESC LIMIT $${paramCount}`
  params.push(limit)

  const result = await db.query(query, params)

  return result.rows.map((row) => ({
    id: row.id,
    resourceId: row.resource_id,
    metricType: row.metric_type,
    severity: row.severity,
    anomalyType: row.anomaly_type,
    value: parseFloat(row.value),
    expectedValue: row.expected_value ? parseFloat(row.expected_value) : undefined,
    deviation: parseFloat(row.deviation),
    timestamp: new Date(row.timestamp),
    description: row.description,
    recommendation: row.recommendation,
  }))
}

