/* eslint-disable @typescript-eslint/no-unused-vars */
/**
 * Health Scoring Algorithm - 6-dimensional product health framework
 * [3.1] Health Metric Framework
 * [3.2] Health Scoring Algorithm
 */

import type {
  ProductHealthScoreDoc,
  ProductHealthDimensions,
  HealthDimension,
  HealthAnomaly,
  HealthForecast,
} from './types.js';

export interface HealthMetricsInput {
  productId: string;
  date: Date;

  // Acquisition metrics
  newUsers: number;
  activationRateDay1: number;
  activationRateDay7: number;
  cac: number;

  // Activation metrics
  firstValueMomentRate: number;
  timeToFirstAction: number;
  onboardingCompletionRate: number;

  // Retention metrics
  dau: number;
  mau: number;
  day7Retention: number;
  day30Retention: number;

  // Engagement metrics
  avgSessionLength: number;
  sessionsPerUser: number;
  featureAdoption: Record<string, number>;

  // Revenue metrics
  mrr: number;
  arpu: number;
  churnRate: number;
  upgradeRate: number;

  // Stability metrics
  crashFreeRate: number;
  errorRate: number;
  avgLatency: number;
  uptimePercent: number;

  // Historical baselines (30-day averages)
  baselines: {
    dau: number;
    newUsers: number;
    activationRateDay1: number;
    day7Retention: number;
    avgSessionLength: number;
    mrr: number;
    errorRate: number;
  };
}

// Dimension weights (should sum to 1)
const DIMENSION_WEIGHTS = {
  acquisition: 0.15,
  activation: 0.15,
  retention: 0.25,
  engagement: 0.15,
  revenue: 0.15,
  stability: 0.15,
};

// Alert thresholds
const THRESHOLDS = {
  critical: 60,
  warning: 75,
};

export class HealthScoringEngine {
  /**
   * Calculate complete health score for a product
   */
  calculateHealthScore(input: HealthMetricsInput): Omit<ProductHealthScoreDoc, 'id' | 'ttl'> {
    const dimensions = this.calculateDimensions(input);
    const overallHealthScore = this.calculateOverallScore(dimensions);
    const healthStatus = this.determineHealthStatus(overallHealthScore, dimensions);

    const anomalies = this.detectAnomalies(input, dimensions);
    const forecasts = this.generateForecasts(input, dimensions);

    const vsBaseline7Day = this.calculateBaselineChange(input, 7);
    const vsBaseline30Day = this.calculateBaselineChange(input, 30);

    return {
      productId: input.productId,
      date: input.date.toISOString().split('T')[0],
      overallHealthScore,
      healthStatus,
      dimensions,
      anomalies,
      forecasts,
      vsBaseline7Day,
      vsBaseline30Day,
      createdAt: new Date().toISOString(),
    };
  }

  /**
   * Calculate individual dimension scores
   */
  private calculateDimensions(input: HealthMetricsInput): ProductHealthDimensions {
    return {
      acquisition: this.calculateAcquisitionDimension(input),
      activation: this.calculateActivationDimension(input),
      retention: this.calculateRetentionDimension(input),
      engagement: this.calculateEngagementDimension(input),
      revenue: this.calculateRevenueDimension(input),
      stability: this.calculateStabilityDimension(input),
    };
  }

  /**
   * Calculate acquisition dimension score
   */
  private calculateAcquisitionDimension(input: HealthMetricsInput): HealthDimension {
    const metrics = {
      newUsers: input.newUsers,
      activationRateDay1: input.activationRateDay1,
      activationRateDay7: input.activationRateDay7,
      cac: input.cac,
    };

    // Score components
    const newUserScore = this.zScoreNormalized(
      input.newUsers,
      input.baselines.newUsers,
      input.baselines.newUsers * 0.3
    );

    const activationScore = input.activationRateDay1 * 0.6 + input.activationRateDay7 * 0.4;

    const cacScore = this.normalizeInverse(input.cac, 100);

    const score = Math.round(
      newUserScore * 0.4 + activationScore * 100 * 0.4 + cacScore * 100 * 0.2
    );

    return {
      score: Math.max(0, Math.min(100, score)),
      metrics,
      trend: this.determineTrend(input.newUsers, input.baselines.newUsers),
    };
  }

  /**
   * Calculate activation dimension score
   */
  private calculateActivationDimension(input: HealthMetricsInput): HealthDimension {
    const metrics = {
      firstValueMomentRate: input.firstValueMomentRate,
      timeToFirstAction: input.timeToFirstAction,
      onboardingCompletionRate: input.onboardingCompletionRate,
    };

    const fvmScore = input.firstValueMomentRate * 100;
    const ttfScore = this.normalizeInverse(input.timeToFirstAction, 60) * 100;
    const onboardingScore = input.onboardingCompletionRate * 100;

    const score = Math.round(fvmScore * 0.4 + ttfScore * 0.3 + onboardingScore * 0.3);

    return {
      score: Math.max(0, Math.min(100, score)),
      metrics,
      trend: this.determineTrend(input.firstValueMomentRate, 0.5),
    };
  }

  /**
   * Calculate retention dimension score (highest weight)
   */
  private calculateRetentionDimension(input: HealthMetricsInput): HealthDimension {
    const dauMauRatio = input.mau > 0 ? input.dau / input.mau : 0;

    const metrics = {
      dau: input.dau,
      mau: input.mau,
      dauMauRatio,
      day7Retention: input.day7Retention,
      day30Retention: input.day30Retention,
    };

    // Retention score with DAU/MAU stickiness
    const retentionScore =
      input.day7Retention * 0.4 + input.day30Retention * 0.4 + dauMauRatio * 100 * 0.2;

    // DAU trend compared to baseline
    const dauScore = this.zScoreNormalized(
      input.dau,
      input.baselines.dau,
      input.baselines.dau * 0.2
    );

    const score = Math.round(retentionScore * 0.7 + dauScore * 0.3);

    return {
      score: Math.max(0, Math.min(100, score)),
      metrics,
      trend: this.determineTrend(input.dau, input.baselines.dau),
    };
  }

  /**
   * Calculate engagement dimension score
   */
  private calculateEngagementDimension(input: HealthMetricsInput): HealthDimension {
    const featureAdoptionAvg = Object.values(input.featureAdoption).length
      ? Object.values(input.featureAdoption).reduce((a, b) => a + b, 0) /
        Object.values(input.featureAdoption).length
      : 0;

    const metrics = {
      avgSessionLength: input.avgSessionLength,
      sessionsPerUser: input.sessionsPerUser,
      featureAdoptionAvg: featureAdoptionAvg,
    };

    const sessionLengthScore = this.normalizeLinear(input.avgSessionLength, 600) * 100;
    const sessionsPerUserScore = this.normalizeLinear(input.sessionsPerUser, 20) * 100;
    const featureAdoptionScore = featureAdoptionAvg * 100;

    const score = Math.round(
      sessionLengthScore * 0.4 + sessionsPerUserScore * 0.3 + featureAdoptionScore * 0.3
    );

    return {
      score: Math.max(0, Math.min(100, score)),
      metrics,
      trend: this.determineTrend(input.avgSessionLength, input.baselines.avgSessionLength),
    };
  }

  /**
   * Calculate revenue dimension score
   */
  private calculateRevenueDimension(input: HealthMetricsInput): HealthDimension {
    const metrics = {
      mrr: input.mrr,
      arpu: input.arpu,
      churnRate: input.churnRate,
      upgradeRate: input.upgradeRate,
    };

    const mrrScore = this.zScoreNormalized(
      input.mrr,
      input.baselines.mrr || input.mrr * 0.9,
      (input.baselines.mrr || input.mrr) * 0.2
    );

    const churnScore = this.normalizeInverse(input.churnRate * 100, 20);
    const upgradeScore = input.upgradeRate * 100;
    const arpuScore = this.normalizeLinear(input.arpu, 50) * 100;

    const score = Math.round(
      mrrScore * 0.4 + churnScore * 100 * 0.3 + upgradeScore * 0.2 + arpuScore * 0.1
    );

    return {
      score: Math.max(0, Math.min(100, score)),
      metrics,
      trend: this.determineTrend(input.mrr, input.baselines.mrr || input.mrr * 0.95),
    };
  }

  /**
   * Calculate stability dimension score
   */
  private calculateStabilityDimension(input: HealthMetricsInput): HealthDimension {
    const metrics = {
      crashFreeRate: input.crashFreeRate,
      errorRate: input.errorRate,
      avgLatency: input.avgLatency,
      uptimePercent: input.uptimePercent,
    };

    const crashFreeScore = input.crashFreeRate * 100;
    const errorRateScore = this.normalizeInverse(input.errorRate * 100, 5);
    const latencyScore = this.normalizeInverse(input.avgLatency, 1000);
    const uptimeScore = input.uptimePercent;

    const score = Math.round(
      crashFreeScore * 0.35 +
        errorRateScore * 100 * 0.35 +
        latencyScore * 100 * 0.15 +
        uptimeScore * 0.15
    );

    return {
      score: Math.max(0, Math.min(100, score)),
      metrics,
      trend: this.determineTrend(1 - input.errorRate, 1 - input.baselines.errorRate),
    };
  }

  /**
   * Calculate overall health score from dimensions
   */
  private calculateOverallScore(dimensions: ProductHealthDimensions): number {
    const weightedScore =
      dimensions.acquisition.score * DIMENSION_WEIGHTS.acquisition +
      dimensions.activation.score * DIMENSION_WEIGHTS.activation +
      dimensions.retention.score * DIMENSION_WEIGHTS.retention +
      dimensions.engagement.score * DIMENSION_WEIGHTS.engagement +
      dimensions.revenue.score * DIMENSION_WEIGHTS.revenue +
      dimensions.stability.score * DIMENSION_WEIGHTS.stability;

    return Math.round(weightedScore);
  }

  /**
   * Determine health status based on score and dimension variance
   */
  private determineHealthStatus(
    overallScore: number,
    dimensions: ProductHealthDimensions
  ): 'critical' | 'warning' | 'healthy' {
    // Check for critical score
    if (overallScore < THRESHOLDS.critical) return 'critical';

    // Check for warning score
    if (overallScore < THRESHOLDS.warning) return 'warning';

    // Check for critical dimension
    const criticalDimension = Object.values(dimensions).some(d => d.score < 40);
    if (criticalDimension) return 'warning';

    // Check for high variance (unstable health)
    const scores = Object.values(dimensions).map(d => d.score);
    const avg = scores.reduce((a, b) => a + b, 0) / scores.length;
    const variance = scores.reduce((sum, s) => sum + Math.pow(s - avg, 2), 0) / scores.length;
    const stdDev = Math.sqrt(variance);

    if (stdDev > 20 && overallScore < 85) return 'warning';

    return 'healthy';
  }

  /**
   * Detect anomalies in metrics
   */
  private detectAnomalies(
    input: HealthMetricsInput,
    dimensions: ProductHealthDimensions
  ): HealthAnomaly[] {
    const anomalies: HealthAnomaly[] = [];

    // Check each metric against baseline
    const checks: Array<{ metric: string; value: number; baseline: number; threshold: number }> = [
      { metric: 'dau', value: input.dau, baseline: input.baselines.dau, threshold: 0.2 },
      {
        metric: 'newUsers',
        value: input.newUsers,
        baseline: input.baselines.newUsers,
        threshold: 0.3,
      },
      {
        metric: 'activationRateDay1',
        value: input.activationRateDay1,
        baseline: input.baselines.activationRateDay1,
        threshold: 0.15,
      },
      { metric: 'day7Retention', value: input.day7Retention, baseline: 0.3, threshold: 0.2 },
      {
        metric: 'errorRate',
        value: input.errorRate,
        baseline: input.baselines.errorRate,
        threshold: 0.5,
      },
    ];

    for (const check of checks) {
      if (check.baseline > 0) {
        const deviation = (check.value - check.baseline) / check.baseline;

        if (Math.abs(deviation) > check.threshold) {
          anomalies.push({
            metric: check.metric,
            expectedValue: check.baseline,
            actualValue: check.value,
            deviationPercent: Math.round(deviation * 100),
            severity: Math.abs(deviation) > check.threshold * 2 ? 'critical' : 'warning',
            suggestedCause: this.suggestAnomalyCause(check.metric, deviation),
          });
        }
      }
    }

    // Check dimension scores for significant drops
    Object.entries(dimensions).forEach(([name, dimension]) => {
      if (dimension.score < 50) {
        anomalies.push({
          metric: `${name}_score`,
          expectedValue: 75,
          actualValue: dimension.score,
          deviationPercent: Math.round(((75 - dimension.score) / 75) * 100),
          severity: dimension.score < 40 ? 'critical' : 'warning',
          suggestedCause: `${name} metrics significantly below target`,
        });
      }
    });

    return anomalies;
  }

  /**
   * Generate health forecasts
   */
  private generateForecasts(
    input: HealthMetricsInput,
    dimensions: ProductHealthDimensions
  ): { next7Days: HealthForecast; next30Days: HealthForecast } {
    const currentScore = this.calculateOverallScore(dimensions);

    // Simple trend-based forecasting (in production, use Prophet/ARIMA)
    const trends: number[] = Object.values(dimensions).map(d =>
      d.trend === 'improving' ? 1 : d.trend === 'declining' ? -1 : 0
    );
    const avgTrend = trends.reduce((a: number, b: number) => a + b, 0) / trends.length;

    // 7-day forecast
    const trend7Days = avgTrend * 2; // Small movement
    const forecast7Days = Math.max(0, Math.min(100, currentScore + trend7Days));

    // 30-day forecast
    const trend30Days = avgTrend * 8; // Larger movement
    const forecast30Days = Math.max(0, Math.min(100, currentScore + trend30Days));

    return {
      next7Days: {
        expectedHealthScore: Math.round(forecast7Days),
        confidenceInterval: [
          Math.round(forecast7Days * 0.9),
          Math.round(Math.min(100, forecast7Days * 1.1)),
        ],
      },
      next30Days: {
        expectedHealthScore: Math.round(forecast30Days),
        confidenceInterval: [
          Math.round(forecast30Days * 0.8),
          Math.round(Math.min(100, forecast30Days * 1.15)),
        ],
      },
    };
  }

  /**
   * Calculate change vs baseline
   */
  private calculateBaselineChange(input: HealthMetricsInput, days: number): number {
    const currentHealth = this.estimateCurrentHealth(input);
    const baselineHealth = 75; // Simplified baseline

    return Math.round(((currentHealth - baselineHealth) / baselineHealth) * 100);
  }

  /**
   * Estimate current health from metrics
   */
  private estimateCurrentHealth(input: HealthMetricsInput): number {
    const scores = [
      input.activationRateDay1 * 100,
      input.day7Retention * 100,
      this.normalizeInverse(input.churnRate * 100, 20) * 100,
      input.crashFreeRate * 100,
    ];
    return scores.reduce((a, b) => a + b, 0) / scores.length;
  }

  /**
   * Suggest potential cause for anomaly
   */
  private suggestAnomalyCause(metric: string, deviation: number): string {
    const direction = deviation > 0 ? 'increase' : 'decrease';

    const causes: Record<string, string> = {
      dau: `Recent ${direction} may indicate marketing campaign impact or seasonal effect`,
      newUsers: `${direction} in user acquisition - check marketing channels`,
      activationRateDay1: `Onboarding changes may be affecting first-day engagement`,
      day7Retention: `Early retention ${direction} - review recent product changes`,
      errorRate: `Error rate spike - investigate recent deployments`,
    };

    return causes[metric] || `Unusual ${direction} detected in ${metric}`;
  }

  // Normalization helpers
  private zScoreNormalized(value: number, mean: number, stdDev: number): number {
    if (stdDev === 0) return 50;
    const zScore = (value - mean) / stdDev;
    // Convert to 0-100 scale (Z-score -3 to 3 maps to 0-100)
    return Math.max(0, Math.min(100, 50 + zScore * 16.67));
  }

  private normalizeLinear(value: number, max: number): number {
    return Math.min(1, Math.max(0, value / max));
  }

  private normalizeInverse(value: number, max: number): number {
    return 1 - Math.min(1, Math.max(0, value / max));
  }

  private determineTrend(current: number, baseline: number): 'improving' | 'stable' | 'declining' {
    if (baseline === 0) return 'stable';
    const change = (current - baseline) / baseline;
    if (change > 0.1) return 'improving';
    if (change < -0.1) return 'declining';
    return 'stable';
  }
}

export const healthScoringEngine = new HealthScoringEngine();
