/**
 * Service d'analytics Jira pour la surveillance d'équipe
 * Calcule des métriques avancées sur un projet spécifique
 */

import { JiraService } from './jira';
import { jiraAnalyticsCache } from './analytics-cache';
import { getToday, parseDate, subtractDays } from '@/lib/date-utils';
import {
  JiraAnalytics,
  JiraTask,
  AssigneeDistribution,
  SprintVelocity,
  CycleTimeByType,
  StatusDistribution,
  AssigneeWorkload,
} from '@/lib/types';

export interface JiraAnalyticsConfig {
  enabled: boolean;
  baseUrl: string;
  email: string;
  apiToken: string;
  projectKey: string;
}

export class JiraAnalyticsService {
  private jiraService: JiraService;
  private projectKey: string;
  private config: JiraAnalyticsConfig;

  constructor(config: JiraAnalyticsConfig) {
    this.jiraService = new JiraService(config);
    this.projectKey = config.projectKey;
    this.config = config;
  }

  /**
   * Récupère toutes les issues du projet pour filtrage
   */
  async getAllProjectIssues(): Promise<JiraTask[]> {
    try {
      const jql = `project = "${this.projectKey}" ORDER BY created DESC`;
      const issues = await this.jiraService.searchIssues(jql);
      console.log(`📋 Récupéré ${issues.length} issues pour filtrage`);
      return issues;
    } catch (error) {
      console.error('❌ Erreur lors de la récupération des issues:', error);
      throw new Error(
        `Impossible de récupérer les issues: ${error instanceof Error ? error.message : 'Erreur inconnue'}`
      );
    }
  }

  /**
   * Récupère toutes les analytics du projet avec cache
   */
  async getProjectAnalytics(forceRefresh = false): Promise<JiraAnalytics> {
    try {
      // Vérifier le cache d'abord (sauf si forceRefresh)
      if (!forceRefresh) {
        const cachedAnalytics = jiraAnalyticsCache.get(this.config);
        if (cachedAnalytics) {
          return cachedAnalytics;
        }
      }

      console.log(
        `🔄 Calcul des analytics Jira pour projet ${this.projectKey} ${forceRefresh ? '(actualisation forcée)' : '(cache manquant)'}`
      );

      // Récupérer les informations du projet
      const projectInfo = await this.getProjectInfo();

      // Récupérer tous les tickets du projet (pas seulement assignés)
      const allIssues = await this.getAllProjectIssues();
      console.log(`📋 ${allIssues.length} tickets récupérés pour l'analyse`);

      // Calculer les différentes métriques
      const [teamMetrics, velocityMetrics, cycleTimeMetrics, workInProgress] =
        await Promise.all([
          this.calculateTeamMetrics(allIssues),
          this.calculateVelocityMetrics(allIssues),
          this.calculateCycleTimeMetrics(allIssues),
          this.calculateWorkInProgress(allIssues),
        ]);

      const analytics: JiraAnalytics = {
        project: {
          key: this.projectKey,
          name: projectInfo.name,
          totalIssues: allIssues.length,
        },
        teamMetrics,
        velocityMetrics,
        cycleTimeMetrics,
        workInProgress,
      };

      // Mettre en cache le résultat
      jiraAnalyticsCache.set(this.config, analytics);

      return analytics;
    } catch (error) {
      console.error('Erreur lors du calcul des analytics:', error);
      throw error;
    }
  }

  /**
   * Invalide le cache pour ce projet
   */
  invalidateCache(): void {
    jiraAnalyticsCache.invalidate(this.config);
  }

  /**
   * Récupère les informations de base du projet
   */
  private async getProjectInfo(): Promise<{ name: string }> {
    const validation = await this.jiraService.validateProject(this.projectKey);
    if (!validation.exists) {
      throw new Error(`Projet ${this.projectKey} introuvable`);
    }
    return { name: validation.name || this.projectKey };
  }

  /**
   * Calcule les métriques d'équipe (répartition par assignee)
   */
  private async calculateTeamMetrics(issues: JiraTask[]): Promise<{
    totalAssignees: number;
    activeAssignees: number;
    issuesDistribution: AssigneeDistribution[];
  }> {
    const assigneeStats = new Map<
      string,
      {
        displayName: string;
        total: number;
        completed: number;
        inProgress: number;
      }
    >();

    // Analyser chaque ticket
    issues.forEach((issue) => {
      const assignee = issue.assignee;
      const status = issue.status?.name || 'Unknown';

      // Utiliser "Unassigned" si pas d'assignee
      const assigneeKey = assignee?.emailAddress || 'unassigned';
      const displayName = assignee?.displayName || 'Non assigné';

      if (!assigneeStats.has(assigneeKey)) {
        assigneeStats.set(assigneeKey, {
          displayName,
          total: 0,
          completed: 0,
          inProgress: 0,
        });
      }

      const stats = assigneeStats.get(assigneeKey)!;
      stats.total++;

      // Catégoriser par statut (logique simplifiée)
      const statusLower = status.toLowerCase();
      if (
        statusLower.includes('done') ||
        statusLower.includes('closed') ||
        statusLower.includes('resolved')
      ) {
        stats.completed++;
      } else if (
        statusLower.includes('progress') ||
        statusLower.includes('review') ||
        statusLower.includes('testing')
      ) {
        stats.inProgress++;
      }
    });

    // Convertir en tableau et calculer les pourcentages
    const distribution: AssigneeDistribution[] = Array.from(
      assigneeStats.entries()
    )
      .map(([assignee, stats]) => ({
        assignee,
        displayName: stats.displayName,
        totalIssues: stats.total,
        completedIssues: stats.completed,
        inProgressIssues: stats.inProgress,
        percentage: Math.round((stats.total / issues.length) * 100),
        count: stats.total, // Ajout pour compatibilité
      }))
      .sort((a, b) => b.totalIssues - a.totalIssues);

    const activeAssignees = distribution.filter(
      (d) => d.inProgressIssues > 0
    ).length;

    return {
      totalAssignees: assigneeStats.size,
      activeAssignees,
      issuesDistribution: distribution,
    };
  }

  /**
   * Calcule les métriques de vélocité (basées sur les story points)
   */
  private async calculateVelocityMetrics(issues: JiraTask[]): Promise<{
    currentSprintPoints: number;
    averageVelocity: number;
    sprintHistory: SprintVelocity[];
  }> {
    // Pour l'instant, implémentation basique
    // TODO: Intégrer avec l'API Jira Agile pour les vrais sprints

    const completedIssues = issues.filter((issue) => {
      const statusCategory = issue.status?.category?.toLowerCase();
      const statusName = issue.status?.name?.toLowerCase() || '';

      // Support Jira français ET anglais
      const isCompleted =
        statusCategory === 'done' ||
        statusCategory === 'terminé' ||
        statusName.includes('done') ||
        statusName.includes('closed') ||
        statusName.includes('resolved') ||
        statusName.includes('complete') ||
        statusName.includes('fait') ||
        statusName.includes('clôturé') ||
        statusName.includes('cloturé') ||
        statusName.includes('en production') ||
        statusName.includes('finished') ||
        statusName.includes('delivered');

      return isCompleted;
    });

    // Calculer les points (utiliser les vrais story points de Jira)
    const getStoryPoints = (issue: JiraTask) => {
      // Si l'issue a des story points définis, les utiliser
      if (issue.storyPoints && issue.storyPoints > 0) {
        return issue.storyPoints;
      }

      // Sinon, utiliser une estimation basée sur le type d'issue
      switch (issue.issuetype.name.toLowerCase()) {
        case 'epic':
          return 13; // Épic = gros effort
        case 'story':
          return 5; // Story = effort moyen
        case 'task':
          return 3; // Task = effort petit-moyen
        case 'bug':
          return 2; // Bug = effort petit
        case 'subtask':
          return 1; // Subtask = effort minimal
        default:
          return 1; // Par défaut, 1 point
      }
    };

    const currentSprintPoints = completedIssues.reduce(
      (sum, issue) => sum + getStoryPoints(issue),
      0
    );

    // Créer un historique basé sur les données réelles des 4 dernières périodes
    const sprintHistory = this.generateSprintHistoryFromIssues(
      issues,
      completedIssues
    );
    const averageVelocity =
      sprintHistory.length > 0
        ? Math.round(
            sprintHistory.reduce(
              (sum, sprint) => sum + sprint.completedPoints,
              0
            ) / sprintHistory.length
          )
        : 0;

    return {
      currentSprintPoints,
      averageVelocity,
      sprintHistory,
    };
  }

  /**
   * Génère un historique de sprints basé sur les dates de création/résolution des tickets
   */
  private generateSprintHistoryFromIssues(
    allIssues: JiraTask[],
    completedIssues: JiraTask[]
  ): SprintVelocity[] {
    const now = getToday();
    const sprintHistory: SprintVelocity[] = [];

    // Créer 4 périodes de 2 semaines (8 semaines au total)
    for (let i = 3; i >= 0; i--) {
      const endDate = subtractDays(now, i * 14);
      const startDate = subtractDays(endDate, 14);

      // Compter les tickets complétés dans cette période
      const completedInPeriod = completedIssues.filter((issue) => {
        const updatedDate = parseDate(issue.updated);
        return updatedDate >= startDate && updatedDate <= endDate;
      });

      // Compter les tickets créés dans cette période (approximation du planifié)
      const createdInPeriod = allIssues.filter((issue) => {
        const createdDate = parseDate(issue.created);
        return createdDate >= startDate && createdDate <= endDate;
      });

      // Calculer les points avec la même logique que getStoryPoints
      const getStoryPoints = (issue: JiraTask) => {
        if (issue.storyPoints && issue.storyPoints > 0) {
          return issue.storyPoints;
        }
        switch (issue.issuetype.name.toLowerCase()) {
          case 'epic':
            return 13;
          case 'story':
            return 5;
          case 'task':
            return 3;
          case 'bug':
            return 2;
          case 'subtask':
            return 1;
          default:
            return 1;
        }
      };

      const completedPoints = completedInPeriod.reduce(
        (sum, issue) => sum + getStoryPoints(issue),
        0
      );
      const plannedPoints = Math.max(
        completedPoints,
        createdInPeriod.reduce((sum, issue) => sum + getStoryPoints(issue), 0)
      );
      const completionRate =
        plannedPoints > 0
          ? Math.round((completedPoints / plannedPoints) * 100)
          : 0;

      sprintHistory.push({
        sprintName: i === 0 ? 'Sprint actuel' : `Sprint -${i}`,
        startDate: startDate.toISOString(),
        endDate: endDate.toISOString(),
        completedPoints,
        plannedPoints,
        completionRate,
        velocity: completedPoints, // Ajout pour compatibilité
      });
    }

    return sprintHistory;
  }

  /**
   * Calcule les métriques de cycle time
   */
  private async calculateCycleTimeMetrics(issues: JiraTask[]): Promise<{
    averageCycleTime: number;
    cycleTimeByType: CycleTimeByType[];
  }> {
    const completedIssues = issues.filter((issue) => {
      const statusCategory = issue.status?.category?.toLowerCase();
      const statusName = issue.status?.name?.toLowerCase() || '';

      // Support Jira français ET anglais
      return (
        statusCategory === 'done' ||
        statusCategory === 'terminé' ||
        statusName.includes('done') ||
        statusName.includes('closed') ||
        statusName.includes('resolved') ||
        statusName.includes('complete') ||
        statusName.includes('fait') ||
        statusName.includes('clôturé') ||
        statusName.includes('cloturé') ||
        statusName.includes('en production') ||
        statusName.includes('finished') ||
        statusName.includes('delivered')
      );
    });

    // Calculer le cycle time (de création à résolution)
    const cycleTimes = completedIssues
      .filter((issue) => issue.created && issue.updated) // S'assurer qu'on a les dates
      .map((issue) => {
        const created = parseDate(issue.created);
        const resolved = parseDate(issue.updated);
        const days = Math.max(
          0.1,
          (resolved.getTime() - created.getTime()) / (1000 * 60 * 60 * 24)
        ); // Minimum 0.1 jour
        return Math.round(days * 10) / 10; // Arrondir à 1 décimale
      })
      .filter((time) => time > 0 && time < 365); // Filtrer les valeurs aberrantes (plus d'un an)

    const averageCycleTime =
      cycleTimes.length > 0
        ? Math.round(
            (cycleTimes.reduce((sum, time) => sum + time, 0) /
              cycleTimes.length) *
              10
          ) / 10
        : 0;

    // Grouper par type d'issue (recalculer avec les données filtrées)
    const validCompletedIssues = completedIssues.filter(
      (issue) => issue.created && issue.updated
    );
    const typeStats = new Map<string, number[]>();

    validCompletedIssues.forEach((issue, index) => {
      if (index < cycleTimes.length) {
        // Sécurité pour éviter l'index out of bounds
        const issueType = issue.issuetype?.name || 'Unknown';
        if (!typeStats.has(issueType)) {
          typeStats.set(issueType, []);
        }
        const cycleTime = cycleTimes[index];
        if (cycleTime > 0 && cycleTime < 365) {
          // Même filtre que plus haut
          typeStats.get(issueType)!.push(cycleTime);
        }
      }
    });

    const cycleTimeByType: CycleTimeByType[] = Array.from(typeStats.entries())
      .map(([type, times]) => {
        const average =
          times.reduce((sum, time) => sum + time, 0) / times.length;
        const sorted = [...times].sort((a, b) => a - b);
        const median =
          sorted.length % 2 === 0
            ? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2
            : sorted[Math.floor(sorted.length / 2)];

        return {
          issueType: type,
          averageDays: Math.round(average * 10) / 10,
          medianDays: Math.round(median * 10) / 10,
          samples: times.length,
        };
      })
      .sort((a, b) => b.samples - a.samples);

    return {
      averageCycleTime,
      cycleTimeByType,
    };
  }

  /**
   * Calcule le work in progress (WIP)
   */
  private async calculateWorkInProgress(issues: JiraTask[]): Promise<{
    byStatus: StatusDistribution[];
    byAssignee: AssigneeWorkload[];
  }> {
    // Grouper par statut
    const statusCounts = new Map<string, number>();
    issues.forEach((issue) => {
      const status = issue.status?.name || 'Unknown';
      statusCounts.set(status, (statusCounts.get(status) || 0) + 1);
    });

    const byStatus: StatusDistribution[] = Array.from(statusCounts.entries())
      .map(([status, count]) => ({
        status,
        count,
        percentage: Math.round((count / issues.length) * 100),
      }))
      .sort((a, b) => b.count - a.count);

    // Grouper par assignee (WIP seulement)
    const wipIssues = issues.filter((issue) => {
      const statusCategory = issue.status?.category?.toLowerCase();
      const statusName = issue.status?.name?.toLowerCase() || '';

      // Exclure les tickets terminés (support français ET anglais)
      return (
        statusCategory !== 'done' &&
        statusCategory !== 'terminé' &&
        !statusName.includes('done') &&
        !statusName.includes('closed') &&
        !statusName.includes('resolved') &&
        !statusName.includes('complete') &&
        !statusName.includes('fait') &&
        !statusName.includes('clôturé') &&
        !statusName.includes('cloturé') &&
        !statusName.includes('en production') &&
        !statusName.includes('finished') &&
        !statusName.includes('delivered')
      );
    });

    const assigneeWorkload = new Map<
      string,
      {
        displayName: string;
        todo: number;
        inProgress: number;
        review: number;
      }
    >();

    wipIssues.forEach((issue) => {
      const assignee = issue.assignee;
      const status = issue.status?.name?.toLowerCase() || '';

      const assigneeKey = assignee?.emailAddress || 'unassigned';
      const displayName = assignee?.displayName || 'Non assigné';

      if (!assigneeWorkload.has(assigneeKey)) {
        assigneeWorkload.set(assigneeKey, {
          displayName,
          todo: 0,
          inProgress: 0,
          review: 0,
        });
      }

      const workload = assigneeWorkload.get(assigneeKey)!;
      const statusCategory = issue.status?.category?.toLowerCase();

      // Classification robuste français/anglais basée sur les catégories et noms Jira
      if (
        statusCategory === 'indeterminate' ||
        statusCategory === 'en cours' ||
        status.includes('progress') ||
        status.includes('en cours') ||
        status.includes('developing') ||
        status.includes('implementation')
      ) {
        workload.inProgress++;
      } else if (
        status.includes('review') ||
        status.includes('testing') ||
        status.includes('validation') ||
        status.includes('validating') ||
        status.includes('ready for')
      ) {
        workload.review++;
      } else if (
        statusCategory === 'new' ||
        statusCategory === 'a faire' ||
        status.includes('todo') ||
        status.includes('to do') ||
        status.includes('a faire') ||
        status.includes('backlog') ||
        status.includes('product backlog') ||
        status.includes('ready to sprint') ||
        status.includes('estimating') ||
        status.includes('refinement') ||
        status.includes('open') ||
        status.includes('created')
      ) {
        workload.todo++;
      } else {
        // Fallback: si on ne peut pas classifier, mettre en "À faire"
        workload.todo++;
      }
    });

    const byAssignee: AssigneeWorkload[] = Array.from(
      assigneeWorkload.entries()
    )
      .map(([assignee, workload]) => ({
        assignee,
        displayName: workload.displayName,
        todoCount: workload.todo,
        inProgressCount: workload.inProgress,
        reviewCount: workload.review,
        totalActive: workload.todo + workload.inProgress + workload.review,
      }))
      .sort((a, b) => b.totalActive - a.totalActive);

    return {
      byStatus,
      byAssignee,
    };
  }
}
