/**
 * Service de détection d'anomalies dans les métriques Jira
 * Analyse les patterns et tendances pour identifier des problèmes potentiels
 */

import {
  JiraAnalytics,
  SprintVelocity,
  CycleTimeByType,
  AssigneeWorkload,
} from '@/lib/types';
import { getToday } from '@/lib/date-utils';

export interface JiraAnomaly {
  id: string;
  type: 'velocity' | 'cycle_time' | 'workload' | 'completion' | 'blockers';
  severity: 'low' | 'medium' | 'high' | 'critical';
  title: string;
  description: string;
  value: number;
  threshold: number;
  recommendation: string;
  affectedItems: string[];
  timestamp: string;
}

export interface AnomalyDetectionConfig {
  velocityVarianceThreshold: number; // % de variance acceptable
  cycleTimeThreshold: number; // multiplicateur du cycle time moyen
  workloadImbalanceThreshold: number; // ratio max entre assignees
  completionRateThreshold: number; // % minimum de completion
  stalledItemsThreshold: number; // jours sans changement
}

export class JiraAnomalyDetectionService {
  private readonly defaultConfig: AnomalyDetectionConfig = {
    velocityVarianceThreshold: 30, // 30% de variance
    cycleTimeThreshold: 2.0, // 2x le cycle time moyen
    workloadImbalanceThreshold: 3.0, // 3:1 ratio max
    completionRateThreshold: 70, // 70% completion minimum
    stalledItemsThreshold: 7, // 7 jours
  };

  constructor(private config: Partial<AnomalyDetectionConfig> = {}) {
    this.config = { ...this.defaultConfig, ...config };
  }

  /**
   * Analyse toutes les métriques et détecte les anomalies
   */
  async detectAnomalies(analytics: JiraAnalytics): Promise<JiraAnomaly[]> {
    const anomalies: JiraAnomaly[] = [];
    const timestamp = getToday().toISOString();

    // 1. Détection d'anomalies de vélocité
    const velocityAnomalies = this.detectVelocityAnomalies(
      analytics.velocityMetrics,
      timestamp
    );
    anomalies.push(...velocityAnomalies);

    // 2. Détection d'anomalies de cycle time
    const cycleTimeAnomalies = this.detectCycleTimeAnomalies(
      analytics.cycleTimeMetrics,
      timestamp
    );
    anomalies.push(...cycleTimeAnomalies);

    // 3. Détection de déséquilibres de charge
    const workloadAnomalies = this.detectWorkloadAnomalies(
      analytics.workInProgress.byAssignee,
      timestamp
    );
    anomalies.push(...workloadAnomalies);

    // 4. Détection de problèmes de completion
    const completionAnomalies = this.detectCompletionAnomalies(
      analytics.velocityMetrics,
      timestamp
    );
    anomalies.push(...completionAnomalies);

    // Trier par sévérité
    return anomalies.sort(
      (a, b) =>
        this.getSeverityWeight(b.severity) - this.getSeverityWeight(a.severity)
    );
  }

  /**
   * Détecte les anomalies de vélocité (variance excessive, tendance négative)
   */
  private detectVelocityAnomalies(
    velocityMetrics: {
      sprintHistory: SprintVelocity[];
      averageVelocity: number;
    },
    timestamp: string
  ): JiraAnomaly[] {
    const anomalies: JiraAnomaly[] = [];
    const { sprintHistory, averageVelocity } = velocityMetrics;

    if (sprintHistory.length < 3) return anomalies;

    // Calcul de la variance de vélocité
    const velocities = sprintHistory.map(
      (s: SprintVelocity) => s.completedPoints
    );
    const variance = this.calculateVariance(velocities);
    const variancePercent = (Math.sqrt(variance) / averageVelocity) * 100;

    if (
      variancePercent >
      (this.config.velocityVarianceThreshold ??
        this.defaultConfig.velocityVarianceThreshold)
    ) {
      anomalies.push({
        id: `velocity-variance-${Date.now()}`,
        type: 'velocity',
        severity: variancePercent > 50 ? 'high' : 'medium',
        title: 'Vélocité très variable',
        description: `La vélocité de l'équipe varie de ${variancePercent.toFixed(1)}% autour de la moyenne`,
        value: variancePercent,
        threshold:
          this.config.velocityVarianceThreshold ??
          this.defaultConfig.velocityVarianceThreshold,
        recommendation:
          'Analysez les facteurs causant cette instabilité : estimation, complexité, blockers',
        affectedItems: sprintHistory
          .slice(-3)
          .map((s: SprintVelocity) => s.sprintName),
        timestamp,
      });
    }

    // Détection de tendance décroissante
    const recentSprints = sprintHistory.slice(-3);
    const isDecreasing = recentSprints.every(
      (sprint: SprintVelocity, i: number) =>
        i === 0 || sprint.completedPoints < recentSprints[i - 1].completedPoints
    );

    if (isDecreasing && recentSprints.length >= 3) {
      const decline =
        ((recentSprints[0].completedPoints -
          recentSprints[recentSprints.length - 1].completedPoints) /
          recentSprints[0].completedPoints) *
        100;

      anomalies.push({
        id: `velocity-decline-${Date.now()}`,
        type: 'velocity',
        severity: decline > 30 ? 'critical' : 'high',
        title: 'Vélocité en déclin',
        description: `La vélocité a diminué de ${decline.toFixed(1)}% sur les 3 derniers sprints`,
        value: decline,
        threshold: 0,
        recommendation:
          "Identifiez les causes : technical debt, complexité croissante, ou problèmes d'équipe",
        affectedItems: recentSprints.map((s: SprintVelocity) => s.sprintName),
        timestamp,
      });
    }

    return anomalies;
  }

  /**
   * Détecte les anomalies de cycle time (temps excessifs, types problématiques)
   */
  private detectCycleTimeAnomalies(
    cycleTimeMetrics: {
      averageCycleTime: number;
      cycleTimeByType: CycleTimeByType[];
    },
    timestamp: string
  ): JiraAnomaly[] {
    const anomalies: JiraAnomaly[] = [];
    const { averageCycleTime, cycleTimeByType } = cycleTimeMetrics;

    // Détection des types avec cycle time excessif
    cycleTimeByType.forEach((typeMetrics: CycleTimeByType) => {
      const ratio = typeMetrics.averageDays / averageCycleTime;

      if (
        ratio >
        (this.config.cycleTimeThreshold ??
          this.defaultConfig.cycleTimeThreshold)
      ) {
        anomalies.push({
          id: `cycle-time-${typeMetrics.issueType}-${Date.now()}`,
          type: 'cycle_time',
          severity: ratio > 3 ? 'high' : 'medium',
          title: `Cycle time excessif - ${typeMetrics.issueType}`,
          description: `Le type "${typeMetrics.issueType}" prend ${ratio.toFixed(1)}x plus de temps que la moyenne`,
          value: typeMetrics.averageDays,
          threshold:
            averageCycleTime *
            (this.config.cycleTimeThreshold ??
              this.defaultConfig.cycleTimeThreshold),
          recommendation:
            'Analysez les blockers spécifiques à ce type de ticket',
          affectedItems: [typeMetrics.issueType],
          timestamp,
        });
      }
    });

    // Détection cycle time global excessif (> 14 jours)
    if (averageCycleTime > 14) {
      anomalies.push({
        id: `global-cycle-time-${Date.now()}`,
        type: 'cycle_time',
        severity: averageCycleTime > 21 ? 'critical' : 'high',
        title: 'Cycle time global élevé',
        description: `Le cycle time moyen de ${averageCycleTime.toFixed(1)} jours est préoccupant`,
        value: averageCycleTime,
        threshold: 14,
        recommendation:
          "Réduisez la taille des tâches et identifiez les goulots d'étranglement",
        affectedItems: ['Projet global'],
        timestamp,
      });
    }

    return anomalies;
  }

  /**
   * Détecte les déséquilibres de charge de travail
   */
  private detectWorkloadAnomalies(
    assigneeWorkloads: AssigneeWorkload[],
    timestamp: string
  ): JiraAnomaly[] {
    const anomalies: JiraAnomaly[] = [];

    if (assigneeWorkloads.length < 2) return anomalies;

    const workloads = assigneeWorkloads.map((a) => a.totalActive);
    const maxWorkload = Math.max(...workloads);
    const minWorkload = Math.min(...workloads.filter((w) => w > 0));

    if (minWorkload === 0) return anomalies; // Éviter division par zéro

    const imbalanceRatio = maxWorkload / minWorkload;

    if (
      imbalanceRatio >
      (this.config.workloadImbalanceThreshold ??
        this.defaultConfig.workloadImbalanceThreshold)
    ) {
      const overloadedMember = assigneeWorkloads.find(
        (a) => a.totalActive === maxWorkload
      );
      const underloadedMember = assigneeWorkloads.find(
        (a) => a.totalActive === minWorkload
      );

      anomalies.push({
        id: `workload-imbalance-${Date.now()}`,
        type: 'workload',
        severity: imbalanceRatio > 5 ? 'high' : 'medium',
        title: 'Déséquilibre de charge',
        description: `Ratio de ${imbalanceRatio.toFixed(1)}:1 entre membres les plus/moins chargés`,
        value: imbalanceRatio,
        threshold:
          this.config.workloadImbalanceThreshold ??
          this.defaultConfig.workloadImbalanceThreshold,
        recommendation:
          'Redistribuez les tâches pour équilibrer la charge de travail',
        affectedItems: [
          `Surchargé: ${overloadedMember?.displayName} (${maxWorkload} tâches)`,
          `Sous-chargé: ${underloadedMember?.displayName} (${minWorkload} tâches)`,
        ],
        timestamp,
      });
    }

    // Détection de membres avec trop de tâches en cours
    assigneeWorkloads.forEach((assignee) => {
      if (assignee.inProgressCount > 5) {
        anomalies.push({
          id: `wip-limit-${assignee.assignee}-${Date.now()}`,
          type: 'workload',
          severity: assignee.inProgressCount > 8 ? 'high' : 'medium',
          title: 'WIP limite dépassée',
          description: `${assignee.displayName} a ${assignee.inProgressCount} tâches en cours`,
          value: assignee.inProgressCount,
          threshold: 5,
          recommendation:
            'Limitez le WIP à 3-5 tâches par personne pour améliorer le focus',
          affectedItems: [assignee.displayName],
          timestamp,
        });
      }
    });

    return anomalies;
  }

  /**
   * Détecte les problèmes de completion rate
   */
  private detectCompletionAnomalies(
    velocityMetrics: { sprintHistory: SprintVelocity[] },
    timestamp: string
  ): JiraAnomaly[] {
    const anomalies: JiraAnomaly[] = [];
    const { sprintHistory } = velocityMetrics;

    if (sprintHistory.length === 0) return anomalies;

    // Analyse des 3 derniers sprints
    const recentSprints = sprintHistory.slice(-3);
    const avgCompletionRate =
      recentSprints.reduce(
        (sum: number, sprint: SprintVelocity) => sum + sprint.completionRate,
        0
      ) / recentSprints.length;

    if (
      avgCompletionRate <
      (this.config.completionRateThreshold ??
        this.defaultConfig.completionRateThreshold)
    ) {
      anomalies.push({
        id: `low-completion-rate-${Date.now()}`,
        type: 'completion',
        severity: avgCompletionRate < 50 ? 'critical' : 'high',
        title: 'Taux de completion faible',
        description: `Taux de completion moyen de ${avgCompletionRate.toFixed(1)}% sur les derniers sprints`,
        value: avgCompletionRate,
        threshold:
          this.config.completionRateThreshold ??
          this.defaultConfig.completionRateThreshold,
        recommendation: "Revoyez la planification et l'estimation des sprints",
        affectedItems: recentSprints.map(
          (s: SprintVelocity) =>
            `${s.sprintName}: ${s.completionRate.toFixed(1)}%`
        ),
        timestamp,
      });
    }

    return anomalies;
  }

  /**
   * Calcule la variance d'un tableau de nombres
   */
  private calculateVariance(numbers: number[]): number {
    const mean = numbers.reduce((sum, num) => sum + num, 0) / numbers.length;
    const squaredDiffs = numbers.map((num) => Math.pow(num - mean, 2));
    return squaredDiffs.reduce((sum, diff) => sum + diff, 0) / numbers.length;
  }

  /**
   * Retourne le poids numérique d'une sévérité pour le tri
   */
  private getSeverityWeight(severity: string): number {
    switch (severity) {
      case 'critical':
        return 4;
      case 'high':
        return 3;
      case 'medium':
        return 2;
      case 'low':
        return 1;
      default:
        return 0;
    }
  }

  /**
   * Met à jour la configuration de détection
   */
  updateConfig(newConfig: Partial<AnomalyDetectionConfig>): void {
    this.config = { ...this.config, ...newConfig };
  }

  /**
   * Retourne la configuration actuelle
   */
  getConfig(): AnomalyDetectionConfig {
    return { ...this.defaultConfig, ...this.config };
  }
}

export const jiraAnomalyDetection = new JiraAnomalyDetectionService();
