import { ForecastData, ForecasterOptions } from './types'

interface TimeSeriesPoint {
  date: Date
  coverage: number
}

export class AdoptionForecaster {
  forecast(
    historicalData: TimeSeriesPoint[],
    options: ForecasterOptions = { historyDays: 90, forecastMonths: 6, regressionAlpha: 0.05 }
  ): ForecastData {
    if (historicalData.length < 2) {
      return {
        predictedCoverage6m: 0,
        confidence: 0,
        trend: 'stable',
        trendStrength: 0,
      }
    }

    const sorted = [...historicalData].sort((a, b) => a.date.getTime() - b.date.getTime())
    const regression = this.linearRegression(sorted)
    const trend = this.calculateTrend(regression.slope)
    const trendStrength = Math.abs(regression.slope) / 100

    const daysForecast = options.forecastMonths * 30
    const forecastDate = new Date()
    forecastDate.setDate(forecastDate.getDate() + daysForecast)

    const predictedCoverage = this.predictCoverage(regression, daysForecast)
    const confidence = this.calculateConfidence(regression, sorted, options.regressionAlpha)

    return {
      predictedCoverage6m: Math.min(100, Math.max(0, Math.round(predictedCoverage * 100) / 100)),
      confidence: Math.round(confidence * 10000) / 10000,
      trend,
      trendStrength,
    }
  }

  private linearRegression(data: TimeSeriesPoint[]): { slope: number; intercept: number; rSquared: number } {
    const n = data.length
    let sumX = 0
    let sumY = 0
    let sumXY = 0
    let sumX2 = 0
    let sumY2 = 0

    const baseDate = data[0].date.getTime()

    for (const point of data) {
      const x = (point.date.getTime() - baseDate) / (1000 * 60 * 60 * 24)
      const y = point.coverage

      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

    const ssTotal = sumY2 - (sumY * sumY) / n
    const ssResidual = sumY2 - intercept * sumY - slope * sumXY
    const rSquared = ssTotal > 0 ? 1 - ssResidual / ssTotal : 0

    return { slope, intercept, rSquared }
  }

  private predictCoverage(regression: { slope: number; intercept: number }, daysAhead: number): number {
    return regression.intercept + regression.slope * daysAhead
  }

  private calculateTrend(slope: number): 'up' | 'down' | 'stable' {
    if (slope > 0.1) return 'up'
    if (slope < -0.1) return 'down'
    return 'stable'
  }

  private calculateConfidence(
    regression: { slope: number; intercept: number; rSquared: number },
    data: TimeSeriesPoint[],
    alpha: number
  ): number {
    const rSquaredConfidence = regression.rSquared
    const dataPointConfidence = Math.min(1, data.length / 90)
    const trendConfidence = Math.max(0.5, Math.min(1, 1 - alpha))

    return (rSquaredConfidence * 0.4 + dataPointConfidence * 0.35 + trendConfidence * 0.25)
  }

  calculateMovingAverage(data: TimeSeriesPoint[], windowDays: number): TimeSeriesPoint[] {
    const result: TimeSeriesPoint[] = []

    for (let i = 0; i < data.length; i++) {
      const windowStart = data[i].date.getTime() - windowDays * 24 * 60 * 60 * 1000
      const windowData = data.filter((p) => p.date.getTime() >= windowStart && p.date.getTime() <= data[i].date.getTime())

      if (windowData.length > 0) {
        const avgCoverage = windowData.reduce((sum, p) => sum + p.coverage, 0) / windowData.length
        result.push({
          date: data[i].date,
          coverage: Math.round(avgCoverage * 100) / 100,
        })
      }
    }

    return result
  }

  detectAnomalies(
    data: TimeSeriesPoint[],
    stdDevThreshold: number = 2
  ): Array<{ date: Date; coverage: number; deviation: number }> {
    if (data.length < 3) return []

    const mean = data.reduce((sum, p) => sum + p.coverage, 0) / data.length
    const variance = data.reduce((sum, p) => sum + Math.pow(p.coverage - mean, 2), 0) / data.length
    const stdDev = Math.sqrt(variance)

    return data
      .filter((p) => Math.abs(p.coverage - mean) > stdDevThreshold * stdDev)
      .map((p) => ({
        date: p.date,
        coverage: p.coverage,
        deviation: (p.coverage - mean) / stdDev,
      }))
  }
}
