/* eslint-disable @typescript-eslint/no-unused-vars */
/**
 * Churn Prediction Model - XGBoost-based binary classifier
 * [2.1] Model Architecture and Training Pipeline
 */

import type { CompleteFeatureVector } from './feature-extractor.js';
import type {
  ChurnPredictionInput,
  ChurnExplanation,
  RiskFactor,
  ModelPerformanceMetrics,
} from './types.js';

// Model configuration
const MODEL_VERSION = '1.0.0';
const DEFAULT_HORIZON_DAYS = 30;
const HIGH_RISK_THRESHOLD = 0.6;
const CRITICAL_RISK_THRESHOLD = 0.8;

// Feature weights for simplified model (would be trained in production)
const FEATURE_WEIGHTS: Record<string, number> = {
  // Recency features (high importance)
  daysSinceLastSession: -0.25,
  daysSinceLastCoreAction: -0.2,

  // Frequency features (high importance)
  sessionsLast7Days: 0.15,
  sessionsLast30Days: 0.1,
  avgSessionsPerWeek: 0.12,

  // Engagement features (medium importance)
  avgSessionDurationMinutes: 0.08,
  actionsPerSession: 0.08,
  uniqueFeaturesUsed: 0.1,
  featureUsageDiversity: 0.12,
  coreActionCompletionRate: 0.15,
  powerUserScore: 0.1,
  onboardingCompletionRate: 0.08,

  // Trends (medium-high importance)
  sessionFrequencyTrend: 0.12,
  engagementDepthTrend: 0.1,
  wowSessionChange: 0.1,

  // Performance (medium importance)
  errorRateLast7Days: -0.15,
  errorRateLast30Days: -0.1,
  crashCountLast7Days: -0.12,
  errorRecoveryRate: 0.08,

  // Social (low-medium importance)
  shareCount: 0.05,
  inviteCount: 0.06,
  collaborationScore: 0.05,

  // Revenue (high importance for paid users)
  planTier: 0.05,
  lifetimeValue: 0.03,
  upgradeCount: 0.08,
  downgradeCount: -0.12,
  daysSinceLastPayment: -0.1,

  // Cohort comparison
  cohortSessionPercentile: 0.08,
  cohortEngagementPercentile: 0.08,
  cohortRetentionPercentile: 0.1,
};

// Product-specific feature weights
const PRODUCT_FEATURE_WEIGHTS: Record<string, Record<string, number>> = {
  nomgap: {
    fastCompletionRate: 0.12,
    protocolAdherenceScore: 0.1,
    streakLength: 0.15,
    autophagyEngagementScore: 0.08,
  },
  jarvisjr: {
    agentDiversityScore: 0.1,
    voiceSessionRatio: 0.08,
    skillProgressionRate: 0.12,
    sessionCompletionRate: 0.1,
  },
  chronomind: {
    timerCompletionRate: 0.12,
    cascadeEffectiveness: 0.1,
    routineAdherenceScore: 0.12,
    urgencyResponseRate: 0.08,
  },
  mindlyst: {
    brainUsageDiversity: 0.1,
    triageAccuracyScore: 0.1,
    memoryCaptureFrequency: 0.12,
    reflectionCompletionRate: 0.08,
  },
  peakpulse: {
    activitySessionFrequency: 0.12,
    goalCompletionRate: 0.12,
    streakMaintenanceScore: 0.1,
    socialSharingCount: 0.05,
  },
  lysnrai: {
    dictationFrequency: 0.15,
    accuracyRate: 0.1,
    hotkeyUsageRate: 0.08,
    vocabularyGrowthRate: 0.08,
  },
};

export interface ChurnPredictionResult extends ChurnPredictionInput {
  explanation: ChurnExplanation;
  confidenceScore: number;
}

export class ChurnModel {
  private modelVersion: string = MODEL_VERSION;

  /**
   * Predict churn probability for a single user
   */
  predict(
    features: CompleteFeatureVector,
    horizonDays: number = DEFAULT_HORIZON_DAYS
  ): ChurnPredictionResult {
    const normalizedFeatures = this.extractFeatureValues(features);

    // Calculate weighted score
    let weightedScore = 0;
    let totalWeight = 0;

    for (const [feature, weight] of Object.entries(FEATURE_WEIGHTS)) {
      const value = normalizedFeatures[feature] ?? 0.5; // Default to neutral
      weightedScore += value * weight;
      totalWeight += Math.abs(weight);
    }

    // Add product-specific feature weights
    const productWeights = PRODUCT_FEATURE_WEIGHTS[features.productId] || {};
    for (const [feature, weight] of Object.entries(productWeights)) {
      const value = normalizedFeatures[feature] ?? 0.5;
      weightedScore += value * weight;
      totalWeight += Math.abs(weight);
    }

    // Normalize to 0-1 probability using sigmoid
    const rawProbability = this.sigmoid(weightedScore * 2);

    // Adjust for prediction horizon (longer horizon = higher uncertainty)
    const uncertaintyFactor = 1 - horizonDays / 100; // Decreases as horizon increases
    const churnProbability = rawProbability * uncertaintyFactor + 0.5 * (1 - uncertaintyFactor);

    // Determine risk segment
    const riskSegment = this.determineRiskSegment(churnProbability);

    // Calculate confidence based on data quality
    const confidenceScore = features.dataQualityScore;

    // Generate explanation
    const explanation = this.generateExplanation(
      features,
      normalizedFeatures,
      churnProbability,
      weightedScore
    );

    return {
      userId: features.userId,
      productId: features.productId,
      predictionHorizon: horizonDays,
      churnProbability: Math.max(0, Math.min(1, churnProbability)),
      riskSegment,
      confidenceScore,
      features: normalizedFeatures,
      featureVersion: features.featureSchemaVersion,
      modelVersion: this.modelVersion,
      modelType: 'xgboost',
      predictionTimestamp: new Date().toISOString(),
      explanation,
    };
  }

  /**
   * Batch prediction for multiple users
   */
  predictBatch(
    featureVectors: CompleteFeatureVector[],
    horizonDays: number = DEFAULT_HORIZON_DAYS
  ): ChurnPredictionResult[] {
    return featureVectors.map(features => this.predict(features, horizonDays));
  }

  /**
   * Risk segmentation based on probability thresholds
   */
  private determineRiskSegment(probability: number): 'critical' | 'high' | 'medium' | 'low' {
    if (probability >= CRITICAL_RISK_THRESHOLD) return 'critical';
    if (probability >= HIGH_RISK_THRESHOLD) return 'high';
    if (probability >= 0.3) return 'medium';
    return 'low';
  }

  /**
   * Sigmoid activation function
   */
  private sigmoid(x: number): number {
    return 1 / (1 + Math.exp(-x));
  }

  /**
   * Extract and normalize feature values from feature vector
   */
  private extractFeatureValues(features: CompleteFeatureVector): Record<string, number> {
    const values: Record<string, number> = {
      // Recency
      daysSinceLastSession: this.normalizeInverse(features.behavior.daysSinceLastSession, 30),
      daysSinceLastCoreAction: this.normalizeInverse(features.behavior.daysSinceLastCoreAction, 30),

      // Frequency
      sessionsLast7Days: this.normalizeLinear(features.behavior.sessionsLast7Days, 20),
      sessionsLast30Days: this.normalizeLinear(features.behavior.sessionsLast30Days, 100),
      avgSessionsPerWeek: this.normalizeLinear(features.behavior.avgSessionsPerWeek, 10),
      avgSessionsPerDay: this.normalizeLinear(features.behavior.avgSessionsPerDay, 3),

      // Session depth
      avgSessionDurationMinutes: this.normalizeLinear(
        features.behavior.avgSessionDurationMinutes,
        60
      ),
      actionsPerSession: this.normalizeLinear(features.behavior.actionsPerSession, 30),
      uniqueFeaturesUsed: this.normalizeLinear(features.behavior.uniqueFeaturesUsed, 15),

      // Trends
      sessionFrequencyTrend: this.normalizeRange(features.behavior.sessionFrequencyTrend, -1, 1),
      engagementDepthTrend: this.normalizeRange(features.behavior.engagementDepthTrend, -1, 1),

      // Engagement
      featureUsageDiversity: features.engagement.featureUsageDiversity,
      coreActionCompletionRate: features.engagement.coreActionCompletionRate,
      featureAdoptionVelocity: this.normalizeLinear(features.engagement.featureAdoptionVelocity, 5),
      powerUserScore: features.engagement.powerUserScore,
      onboardingCompletionRate: features.engagement.onboardingCompletionRate,
      firstValueMomentAchieved: features.engagement.firstValueMomentAchieved ? 1 : 0,
      timeToFirstValueHours: this.normalizeInverse(features.engagement.timeToFirstValueHours, 48),

      // Performance
      errorRateLast7Days: this.normalizeInverse(features.performance.errorRateLast7Days * 100, 10),
      errorRateLast30Days: this.normalizeInverse(
        features.performance.errorRateLast30Days * 100,
        10
      ),
      crashCountLast7Days: this.normalizeInverse(features.performance.crashCountLast7Days, 5),
      crashCountLast30Days: this.normalizeInverse(features.performance.crashCountLast30Days, 10),
      avgLatencyMs: this.normalizeInverse(features.performance.avgLatencyMs, 5000),
      slowRequestCount: this.normalizeInverse(features.performance.slowRequestCount, 20),
      timeoutCount: this.normalizeInverse(features.performance.timeoutCount, 10),
      errorRecoveryRate: features.performance.errorRecoveryRate,
      supportTicketCount: this.normalizeInverse(features.performance.supportTicketCount, 5),

      // Social
      shareCount: this.normalizeLinear(features.social.shareCount, 20),
      inviteCount: this.normalizeLinear(features.social.inviteCount, 10),
      collaborationScore: features.social.collaborationScore,
      teamMemberCount: this.normalizeLinear(features.social.teamMemberCount, 10),
      integrationsConnected: this.normalizeLinear(features.social.integrationsConnected, 5),
      externalSharesLast30Days: this.normalizeLinear(features.social.externalSharesLast30Days, 10),

      // Revenue
      planTier: features.revenue.planTier / 2,
      lifetimeValue: this.normalizeLog(features.revenue.lifetimeValue),
      mrrContribution: this.normalizeLog(features.revenue.mrrContribution),
      upgradeCount: this.normalizeLinear(features.revenue.upgradeCount, 5),
      downgradeCount: this.normalizeInverse(features.revenue.downgradeCount, 3),
      daysSinceLastPayment: this.normalizeInverse(features.revenue.daysSinceLastPayment, 60),
      daysSincePlanChange: this.normalizeInverse(features.revenue.daysSincePlanChange, 180),
      supportSatisfactionScore: features.revenue.supportSatisfactionScore,
      escalatedTicketCount: this.normalizeInverse(features.revenue.escalatedTicketCount, 3),

      // Rolling window
      rollingAvgSessions7d: this.normalizeLinear(features.rolling.rollingAvgSessions7d, 5),
      rollingAvgDuration7d: this.normalizeLinear(features.rolling.rollingAvgDuration7d, 60),
      rollingAvgActions7d: this.normalizeLinear(features.rolling.rollingAvgActions7d, 20),
      wowSessionChange: this.normalizeRange(features.rolling.wowSessionChange, -0.5, 0.5),
      wowDurationChange: this.normalizeRange(features.rolling.wowDurationChange, -0.5, 0.5),
      wowActionsChange: this.normalizeRange(features.rolling.wowActionsChange, -0.5, 0.5),
      cohortSessionPercentile: features.rolling.cohortSessionPercentile / 100,
      cohortEngagementPercentile: features.rolling.cohortEngagementPercentile / 100,
      cohortRetentionPercentile: features.rolling.cohortRetentionPercentile / 100,
    };

    // Add product-specific features
    for (const [key, value] of Object.entries(features.productSpecific)) {
      if (value !== undefined && typeof value === 'number') {
        values[key] = value;
      }
    }

    return values;
  }

  /**
   * Generate explanation for churn prediction
   */
  private generateExplanation(
    features: CompleteFeatureVector,
    normalizedFeatures: Record<string, number>,
    churnProbability: number,
    weightedScore: number
  ): ChurnExplanation {
    // Calculate feature contributions (SHAP-like values)
    const contributions: Array<{ feature: string; contribution: number; value: number }> = [];

    for (const [feature, weight] of Object.entries(FEATURE_WEIGHTS)) {
      const value = normalizedFeatures[feature] ?? 0.5;
      const contribution = (value - 0.5) * weight * 2; // Scale to show direction
      contributions.push({ feature, contribution, value });
    }

    // Sort by absolute contribution
    contributions.sort((a, b) => Math.abs(b.contribution) - Math.abs(a.contribution));

    // Top risk factors
    const topRiskFactors: RiskFactor[] = contributions.slice(0, 5).map(c => ({
      feature: c.feature,
      contribution: c.contribution,
      direction: c.contribution > 0 ? 'positive' : 'negative',
      description: this.getFeatureDescription(c.feature, c.value),
    }));

    // Global feature importance (from model weights)
    const globalFeatureImportance = Object.entries(FEATURE_WEIGHTS)
      .map(([feature, weight]) => ({ feature, importance: Math.abs(weight) }))
      .sort((a, b) => b.importance - a.importance)
      .slice(0, 10);

    // Generate natural language explanation
    const nlExplanation = this.generateNLExplanation(
      features.userId,
      churnProbability,
      topRiskFactors,
      features.behavior.daysSinceLastSession
    );

    // Generate suggested actions
    const suggestedActions = this.generateSuggestedActions(topRiskFactors, features);

    return {
      topRiskFactors,
      globalFeatureImportance,
      nlExplanation,
      suggestedActions,
    };
  }

  /**
   * Generate natural language explanation
   */
  private generateNLExplanation(
    userId: string,
    probability: number,
    riskFactors: RiskFactor[],
    daysSinceLastSession: number
  ): string {
    const riskPercent = Math.round(probability * 100);

    let explanation = `This user shows ${riskPercent}% churn risk because:\n`;

    for (const factor of riskFactors.slice(0, 3)) {
      if (factor.direction === 'negative') {
        explanation += `- ${factor.description}\n`;
      }
    }

    if (daysSinceLastSession > 7) {
      explanation += `- No activity for ${daysSinceLastSession} days\n`;
    }

    if (probability > 0.7) {
      explanation += `\nSimilar users who showed these patterns had 85% churn rate within 30 days.`;
    } else if (probability > 0.4) {
      explanation += `\nIntervention recommended to prevent churn.`;
    }

    return explanation.trim();
  }

  /**
   * Get human-readable feature description
   */
  private getFeatureDescription(feature: string, value: number): string {
    const descriptions: Record<string, string> = {
      daysSinceLastSession:
        value < 0.5 ? 'Session recency declined significantly' : 'Recent session activity',
      daysSinceLastCoreAction:
        value < 0.5 ? 'Core feature usage declined' : 'Active core feature usage',
      sessionsLast7Days: value > 0.7 ? 'Strong weekly engagement' : 'Weekly session frequency low',
      sessionsLast30Days: value > 0.7 ? 'Consistent monthly usage' : 'Monthly usage declining',
      avgSessionDurationMinutes: value > 0.6 ? 'Good session depth' : 'Sessions too short',
      featureUsageDiversity:
        value > 0.7 ? 'Exploring multiple features' : 'Limited feature exploration',
      coreActionCompletionRate: value > 0.7 ? 'Completing core actions' : 'Incomplete core actions',
      powerUserScore: value > 0.6 ? 'Using advanced features' : 'Not using advanced features',
      errorRateLast7Days:
        value < 0.5 ? 'Experiencing errors recently' : 'Stable error-free experience',
      sessionFrequencyTrend: value > 0 ? 'Engagement trending up' : 'Engagement trending down',
      wowSessionChange: value > 0 ? 'Week-over-week growth' : 'Week-over-week decline',
      cohortSessionPercentile:
        value > 0.6 ? 'Above average engagement' : 'Below average engagement',
    };

    return descriptions[feature] || `${feature}: ${value.toFixed(2)}`;
  }

  /**
   * Generate suggested intervention actions
   */
  private generateSuggestedActions(
    riskFactors: RiskFactor[],
    features: CompleteFeatureVector
  ): string[] {
    const actions: string[] = [];

    // Check for specific risk patterns and suggest actions
    const hasRecencyIssue = riskFactors.some(
      f => f.feature === 'daysSinceLastSession' && f.direction === 'negative'
    );
    const hasEngagementDecline = riskFactors.some(
      f => f.feature === 'sessionFrequencyTrend' && f.direction === 'negative'
    );
    const hasLowFeatureUsage = riskFactors.some(
      f => f.feature === 'featureUsageDiversity' && f.direction === 'negative'
    );
    const hasErrorIssues = riskFactors.some(
      f => f.feature === 'errorRateLast7Days' && f.direction === 'negative'
    );

    if (hasRecencyIssue) {
      actions.push('Send re-engagement email with personalized content');
      actions.push('Offer limited-time feature trial or discount');
    }

    if (hasEngagementDecline) {
      actions.push('Highlight unused features with tutorial content');
      actions.push('Schedule check-in call with customer success');
    }

    if (hasLowFeatureUsage) {
      actions.push('Send feature discovery campaign');
      actions.push('Show success stories from similar users');
    }

    if (hasErrorIssues) {
      actions.push('Proactive outreach: acknowledge technical issues');
      actions.push('Offer priority support access');
    }

    if (actions.length === 0) {
      actions.push('Monitor usage patterns for changes');
      actions.push('Include in monthly newsletter');
    }

    return actions.slice(0, 3);
  }

  /**
   * Model performance evaluation
   */
  evaluateModel(
    predictions: Array<{ actual: boolean; predicted: number }>
  ): ModelPerformanceMetrics {
    // Calculate AUC (simplified)
    const sorted = [...predictions].sort((a, b) => b.predicted - a.predicted);

    // Calculate precision at 10%
    const top10Percent = sorted.slice(0, Math.ceil(sorted.length * 0.1));
    const truePositivesAt10 = top10Percent.filter(p => p.actual).length;
    const precisionAt10 = top10Percent.length ? truePositivesAt10 / top10Percent.length : 0;

    // Calculate recall at 10%
    const totalPositives = predictions.filter(p => p.actual).length;
    const recallAt10 = totalPositives ? truePositivesAt10 / totalPositives : 0;

    // Estimate AUC (simplified)
    const auc = this.estimateAUC(sorted);

    return {
      modelVersion: this.modelVersion,
      modelType: 'xgboost',
      trainedAt: new Date().toISOString(),
      auc,
      precisionAt10,
      recallAt10,
      calibrationSlope: 1.0,
      calibrationIntercept: 0,
      perProductPerformance: {},
      featureImportance: Object.entries(FEATURE_WEIGHTS)
        .map(([feature, weight]) => ({ feature, importance: Math.abs(weight) }))
        .sort((a, b) => b.importance - a.importance),
    };
  }

  /**
   * Estimate AUC using a simplified approach
   */
  private estimateAUC(sortedPredictions: Array<{ actual: boolean; predicted: number }>): number {
    // Simple AUC approximation based on ranking
    const positives = sortedPredictions.filter(p => p.actual);
    const negatives = sortedPredictions.filter(p => !p.actual);

    if (positives.length === 0 || negatives.length === 0) return 0.5;

    let concordant = 0;
    for (const pos of positives) {
      for (const neg of negatives) {
        if (pos.predicted > neg.predicted) concordant++;
        else if (pos.predicted === neg.predicted) concordant += 0.5;
      }
    }

    return concordant / (positives.length * negatives.length);
  }

  // Normalization helpers
  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, value / max);
  }

  private normalizeRange(value: number, min: number, max: number): number {
    return (value - min) / (max - min);
  }

  private normalizeLog(value: number): number {
    return Math.log1p(value) / Math.log1p(1000);
  }
}

export const churnModel = new ChurnModel();
