/**
 * Trend Analysis for Analytics
 * Provides trend detection, forecasting, and pattern analysis
 */

import { TimeSeriesDataPoint, TrendDirection, TrendData, TimeRange } from './types';
import { mean, standardDeviation, percentageChange, movingAverage } from './metrics';
import { format, parseISO, differenceInDays } from 'date-fns';

/**
 * Analyze trend from time series data
 */
export function analyzeTrend(data: TimeSeriesDataPoint[]): TrendData {
  if (data.length < 2) {
    return {
      metric: 'Unknown',
      currentValue: data[0]?.value || 0,
      previousValue: 0,
      change: 0,
      changePercent: 0,
      direction: 'stable',
      period: 'N/A',
    };
  }

  const midpoint = Math.floor(data.length / 2);
  const firstHalf = data.slice(0, midpoint);
  const secondHalf = data.slice(midpoint);

  const firstAvg = mean(firstHalf.map(d => d.value));
  const secondAvg = mean(secondHalf.map(d => d.value));
  const change = secondAvg - firstAvg;
  const changePercent = percentageChange(secondAvg, firstAvg);

  let direction: TrendDirection = 'stable';
  if (changePercent > 5) direction = 'up';
  else if (changePercent < -5) direction = 'down';

  return {
    metric: '',
    currentValue: secondAvg,
    previousValue: firstAvg,
    change,
    changePercent: Math.round(changePercent * 10) / 10,
    direction,
    period: `${data[0].timestamp} - ${data[data.length - 1].timestamp}`,
  };
}

/**
 * Calculate linear regression for forecasting
 */
export function linearRegression(data: TimeSeriesDataPoint[]): {
  slope: number;
  intercept: number;
  rSquared: number;
} {
  const n = data.length;
  if (n < 2) return { slope: 0, intercept: 0, rSquared: 0 };

  let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0, sumY2 = 0;

  data.forEach((point, i) => {
    const x = i;
    const y = point.value;
    sumX += x;
    sumY += y;
    sumXY += x * y;
    sumX2 += x * x;
    sumY2 += y * y;
  });

  const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
  const intercept = (sumY - slope * sumX) / n;

  // Calculate R-squared
  const yMean = sumY / n;
  let ssRes = 0, ssTot = 0;
  data.forEach((point, i) => {
    const predicted = slope * i + intercept;
    ssRes += Math.pow(point.value - predicted, 2);
    ssTot += Math.pow(point.value - yMean, 2);
  });
  const rSquared = ssTot === 0 ? 1 : 1 - ssRes / ssTot;

  return {
    slope: Math.round(slope * 1000) / 1000,
    intercept: Math.round(intercept * 1000) / 1000,
    rSquared: Math.round(rSquared * 1000) / 1000,
  };
}

/**
 * Forecast future values using linear regression
 */
export function forecast(
  data: TimeSeriesDataPoint[],
  periodsAhead: number
): TimeSeriesDataPoint[] {
  const regression = linearRegression(data);
  const predictions: TimeSeriesDataPoint[] = [];
  const lastIndex = data.length - 1;

  for (let i = 1; i <= periodsAhead; i++) {
    const predictedValue = regression.slope * (lastIndex + i) + regression.intercept;
    predictions.push({
      timestamp: `+${i}`,
      value: Math.max(0, Math.round(predictedValue * 100) / 100),
      label: `Forecast ${i}`,
    });
  }

  return predictions;
}

/**
 * Detect seasonality in data
 */
export function detectSeasonality(data: TimeSeriesDataPoint[]): {
  hasSeasonality: boolean;
  period: number;
  strength: number;
} {
  if (data.length < 14) {
    return { hasSeasonality: false, period: 0, strength: 0 };
  }

  // Try common periods: 7 days (weekly), 30 days (monthly)
  const periods = [7, 14, 30];
  let bestPeriod = 0;
  let bestCorrelation = 0;

  for (const period of periods) {
    if (data.length < period * 2) continue;

    let correlation = 0;
    let count = 0;

    for (let i = period; i < data.length; i++) {
      correlation += Math.abs(data[i].value - data[i - period].value);
      count++;
    }

    const avgCorr = count > 0 ? 1 - correlation / (count * mean(data.map(d => d.value))) : 0;
    if (avgCorr > bestCorrelation) {
      bestCorrelation = avgCorr;
      bestPeriod = period;
    }
  }

  return {
    hasSeasonality: bestCorrelation > 0.5,
    period: bestPeriod,
    strength: Math.round(bestCorrelation * 100) / 100,
  };
}

/**
 * Identify peaks and valleys in data
 */
export function findPeaksAndValleys(
  data: TimeSeriesDataPoint[],
  sensitivity: number = 1.5
): {
  peaks: TimeSeriesDataPoint[];
  valleys: TimeSeriesDataPoint[];
} {
  const peaks: TimeSeriesDataPoint[] = [];
  const valleys: TimeSeriesDataPoint[] = [];

  if (data.length < 3) return { peaks, valleys };

  const avg = mean(data.map(d => d.value));
  const std = standardDeviation(data.map(d => d.value));
  const threshold = std * sensitivity;

  for (let i = 1; i < data.length - 1; i++) {
    const prev = data[i - 1].value;
    const curr = data[i].value;
    const next = data[i + 1].value;

    // Peak detection
    if (curr > prev && curr > next && curr > avg + threshold) {
      peaks.push(data[i]);
    }

    // Valley detection
    if (curr < prev && curr < next && curr < avg - threshold) {
      valleys.push(data[i]);
    }
  }

  return { peaks, valleys };
}

/**
 * Calculate trend momentum
 */
export function calculateMomentum(data: TimeSeriesDataPoint[], lookback: number = 5): number {
  if (data.length < lookback) return 0;

  const recent = data.slice(-lookback);
  const older = data.slice(-lookback * 2, -lookback);

  if (older.length === 0) return 0;

  const recentAvg = mean(recent.map(d => d.value));
  const olderAvg = mean(older.map(d => d.value));

  return Math.round(((recentAvg - olderAvg) / olderAvg) * 100 * 10) / 10;
}

/**
 * Smooth data using exponential smoothing
 */
export function exponentialSmoothing(
  data: TimeSeriesDataPoint[],
  alpha: number = 0.3
): TimeSeriesDataPoint[] {
  if (data.length === 0) return [];

  const smoothed: TimeSeriesDataPoint[] = [data[0]];

  for (let i = 1; i < data.length; i++) {
    const smoothedValue = alpha * data[i].value + (1 - alpha) * smoothed[i - 1].value;
    smoothed.push({
      ...data[i],
      value: Math.round(smoothedValue * 100) / 100,
    });
  }

  return smoothed;
}

/**
 * Generate trend summary text
 */
export function generateTrendSummary(data: TimeSeriesDataPoint[], metricName: string): string {
  if (data.length < 2) return `Insufficient data for ${metricName} analysis.`;

  const trend = analyzeTrend(data);
  const regression = linearRegression(data);
  const { peaks, valleys } = findPeaksAndValleys(data);
  const momentum = calculateMomentum(data);

  let summary = '';

  // Overall direction
  if (trend.direction === 'up') {
    summary += `${metricName} is trending upward with a ${Math.abs(trend.changePercent).toFixed(1)}% increase. `;
  } else if (trend.direction === 'down') {
    summary += `${metricName} is trending downward with a ${Math.abs(trend.changePercent).toFixed(1)}% decrease. `;
  } else {
    summary += `${metricName} remains relatively stable. `;
  }

  // Trend strength
  if (regression.rSquared > 0.8) {
    summary += 'The trend is strong and consistent. ';
  } else if (regression.rSquared > 0.5) {
    summary += 'The trend is moderate with some variability. ';
  } else {
    summary += 'Data shows high variability. ';
  }

  // Momentum
  if (momentum > 10) {
    summary += 'Recent acceleration detected. ';
  } else if (momentum < -10) {
    summary += 'Recent deceleration detected. ';
  }

  // Notable events
  if (peaks.length > 0) {
    summary += `${peaks.length} notable peak(s) observed. `;
  }
  if (valleys.length > 0) {
    summary += `${valleys.length} notable dip(s) observed. `;
  }

  return summary.trim();
}

/**
 * Compare two time series for similarity
 */
export function compareTimeSeries(
  series1: TimeSeriesDataPoint[],
  series2: TimeSeriesDataPoint[]
): {
  correlation: number;
  leadLag: number;
  divergence: number;
} {
  // Ensure same length
  const minLength = Math.min(series1.length, series2.length);
  const s1 = series1.slice(0, minLength).map(d => d.value);
  const s2 = series2.slice(0, minLength).map(d => d.value);

  // Calculate correlation
  const mean1 = mean(s1);
  const mean2 = mean(s2);
  let numerator = 0, denom1 = 0, denom2 = 0;

  for (let i = 0; i < minLength; i++) {
    const d1 = s1[i] - mean1;
    const d2 = s2[i] - mean2;
    numerator += d1 * d2;
    denom1 += d1 * d1;
    denom2 += d2 * d2;
  }

  const correlation = denom1 * denom2 === 0 ? 0 : numerator / Math.sqrt(denom1 * denom2);

  // Calculate divergence (normalized difference)
  const divergence = mean(s1.map((v, i) => Math.abs(v - s2[i]))) / ((mean1 + mean2) / 2);

  return {
    correlation: Math.round(correlation * 1000) / 1000,
    leadLag: 0, // Simplified - full cross-correlation would require more computation
    divergence: Math.round(divergence * 1000) / 1000,
  };
}

/**
 * Get trend confidence level
 */
export function getTrendConfidence(data: TimeSeriesDataPoint[]): {
  level: 'high' | 'medium' | 'low';
  score: number;
  factors: string[];
} {
  const factors: string[] = [];
  let score = 50;

  // Data quantity factor
  if (data.length >= 30) {
    score += 15;
    factors.push('Sufficient data points');
  } else if (data.length >= 14) {
    score += 10;
    factors.push('Moderate data points');
  } else {
    score -= 10;
    factors.push('Limited data points');
  }

  // Consistency factor
  const std = standardDeviation(data.map(d => d.value));
  const avg = mean(data.map(d => d.value));
  const cv = avg !== 0 ? std / avg : 0;

  if (cv < 0.2) {
    score += 15;
    factors.push('Low variability');
  } else if (cv < 0.5) {
    score += 5;
    factors.push('Moderate variability');
  } else {
    score -= 10;
    factors.push('High variability');
  }

  // Trend strength
  const regression = linearRegression(data);
  if (regression.rSquared > 0.7) {
    score += 20;
    factors.push('Strong trend fit');
  } else if (regression.rSquared > 0.4) {
    score += 10;
    factors.push('Moderate trend fit');
  } else {
    score -= 5;
    factors.push('Weak trend fit');
  }

  const level = score >= 70 ? 'high' : score >= 50 ? 'medium' : 'low';

  return {
    level,
    score: Math.min(100, Math.max(0, score)),
    factors,
  };
}

/**
 * Calculate year-over-year comparison
 */
export function yearOverYearComparison(
  currentPeriod: TimeSeriesDataPoint[],
  previousPeriod: TimeSeriesDataPoint[]
): {
  currentTotal: number;
  previousTotal: number;
  change: number;
  changePercent: number;
  trend: TrendDirection;
} {
  const currentTotal = currentPeriod.reduce((sum, d) => sum + d.value, 0);
  const previousTotal = previousPeriod.reduce((sum, d) => sum + d.value, 0);
  const change = currentTotal - previousTotal;
  const changePercent = percentageChange(currentTotal, previousTotal);

  return {
    currentTotal: Math.round(currentTotal * 100) / 100,
    previousTotal: Math.round(previousTotal * 100) / 100,
    change: Math.round(change * 100) / 100,
    changePercent: Math.round(changePercent * 10) / 10,
    trend: changePercent > 5 ? 'up' : changePercent < -5 ? 'down' : 'stable',
  };
}
