import 'dart:math' as math;
import '../data/models/session.dart';
import '../data/models/shot.dart';

/// Time period for filtering statistics
enum StatsPeriod {
  session,  // Single session
  week,     // Last 7 days
  month,    // Last 30 days
  all,      // All time
}

/// Heat zone data for a region of the target
class HeatZone {
  final int row;
  final int col;
  final int shotCount;
  final double intensity; // 0-1, normalized
  final double avgScore;

  const HeatZone({
    required this.row,
    required this.col,
    required this.shotCount,
    required this.intensity,
    required this.avgScore,
  });
}

/// Heat map grid for the target
class HeatMap {
  final int gridSize;
  final List<List<HeatZone>> zones;
  final int maxShotsInZone;
  final int totalShots;

  const HeatMap({
    required this.gridSize,
    required this.zones,
    required this.maxShotsInZone,
    required this.totalShots,
  });
}

/// Precision statistics
class PrecisionStats {
  /// Average distance from target center (0-1 normalized)
  final double avgDistanceFromCenter;

  /// Grouping diameter (spread of shots)
  final double groupingDiameter;

  /// Precision score (0-100, higher = better)
  final double precisionScore;

  /// Consistency score based on standard deviation (0-100)
  final double consistencyScore;

  const PrecisionStats({
    required this.avgDistanceFromCenter,
    required this.groupingDiameter,
    required this.precisionScore,
    required this.consistencyScore,
  });
}

/// Standard deviation statistics
class StdDevStats {
  final double stdDevX;
  final double stdDevY;
  final double stdDevRadial;
  final double stdDevScore;
  final double meanX;
  final double meanY;
  final double meanScore;

  const StdDevStats({
    required this.stdDevX,
    required this.stdDevY,
    required this.stdDevRadial,
    required this.stdDevScore,
    required this.meanX,
    required this.meanY,
    required this.meanScore,
  });
}

/// Regional distribution
class RegionalStats {
  final Map<String, int> quadrantDistribution;
  final Map<String, int> sectorDistribution;
  final String dominantDirection;
  final double biasX;
  final double biasY;

  const RegionalStats({
    required this.quadrantDistribution,
    required this.sectorDistribution,
    required this.dominantDirection,
    required this.biasX,
    required this.biasY,
  });
}

/// Complete statistics result
class SessionStatistics {
  final int totalShots;
  final int totalScore;
  final double avgScore;
  final int maxScore;
  final int minScore;
  final HeatMap heatMap;
  final PrecisionStats precision;
  final StdDevStats stdDev;
  final RegionalStats regional;
  final List<Session> sessions;
  final StatsPeriod period;

  const SessionStatistics({
    required this.totalShots,
    required this.totalScore,
    required this.avgScore,
    required this.maxScore,
    required this.minScore,
    required this.heatMap,
    required this.precision,
    required this.stdDev,
    required this.regional,
    required this.sessions,
    required this.period,
  });
}

/// Service for calculating shooting statistics
class StatisticsService {
  /// Calculate statistics for given sessions
  SessionStatistics calculateStatistics(
    List<Session> sessions, {
    StatsPeriod period = StatsPeriod.all,
  }) {
    // Filter sessions by period
    final filteredSessions = _filterByPeriod(sessions, period);

    // Collect all shots from all analyses in all sessions
    final allShots = <Shot>[];
    for (final session in filteredSessions) {
      for (final analysis in session.analyses) {
        allShots.addAll(analysis.shots);
      }
    }

    if (allShots.isEmpty) {
      return _emptyStatistics(period, filteredSessions);
    }

    // Calculate basic stats
    final totalShots = allShots.length;
    final totalScore = allShots.fold<int>(0, (sum, shot) => sum + shot.score);
    final avgScore = totalScore / totalShots;
    final maxScore = allShots.map((s) => s.score).reduce(math.max);
    final minScore = allShots.map((s) => s.score).reduce(math.min);

    // Calculate heat map
    final heatMap = _calculateHeatMap(allShots, gridSize: 5);

    // Calculate precision (using relative center 0.5, 0.5)
    final precision = _calculatePrecision(allShots, 0.5, 0.5);

    // Calculate standard deviation
    final stdDev = _calculateStdDev(allShots);

    // Calculate regional distribution
    final regional = _calculateRegional(allShots, 0.5, 0.5);

    return SessionStatistics(
      totalShots: totalShots,
      totalScore: totalScore,
      avgScore: avgScore,
      maxScore: maxScore,
      minScore: minScore,
      heatMap: heatMap,
      precision: precision,
      stdDev: stdDev,
      regional: regional,
      sessions: filteredSessions,
      period: period,
    );
  }

  /// Filter sessions by time period
  List<Session> _filterByPeriod(List<Session> sessions, StatsPeriod period) {
    if (period == StatsPeriod.all) return sessions;

    final now = DateTime.now();
    final cutoff = switch (period) {
      StatsPeriod.session => now.subtract(const Duration(hours: 24)),
      StatsPeriod.week => now.subtract(const Duration(days: 7)),
      StatsPeriod.month => now.subtract(const Duration(days: 30)),
      StatsPeriod.all => DateTime(1970),
    };

    return sessions.where((s) => s.createdAt.isAfter(cutoff)).toList();
  }

  /// Calculate heat map
  HeatMap _calculateHeatMap(List<Shot> shots, {int gridSize = 5}) {
    final grid = List.generate(gridSize, (_) => List.generate(gridSize, (_) => <Shot>[]));
    for (final shot in shots) {
      final col = (shot.x * gridSize).floor().clamp(0, gridSize - 1);
      final row = (shot.y * gridSize).floor().clamp(0, gridSize - 1);
      grid[row][col].add(shot);
    }

    int maxCount = 0;
    for (final row in grid) {
      for (final cell in row) {
        if (cell.length > maxCount) maxCount = cell.length;
      }
    }

    final zones = <List<HeatZone>>[];
    for (int row = 0; row < gridSize; row++) {
      final rowZones = <HeatZone>[];
      for (int col = 0; col < gridSize; col++) {
        final cellShots = grid[row][col];
        final avgScore = cellShots.isEmpty ? 0.0 : cellShots.fold<int>(0, (sum, s) => sum + s.score) / cellShots.length;
        rowZones.add(HeatZone(
          row: row,
          col: col,
          shotCount: cellShots.length,
          intensity: maxCount > 0 ? cellShots.length / maxCount : 0,
          avgScore: avgScore,
        ));
      }
      zones.add(rowZones);
    }

    return HeatMap(gridSize: gridSize, zones: zones, maxShotsInZone: maxCount, totalShots: shots.length);
  }

  /// Calculate precision statistics
  PrecisionStats _calculatePrecision(List<Shot> shots, double centerX, double centerY) {
    if (shots.isEmpty) {
      return const PrecisionStats(avgDistanceFromCenter: 0, groupingDiameter: 0, precisionScore: 0, consistencyScore: 0);
    }

    final distances = shots.map((shot) {
      final dx = shot.x - centerX;
      final dy = shot.y - centerY;
      return math.sqrt(dx * dx + dy * dy);
    }).toList();

    final avgDistance = distances.reduce((a, b) => a + b) / distances.length;
    double maxSpread = 0;
    for (int i = 0; i < shots.length; i++) {
      for (int j = i + 1; j < shots.length; j++) {
        final dx = shots[i].x - shots[j].x;
        final dy = shots[i].y - shots[j].y;
        final dist = math.sqrt(dx * dx + dy * dy);
        if (dist > maxSpread) maxSpread = dist;
      }
    }

    double variance = 0;
    for (final d in distances) {
      variance += math.pow(d - avgDistance, 2);
    }
    final stdDevDist = math.sqrt(variance / distances.length);

    final precisionScore = math.max(0, (1 - avgDistance * 2) * 100);
    final consistencyScore = math.max(0, (1 - stdDevDist * 5) * 100);

    return PrecisionStats(
      avgDistanceFromCenter: avgDistance,
      groupingDiameter: maxSpread,
      precisionScore: precisionScore.toDouble().clamp(0.0, 100.0),
      consistencyScore: consistencyScore.toDouble().clamp(0.0, 100.0),
    );
  }

  /// Calculate standard deviation statistics
  StdDevStats _calculateStdDev(List<Shot> shots) {
    if (shots.isEmpty) {
      return const StdDevStats(stdDevX: 0, stdDevY: 0, stdDevRadial: 0, stdDevScore: 0, meanX: 0.5, meanY: 0.5, meanScore: 0);
    }

    double sumX = 0, sumY = 0, sumScore = 0;
    for (final shot in shots) {
      sumX += shot.x;
      sumY += shot.y;
      sumScore += shot.score;
    }
    final meanX = sumX / shots.length;
    final meanY = sumY / shots.length;
    final meanScore = sumScore / shots.length;

    double varianceX = 0, varianceY = 0, varianceScore = 0;
    for (final shot in shots) {
      varianceX += math.pow(shot.x - meanX, 2);
      varianceY += math.pow(shot.y - meanY, 2);
      varianceScore += math.pow(shot.score - meanScore, 2);
    }
    varianceX /= shots.length;
    varianceY /= shots.length;
    varianceScore /= shots.length;

    return StdDevStats(
      stdDevX: math.sqrt(varianceX),
      stdDevY: math.sqrt(varianceY),
      stdDevRadial: math.sqrt(varianceX + varianceY),
      stdDevScore: math.sqrt(varianceScore),
      meanX: meanX,
      meanY: meanY,
      meanScore: meanScore,
    );
  }

  /// Calculate regional distribution
  RegionalStats _calculateRegional(List<Shot> shots, double centerX, double centerY) {
    if (shots.isEmpty) {
      return const RegionalStats(quadrantDistribution: {}, sectorDistribution: {}, dominantDirection: 'Centre', biasX: 0, biasY: 0);
    }

    final quadrants = <String, int>{'Haut-Gauche': 0, 'Haut-Droite': 0, 'Bas-Gauche': 0, 'Bas-Droite': 0};
    final sectors = <String, int>{'N': 0, 'NE': 0, 'E': 0, 'SE': 0, 'S': 0, 'SO': 0, 'O': 0, 'NO': 0, 'Centre': 0};
    double sumDx = 0, sumDy = 0;

    for (final shot in shots) {
      final dx = shot.x - centerX;
      final dy = shot.y - centerY;
      sumDx += dx;
      sumDy += dy;

      if (dy < 0) {
        quadrants[dx < 0 ? 'Haut-Gauche' : 'Haut-Droite'] = quadrants[dx < 0 ? 'Haut-Gauche' : 'Haut-Droite']! + 1;
      } else {
        quadrants[dx < 0 ? 'Bas-Gauche' : 'Bas-Droite'] = quadrants[dx < 0 ? 'Bas-Gauche' : 'Bas-Droite']! + 1;
      }

      final distance = math.sqrt(dx * dx + dy * dy);
      if (distance < 0.1) {
        sectors['Centre'] = sectors['Centre']! + 1;
      } else {
        final angle = math.atan2(dy, dx) * 180 / math.pi;
        final sector = _angleToSector(angle);
        sectors[sector] = sectors[sector]! + 1;
      }
    }

    String dominant = 'Centre';
    int maxCount = 0;
    sectors.forEach((key, value) {
      if (value > maxCount) {
        maxCount = value;
        dominant = key;
      }
    });

    return RegionalStats(
      quadrantDistribution: quadrants,
      sectorDistribution: sectors,
      dominantDirection: dominant,
      biasX: sumDx / shots.length,
      biasY: sumDy / shots.length,
    );
  }

  String _angleToSector(double angle) {
    if (angle >= -22.5 && angle < 22.5) return 'E';
    if (angle >= 22.5 && angle < 67.5) return 'SE';
    if (angle >= 67.5 && angle < 112.5) return 'S';
    if (angle >= 112.5 && angle < 157.5) return 'SO';
    if (angle >= 157.5 || angle < -157.5) return 'O';
    if (angle >= -157.5 && angle < -112.5) return 'NO';
    if (angle >= -112.5 && angle < -67.5) return 'N';
    if (angle >= -67.5 && angle < -22.5) return 'NE';
    return 'Centre';
  }

  SessionStatistics _emptyStatistics(StatsPeriod period, List<Session> sessions) {
    return SessionStatistics(
      totalShots: 0, totalScore: 0, avgScore: 0, maxScore: 0, minScore: 0,
      heatMap: const HeatMap(gridSize: 5, zones: [], maxShotsInZone: 0, totalShots: 0),
      precision: const PrecisionStats(avgDistanceFromCenter: 0, groupingDiameter: 0, precisionScore: 0, consistencyScore: 0),
      stdDev: const StdDevStats(stdDevX: 0, stdDevY: 0, stdDevRadial: 0, stdDevScore: 0, meanX: 0.5, meanY: 0.5, meanScore: 0),
      regional: const RegionalStats(quadrantDistribution: {}, sectorDistribution: {}, dominantDirection: 'Centre', biasX: 0, biasY: 0),
      sessions: sessions, period: period,
    );
  }
}
