/* eslint-disable @typescript-eslint/no-unused-vars */
/**
 * Predictive Analytics Module Tests
 * [Target 20+ tests]
 */

import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { TelemetryEventDoc } from '../telemetry/types.js';
import {
  extractFeaturesFromTelemetry,
  extractProductSpecificFeatures,
  CompleteFeatureVector,
} from './feature-extractor.js';
import { ChurnModel } from './churn-model.js';
import { HealthScoringEngine, HealthMetricsInput } from './health-scoring.js';
import { AnomalyDetectionEngine } from './anomaly-detection.js';
import {
  ChurnPredictionInput,
  RiskSegmentEnum,
  ModelTypeEnum,
  PredictionHorizonEnum,
} from './types.js';

// ============================================================================
// Test Fixtures
// ============================================================================

function createMockTelemetryEvent(overrides: Partial<TelemetryEventDoc> = {}): TelemetryEventDoc {
  return {
    id: crypto.randomUUID(),
    pk: 'test:202601:ios',
    productId: 'test',
    userId: 'user_123',
    sessionId: 'session_456',
    platform: 'ios',
    channel: 'mobile_app',
    osFamily: 'ios',
    appVersion: '1.0.0',
    buildNumber: '100',
    releaseChannel: 'prod',
    eventType: 'info',
    module: 'test',
    feature: 'core',
    eventName: 'test_event',
    occurredAt: new Date().toISOString(),
    receivedAt: new Date().toISOString(),
    ttl: 30 * 24 * 60 * 60,
    ...overrides,
  };
}

function createMockEventSeries(userId: string, days: number): TelemetryEventDoc[] {
  const events: TelemetryEventDoc[] = [];
  const now = new Date();

  for (let d = 0; d < days; d++) {
    const date = new Date(now);
    date.setDate(date.getDate() - d);

    // Create 2-5 sessions per day
    const sessions = 2 + Math.floor(Math.random() * 3);
    for (let s = 0; s < sessions; s++) {
      events.push(
        createMockTelemetryEvent({
          userId,
          sessionId: `session_${d}_${s}`,
          occurredAt: date.toISOString(),
          feature: ['core', 'advanced', 'settings'][Math.floor(Math.random() * 3)],
          eventName: 'action_completed',
          metrics: { duration: 1000 + Math.random() * 5000 },
        })
      );
    }
  }

  return events;
}

// ============================================================================
// Feature Extraction Tests
// ============================================================================

describe('Feature Extractor', () => {
  it('should extract features from telemetry events', () => {
    const events = createMockEventSeries('user_123', 14);
    const features = extractFeaturesFromTelemetry('user_123', 'test', events);

    expect(features.userId).toBe('user_123');
    expect(features.productId).toBe('test');
    expect(features.featureSchemaVersion).toBe('1.0.0');
    expect(features.dataQualityScore).toBeGreaterThan(0);
    expect(features.dataQualityScore).toBeLessThanOrEqual(1);
  });

  it('should calculate behavior features correctly', () => {
    const now = new Date();
    const twentyFiveHoursAgo = new Date(now.getTime() - 25 * 60 * 60 * 1000);

    const events: TelemetryEventDoc[] = [
      createMockTelemetryEvent({
        sessionId: 'session_1',
        occurredAt: twentyFiveHoursAgo.toISOString(),
        feature: 'core',
        eventName: 'session_start',
        metrics: { duration: 300000 },
      }),
    ];

    const features = extractFeaturesFromTelemetry('user_123', 'test', events, now);

    expect(features.behavior.daysSinceLastSession).toBeGreaterThanOrEqual(1);
    expect(features.behavior.sessionsLast24Hours).toBe(0);
    expect(features.behavior.sessionsLast7Days).toBe(1);
  });

  it('should calculate engagement features correctly', () => {
    const events: TelemetryEventDoc[] = [
      createMockTelemetryEvent({
        feature: 'core',
        eventName: 'core_action_completed',
      }),
      createMockTelemetryEvent({
        feature: 'advanced',
        eventName: 'feature_used',
      }),
    ];

    const features = extractFeaturesFromTelemetry('user_123', 'test', events);

    expect(features.engagement.featureUsageDiversity).toBeGreaterThan(0);
    expect(features.behavior.uniqueFeaturesUsed).toBe(2);
  });

  it('should calculate rolling window features', () => {
    const events = createMockEventSeries('user_123', 30);
    const features = extractFeaturesFromTelemetry('user_123', 'test', events);

    expect(features.rolling.rollingAvgSessions7d).toBeGreaterThanOrEqual(0);
    expect(features.rolling.cohortSessionPercentile).toBeGreaterThanOrEqual(0);
    expect(features.rolling.cohortSessionPercentile).toBeLessThanOrEqual(100);
  });

  it('should extract product-specific features for NomGap', () => {
    const events: TelemetryEventDoc[] = [
      createMockTelemetryEvent({
        feature: 'fasting',
        eventName: 'fast_started',
      }),
      createMockTelemetryEvent({
        feature: 'fasting',
        eventName: 'fast_completed',
        context: { adhered: true },
      }),
    ];

    const features = extractProductSpecificFeatures(events, 'nomgap');

    expect(features.fastCompletionRate).toBe(1);
    expect(features.protocolAdherenceScore).toBeGreaterThanOrEqual(0);
  });

  it('should extract product-specific features for JarvisJr', () => {
    const events: TelemetryEventDoc[] = [
      createMockTelemetryEvent({
        feature: 'agent',
        eventName: 'session_started',
        context: { mode: 'voice', agentId: 'agent_1' },
      }),
      createMockTelemetryEvent({
        feature: 'agent',
        eventName: 'session_completed',
        context: { mode: 'text', agentId: 'agent_2' },
      }),
    ];

    const features = extractProductSpecificFeatures(events, 'jarvisjr');

    expect(features.agentDiversityScore).toBeGreaterThan(0);
    expect(features.voiceSessionRatio).toBe(0.5);
  });

  it('should handle empty telemetry gracefully', () => {
    const features = extractFeaturesFromTelemetry('user_123', 'test', [], new Date());

    expect(features.behavior.sessionsLast30Days).toBe(0);
    expect(features.engagement.featureUsageDiversity).toBe(0);
    expect(features.dataQualityScore).toBe(0);
  });

  it('should calculate data quality score based on completeness', () => {
    const richEvents = createMockEventSeries('user_123', 30);
    const features = extractFeaturesFromTelemetry('user_123', 'test', richEvents);

    expect(features.dataQualityScore).toBeGreaterThan(0.3);
  });
});

// ============================================================================
// Churn Model Tests
// ============================================================================

describe('Churn Model', () => {
  let model: ChurnModel;

  beforeEach(() => {
    model = new ChurnModel();
  });

  function createMockFeatures(): CompleteFeatureVector {
    const now = new Date();
    return {
      userId: 'user_123',
      productId: 'test',
      computedAt: now,
      observationWindow: { start: now, end: now },
      behavior: {
        daysSinceLastSession: 3,
        daysSinceLastCoreAction: 2,
        hoursSinceLastLogin: 72,
        sessionsLast24Hours: 1,
        sessionsLast7Days: 5,
        sessionsLast30Days: 20,
        avgSessionsPerWeek: 4,
        avgSessionsPerDay: 0.6,
        avgSessionDurationMinutes: 15,
        totalSessionDurationMinutes: 300,
        actionsPerSession: 8,
        uniqueFeaturesUsed: 5,
        sessionFrequencyTrend: 0.1,
        engagementDepthTrend: 0.05,
      },
      engagement: {
        featureUsageDiversity: 0.5,
        coreActionCompletionRate: 0.8,
        featureAdoptionVelocity: 1.2,
        powerUserScore: 0.3,
        onboardingCompletionRate: 0.9,
        firstValueMomentAchieved: true,
        timeToFirstValueHours: 2,
      },
      performance: {
        errorRateLast7Days: 0.01,
        errorRateLast30Days: 0.02,
        crashCountLast7Days: 0,
        crashCountLast30Days: 1,
        avgLatencyMs: 200,
        slowRequestCount: 2,
        timeoutCount: 0,
        errorRecoveryRate: 0.95,
        supportTicketCount: 0,
      },
      social: {
        shareCount: 3,
        inviteCount: 1,
        collaborationScore: 0.2,
        teamMemberCount: 2,
        integrationsConnected: 1,
        externalSharesLast30Days: 2,
      },
      revenue: {
        planTier: 1,
        lifetimeValue: 120,
        mrrContribution: 10,
        upgradeCount: 1,
        downgradeCount: 0,
        daysSinceLastPayment: 5,
        daysSincePlanChange: 60,
        supportTicketCount: 0,
        supportSatisfactionScore: 4.5,
        escalatedTicketCount: 0,
      },
      rolling: {
        rollingAvgSessions7d: 0.7,
        rollingAvgDuration7d: 15,
        rollingAvgActions7d: 8,
        wowSessionChange: 0.1,
        wowDurationChange: 0.05,
        wowActionsChange: 0.08,
        cohortSessionPercentile: 60,
        cohortEngagementPercentile: 55,
        cohortRetentionPercentile: 65,
      },
      productSpecific: {},
      timeWindows: {
        recent: {
          sessionCount: 1,
          totalDuration: 900,
          actionCount: 8,
          errorCount: 0,
          uniqueFeatures: ['core'],
        },
        weekly: {
          sessionCount: 5,
          totalDuration: 4500,
          actionCount: 40,
          errorCount: 0,
          uniqueFeatures: ['core', 'advanced'],
          daysActive: 4,
        },
        monthly: {
          sessionCount: 20,
          totalDuration: 18000,
          actionCount: 160,
          errorCount: 2,
          uniqueFeatures: ['core', 'advanced', 'settings'],
          daysActive: 15,
        },
        lifetime: {
          totalSessions: 100,
          totalDuration: 90000,
          totalActions: 800,
          totalErrors: 5,
          allFeaturesUsed: ['core', 'advanced', 'settings'],
          accountAgeDays: 90,
        },
      },
      featureSchemaVersion: '1.0.0',
      dataQualityScore: 0.85,
    };
  }

  it('should predict churn probability', () => {
    const features = createMockFeatures();
    const prediction = model.predict(features);

    expect(prediction.churnProbability).toBeGreaterThanOrEqual(0);
    expect(prediction.churnProbability).toBeLessThanOrEqual(1);
    expect(prediction.userId).toBe('user_123');
    expect(prediction.productId).toBe('test');
    expect(prediction.modelType).toBe('xgboost');
  });

  it('should categorize risk segments correctly', () => {
    const features = createMockFeatures();

    // Low engagement should increase churn risk
    features.engagement.featureUsageDiversity = 0.1;
    features.behavior.daysSinceLastSession = 20;

    const prediction = model.predict(features);

    expect(['critical', 'high', 'medium', 'low']).toContain(prediction.riskSegment);
  });

  it('should generate explanations with top risk factors', () => {
    const features = createMockFeatures();
    const prediction = model.predict(features);

    expect(prediction.explanation.topRiskFactors).toHaveLength(5);
    expect(prediction.explanation.nlExplanation).toContain('churn risk');
    expect(prediction.explanation.suggestedActions.length).toBeGreaterThan(0);
  });

  it('should calculate confidence score based on data quality', () => {
    const features = createMockFeatures();
    features.dataQualityScore = 0.3;

    const prediction = model.predict(features);

    expect(prediction.confidenceScore).toBeLessThanOrEqual(features.dataQualityScore + 0.1);
  });

  it('should predict batch correctly', () => {
    const features = [createMockFeatures(), createMockFeatures()];
    features[1].userId = 'user_456';

    const predictions = model.predictBatch(features);

    expect(predictions).toHaveLength(2);
    expect(predictions[0].userId).toBe('user_123');
    expect(predictions[1].userId).toBe('user_456');
  });

  it('should handle different prediction horizons', () => {
    const features = createMockFeatures();

    const p7 = model.predict(features, 7);
    const p30 = model.predict(features, 30);

    // Longer horizon should generally have different probability
    expect(p7.predictionHorizon).toBe(7);
    expect(p30.predictionHorizon).toBe(30);
  });

  it('should evaluate model performance', () => {
    const predictions = [
      { actual: true, predicted: 0.8 },
      { actual: false, predicted: 0.2 },
      { actual: true, predicted: 0.9 },
      { actual: false, predicted: 0.1 },
    ];

    const metrics = model.evaluateModel(predictions);

    expect(metrics.auc).toBeGreaterThan(0.5);
    expect(metrics.modelVersion).toBeDefined();
    expect(metrics.featureImportance.length).toBeGreaterThan(0);
  });
});

// ============================================================================
// Health Scoring Tests
// ============================================================================

describe('Health Scoring Engine', () => {
  let engine: HealthScoringEngine;

  beforeEach(() => {
    engine = new HealthScoringEngine();
  });

  function createMockHealthInput(): HealthMetricsInput {
    return {
      productId: 'test',
      date: new Date(),
      newUsers: 100,
      activationRateDay1: 0.6,
      activationRateDay7: 0.45,
      cac: 50,
      firstValueMomentRate: 0.55,
      timeToFirstAction: 120,
      onboardingCompletionRate: 0.75,
      dau: 1000,
      mau: 5000,
      day7Retention: 0.35,
      day30Retention: 0.25,
      avgSessionLength: 300,
      sessionsPerUser: 3,
      featureAdoption: { feature1: 0.5, feature2: 0.3 },
      mrr: 10000,
      arpu: 20,
      churnRate: 0.05,
      upgradeRate: 0.1,
      crashFreeRate: 0.99,
      errorRate: 0.01,
      avgLatency: 150,
      uptimePercent: 99.9,
      baselines: {
        dau: 900,
        newUsers: 90,
        activationRateDay1: 0.55,
        day7Retention: 0.33,
        avgSessionLength: 280,
        mrr: 9500,
        errorRate: 0.015,
      },
    };
  }

  it('should calculate overall health score', () => {
    const input = createMockHealthInput();
    const score = engine.calculateHealthScore(input);

    expect(score.overallHealthScore).toBeGreaterThanOrEqual(0);
    expect(score.overallHealthScore).toBeLessThanOrEqual(100);
    expect(score.productId).toBe('test');
  });

  it('should calculate all 6 dimension scores', () => {
    const input = createMockHealthInput();
    const score = engine.calculateHealthScore(input);

    expect(score.dimensions.acquisition.score).toBeGreaterThanOrEqual(0);
    expect(score.dimensions.activation.score).toBeGreaterThanOrEqual(0);
    expect(score.dimensions.retention.score).toBeGreaterThanOrEqual(0);
    expect(score.dimensions.engagement.score).toBeGreaterThanOrEqual(0);
    expect(score.dimensions.revenue.score).toBeGreaterThanOrEqual(0);
    expect(score.dimensions.stability.score).toBeGreaterThanOrEqual(0);
  });

  it('should detect health status correctly', () => {
    const input = createMockHealthInput();

    // Healthy case
    const healthyScore = engine.calculateHealthScore(input);
    expect(['healthy', 'warning', 'critical']).toContain(healthyScore.healthStatus);

    // Critical case
    input.day7Retention = 0.05;
    input.churnRate = 0.5;
    input.crashFreeRate = 0.8;
    const criticalScore = engine.calculateHealthScore(input);
    expect(criticalScore.healthStatus).toBe('critical');
  });

  it('should detect anomalies', () => {
    const input = createMockHealthInput();
    input.dau = 500; // 50% drop from baseline

    const score = engine.calculateHealthScore(input);

    expect(score.anomalies.length).toBeGreaterThan(0);
    expect(score.anomalies.some(a => a.metric === 'dau')).toBe(true);
  });

  it('should generate forecasts', () => {
    const input = createMockHealthInput();
    const score = engine.calculateHealthScore(input);

    expect(score.forecasts.next7Days.expectedHealthScore).toBeGreaterThanOrEqual(0);
    expect(score.forecasts.next7Days.confidenceInterval).toHaveLength(2);
    expect(score.forecasts.next30Days.expectedHealthScore).toBeGreaterThanOrEqual(0);
  });

  it('should calculate baseline comparisons', () => {
    const input = createMockHealthInput();
    const score = engine.calculateHealthScore(input);

    expect(typeof score.vsBaseline7Day).toBe('number');
    expect(typeof score.vsBaseline30Day).toBe('number');
  });

  it('should determine trends correctly', () => {
    const input = createMockHealthInput();

    // Improving trend
    input.dau = 1200;
    const improving = engine.calculateHealthScore(input);
    expect(improving.dimensions.retention.trend).toBe('improving');

    // Declining trend
    input.dau = 600;
    const declining = engine.calculateHealthScore(input);
    expect(declining.dimensions.retention.trend).toBe('declining');
  });
});

// ============================================================================
// Anomaly Detection Tests
// ============================================================================

describe('Anomaly Detection Engine', () => {
  let engine: AnomalyDetectionEngine;

  beforeEach(() => {
    engine = new AnomalyDetectionEngine();
  });

  function createTimeSeries(
    length: number,
    baseValue: number,
    noise: number
  ): Array<{ timestamp: Date; value: number }> {
    const series: Array<{ timestamp: Date; value: number }> = [];
    const now = new Date();

    for (let i = 0; i < length; i++) {
      const date = new Date(now);
      date.setDate(date.getDate() - (length - i));
      series.push({
        timestamp: date,
        value: baseValue + (Math.random() - 0.5) * noise,
      });
    }

    return series;
  }

  it('should detect anomalies in time series', () => {
    const series = createTimeSeries(30, 100, 10);

    // Add anomaly
    series[series.length - 1].value = 200;

    const anomalies = engine.detectAnomalies(series, 'test_metric');

    expect(anomalies.length).toBeGreaterThan(0);
  });

  it('should not detect anomalies in stable series', () => {
    // Use perfectly flat data to avoid any random outliers
    const series = Array.from({ length: 30 }, (_, i) => ({
      timestamp: new Date(Date.now() - (30 - i) * 24 * 60 * 60 * 1000),
      value: 100, // Constant value, no noise
    }));

    const anomalies = engine.detectAnomalies(series, 'test_metric');

    expect(anomalies.length).toBe(0);
  });

  it('should require minimum data for detection', () => {
    const shortSeries = createTimeSeries(5, 100, 10);

    const anomalies = engine.detectAnomalies(shortSeries, 'test_metric');

    expect(anomalies.length).toBe(0);
  });

  it('should forecast future values', () => {
    const series = createTimeSeries(30, 100, 10);

    const forecast = engine.forecast(series, 7);

    expect(forecast.forecast.length).toBe(7);
    expect(forecast.confidenceIntervals.length).toBe(7);
    expect(forecast.forecast[0].value).toBeGreaterThan(0);
  });

  it('should return empty forecast for insufficient data', () => {
    const shortSeries = createTimeSeries(5, 100, 10);

    const forecast = engine.forecast(shortSeries, 7);

    expect(forecast.forecast.length).toBe(0);
  });

  it('should detect multi-dimensional anomalies', () => {
    const metrics: Record<string, Array<{ timestamp: Date; value: number }>> = {
      metric1: createTimeSeries(30, 100, 10),
      metric2: createTimeSeries(30, 50, 5),
    };

    // Add correlated anomalies
    metrics.metric1[metrics.metric1.length - 1].value = 180;
    metrics.metric2[metrics.metric2.length - 1].value = 90;

    const anomalies = engine.detectMultiDimensionalAnomaly(metrics);

    expect(anomalies.length).toBeGreaterThan(0);
  });
});

// ============================================================================
// Integration Tests
// ============================================================================

describe('Predictive Analytics Integration', () => {
  it('should complete full prediction pipeline', () => {
    // 1. Extract features from telemetry
    const events = createMockEventSeries('user_123', 30);
    const features = extractFeaturesFromTelemetry('user_123', 'test', events);

    // 2. Run churn prediction
    const model = new ChurnModel();
    const prediction = model.predict(features);

    // 3. Verify prediction structure
    expect(prediction.churnProbability).toBeDefined();
    expect(prediction.riskSegment).toBeDefined();
    expect(prediction.explanation).toBeDefined();
    expect(prediction.explanation.topRiskFactors).toHaveLength(5);
    expect(prediction.explanation.suggestedActions.length).toBeGreaterThan(0);
  });

  it('should handle different product types', () => {
    const products = ['nomgap', 'jarvisjr', 'chronomind', 'mindlyst', 'peakpulse', 'lysnrai'];

    for (const productId of products) {
      const events = createMockEventSeries('user_123', 14);
      const features = extractFeaturesFromTelemetry('user_123', productId, events);
      const productSpecific = extractProductSpecificFeatures(events, productId);

      // Should complete without errors
      expect(features.productId).toBe(productId);
      expect(productSpecific).toBeDefined();
    }
  });
});

// ============================================================================
// Summary
// ============================================================================
// Total tests: 26+
// - Feature extraction: 7 tests
// - Churn model: 7 tests
// - Health scoring: 7 tests
// - Anomaly detection: 6 tests
// - Integration: 2 tests
