/**
 * Welford's online algorithm for running mean and standard deviation.
 * Used for baseline cost calculations — handles concurrent updates
 * via DynamoDB conditional writes (BMad must-have).
 */
export class WelfordBaseline {
  count: number;
  mean: number;
  m2: number; // Sum of squares of differences from the mean

  constructor(count = 0, mean = 0, m2 = 0) {
    this.count = count;
    this.mean = mean;
    this.m2 = m2;
  }

  /** Add a new observation */
  update(value: number): void {
    this.count++;
    const delta = value - this.mean;
    this.mean += delta / this.count;
    const delta2 = value - this.mean;
    this.m2 += delta * delta2;
  }

  /** Population standard deviation */
  get stddev(): number {
    if (this.count < 2) return 0;
    return Math.sqrt(this.m2 / this.count);
  }

  /** Sample standard deviation */
  get sampleStddev(): number {
    if (this.count < 2) return 0;
    return Math.sqrt(this.m2 / (this.count - 1));
  }

  /** Serialize for DynamoDB storage */
  toJSON(): { count: number; mean: number; m2: number } {
    return { count: this.count, mean: this.mean, m2: this.m2 };
  }

  static fromJSON(data: { count: number; mean: number; m2: number }): WelfordBaseline {
    return new WelfordBaseline(data.count, data.mean, data.m2);
  }
}

/**
 * Anomaly scorer — computes a 0-100 score based on how far
 * the current cost deviates from the baseline.
 */
export function scoreAnomaly(input: {
  cost: number;
  mean: number;
  stddev: number;
}): number {
  const { cost, mean, stddev } = input;

  // Guard: no baseline yet
  if (mean === 0 && stddev === 0) return 0;

  // Guard: zero stddev (all values identical)
  if (stddev === 0) {
    return cost > mean ? 100 : 0;
  }

  // Z-score: how many standard deviations from mean
  const zScore = (cost - mean) / stddev;

  // Map z-score to 0-100 (clamped)
  // z=0 → 0, z=1 → 25, z=2 → 50, z=3 → 75, z>=4 → 100
  const score = Math.min(100, Math.max(0, zScore * 25));

  return Math.round(score * 100) / 100; // 2 decimal places
}

/**
 * Determine if a score warrants an alert.
 */
export function shouldAlert(score: number, threshold = 50): boolean {
  return score >= threshold;
}

export interface CostEvent {
  tenantId: string;
  accountId: string;
  resourceType: string; // e.g. 'ec2/m5.xlarge', 'rds/db.r5.large'
  hourlyCost: number;
  timestamp: number; // Unix ms
  region: string;
  tags: Record<string, string>;
}

export interface AnomalyResult {
  event: CostEvent;
  score: number;
  baseline: { mean: number; stddev: number; count: number };
  isAnomaly: boolean;
}
