import 'package:lanebow/models/member_model.dart';
import 'package:lanebow/models/score_model.dart';
import 'package:lanebow/models/tie_breaker_option.dart';

/// 동점자 처리를 위한 유틸리티 클래스
class TieBreakerUtil {
  /// 동점자 처리 옵션에 따라 점수 목록을 정렬
  /// 
  /// [scores] 정렬할 점수 목록
  /// [options] 적용할 동점자 처리 옵션 우선순위 목록
  /// [members] 회원 정보 목록 (연령 기준 정렬 시 필요)
  /// [includeHandicap] 핸디캡 포함 여부
  /// 
  /// 반환값: 정렬된 점수 목록
  static List<Score> sortScoresByOptions(
    List<Score> scores, 
    List<TieBreakerOption>? options, 
    List<Member>? members, 
    bool includeHandicap
  ) {
    // 옵션이 없거나 비어있으면 기본 정렬만 적용
    if (options == null || options.isEmpty) {
      return _sortScoresByDefault(scores, includeHandicap);
    }
    
    // olderAge 옵션이 단독으로 있는 경우 특별 처리
    if (options.length == 1 && options.contains(TieBreakerOption.olderAge)) {
      if (members == null || members.isEmpty) {
        return _sortScoresByDefault(scores, includeHandicap);
      }
      
      // 연장자 순으로 정렬
      return List.from(scores)..sort((a, b) {
        DateTime? birthDateA = _findMemberBirthDate(a.memberId, members);
        DateTime? birthDateB = _findMemberBirthDate(b.memberId, members);
        
        if (birthDateA == null || birthDateB == null) {
          // 생년월일이 없으면 점수로 정렬
          int scoreA = includeHandicap ? (a.totalScore + (a.handicap ?? 0)) : a.totalScore;
          int scoreB = includeHandicap ? (b.totalScore + (b.handicap ?? 0)) : b.totalScore;
          return scoreB.compareTo(scoreA); // 내림차순
        }
        
        return birthDateA.compareTo(birthDateB); // 오름차순 정렬로 연장자 우선
      });
    }
    
    // 기본 점수 정렬 (점수 내림차순)
    List<Score> sortedScores = _sortScoresByDefault(scores, includeHandicap);
    
    // 점수별 그룹화
    Map<int, List<Score>> scoreGroups = {};
    
    for (int i = 0; i < sortedScores.length; i++) {
      int currentScore = includeHandicap 
          ? (sortedScores[i].totalScore + (sortedScores[i].handicap ?? 0))
          : sortedScores[i].totalScore;
      
      if (!scoreGroups.containsKey(currentScore)) {
        scoreGroups[currentScore] = [];
      }
      scoreGroups[currentScore]!.add(sortedScores[i]);
    }
    
    // 결과 목록 준비
    List<Score> result = [];
    
    // 점수 내림차순 정렬
    List<int> sortedScoreValues = scoreGroups.keys.toList()..sort((a, b) => b.compareTo(a));
    
    // 각 점수 그룹에 대해 처리
    for (int scoreValue in sortedScoreValues) {
      List<Score> group = scoreGroups[scoreValue]!;
      
      // 그룹 크기가 1이면 그대로 추가
      if (group.length == 1) {
        result.add(group[0]);
        continue;
      }
      
      // 동점자 그룹 정렬
      List<Score> resolvedGroup = List.from(group);
      
      // 옵션에 따른 정렬 적용
      if (options.contains(TieBreakerOption.lowerHandicap)) {
        // 핸디캡이 낮은 순으로 정렬
        resolvedGroup.sort((a, b) {
          int handicapA = a.handicap ?? 0;
          int handicapB = b.handicap ?? 0;
          return handicapA.compareTo(handicapB);
        });
      }
      
      // 핸디캡이 같은 경우 연장자 우선 정렬
      if (options.contains(TieBreakerOption.olderAge) && members != null && members.isNotEmpty) {
        // 핸디캡이 같은 그룹들로 나누기
        Map<int, List<Score>> handicapGroups = {};
        for (var score in resolvedGroup) {
          int handicap = score.handicap ?? 0;
          if (!handicapGroups.containsKey(handicap)) {
            handicapGroups[handicap] = [];
          }
          handicapGroups[handicap]!.add(score);
        }
        
        // 각 핸디캡 그룹 내에서 연장자 순으로 정렬
        List<Score> newResolvedGroup = [];
        List<int> handicapValues = handicapGroups.keys.toList()..sort((a, b) => a.compareTo(b));
        
        for (int handicap in handicapValues) {
          List<Score> handicapGroup = handicapGroups[handicap]!;
          if (handicapGroup.length > 1) {
            handicapGroup.sort((a, b) {
              DateTime? birthDateA = _findMemberBirthDate(a.memberId, members);
              DateTime? birthDateB = _findMemberBirthDate(b.memberId, members);
              
              if (birthDateA == null || birthDateB == null) return 0;
              return birthDateA.compareTo(birthDateB); // 오름차순 정렬로 연장자 우선
            });
          }
          newResolvedGroup.addAll(handicapGroup);
        }
        
        resolvedGroup = newResolvedGroup;
      }
      
      // 점수 격차가 적은 순으로 정렬
      if (options.contains(TieBreakerOption.lowerScoreGap)) {
        // 이미 정렬된 그룹에서 추가 정렬 적용
        List<Score> scoreGapSorted = [];
        
        // 핸디캡과 연령으로 이미 정렬된 그룹들을 찾아서 각 그룹 내에서 점수 격차 정렬 적용
        Map<String, List<Score>> tiedGroups = {};
        String currentKey = "";
        
        for (int i = 0; i < resolvedGroup.length; i++) {
          Score current = resolvedGroup[i];
          int handicap = current.handicap ?? 0;
          String memberId = current.memberId ?? "";
          
          String key = "$handicap-$memberId";
          if (i == 0 || key != currentKey) {
            currentKey = key;
            tiedGroups[currentKey] = [];
          }
          tiedGroups[currentKey]!.add(current);
        }
        
        // 각 그룹에 대해 점수 격차 정렬 적용
        for (var group in tiedGroups.values) {
          if (group.length > 1) {
            group.sort((a, b) {
              int gapA = _calculateScoreGap(a.frames);
              int gapB = _calculateScoreGap(b.frames);
              return gapA.compareTo(gapB); // 오름차순 (격차가 적은 것이 우선)
            });
          }
          scoreGapSorted.addAll(group);
        }
        
        resolvedGroup = scoreGapSorted;
      }
      
      // 정렬된 그룹 추가
      result.addAll(resolvedGroup);
    }
    
    return result;
  }
  
  /// 기본 점수 정렬 (점수 내림차순)
  static List<Score> _sortScoresByDefault(List<Score> scores, bool includeHandicap) {
    return List.from(scores)..sort((a, b) {
      int scoreA = includeHandicap ? (a.totalScore + (a.handicap ?? 0)) : a.totalScore;
      int scoreB = includeHandicap ? (b.totalScore + (b.handicap ?? 0)) : b.totalScore;
      return scoreB.compareTo(scoreA); // 내림차순
    });
  }
  
  /// 동점자 그룹 찾기
  static Map<int, List<Score>> _findTiedGroups(List<Score> sortedScores, bool includeHandicap) {
    Map<int, List<Score>> tiedGroups = {};
    
    for (int i = 0; i < sortedScores.length; i++) {
      int currentScore = includeHandicap 
          ? (sortedScores[i].totalScore + (sortedScores[i].handicap ?? 0))
          : sortedScores[i].totalScore;
      
      List<Score> sameScores = [sortedScores[i]];
      
      // 같은 점수를 가진 항목 찾기
      for (int j = i + 1; j < sortedScores.length; j++) {
        int nextScore = includeHandicap 
            ? (sortedScores[j].totalScore + (sortedScores[j].handicap ?? 0))
            : sortedScores[j].totalScore;
        
        if (currentScore == nextScore) {
          sameScores.add(sortedScores[j]);
          i = j; // 이미 처리한 항목 건너뛰기
        } else {
          break; // 다른 점수 발견 시 중단
        }
      }
      
      // 동점자가 2명 이상인 경우만 그룹에 추가
      if (sameScores.length > 1) {
        tiedGroups[currentScore] = sameScores;
      }
    }
    
    return tiedGroups;
  }
  
  /// 동점자 처리 옵션 적용
  static List<Score> _applyTieBreakerOption(
    List<Score> tiedScores, 
    TieBreakerOption option, 
    List<Member>? members, 
    bool includeHandicap
  ) {
    switch (option) {
      case TieBreakerOption.lowerHandicap:
        return _sortByLowerHandicap(tiedScores);
      
      case TieBreakerOption.lowerScoreGap:
        return _sortByLowerScoreGap(tiedScores);
      
      case TieBreakerOption.olderAge:
        return _sortByOlderAge(tiedScores, members);
      
      case TieBreakerOption.none:
        return tiedScores; // 변경 없음
    }
  }
  
  /// 핸디캡이 적은 순으로 정렬
  static List<Score> _sortByLowerHandicap(List<Score> tiedScores) {
    return List.from(tiedScores)..sort((a, b) {
      int handicapA = a.handicap ?? 0;
      int handicapB = b.handicap ?? 0;
      return handicapA.compareTo(handicapB); // 오름차순 (적은 것이 우선)
    });
  }
  
  /// 점수 격차가 적은 순으로 정렬
  static List<Score> _sortByLowerScoreGap(List<Score> tiedScores) {
    return List.from(tiedScores)..sort((a, b) {
      // 프레임 점수 중 최고점과 최저점의 차이 계산
      int gapA = _calculateScoreGap(a.frames);
      int gapB = _calculateScoreGap(b.frames);
      return gapA.compareTo(gapB); // 오름차순 (격차가 적은 것이 우선)
    });
  }
  
  /// 연장자 우선으로 정렬
  static List<Score> _sortByOlderAge(List<Score> tiedScores, List<Member>? members) {
    if (members == null || members.isEmpty) return tiedScores;
    
    return List.from(tiedScores)..sort((a, b) {
      // 회원 정보에서 생년월일 찾기
      DateTime? birthDateA = _findMemberBirthDate(a.memberId, members);
      DateTime? birthDateB = _findMemberBirthDate(b.memberId, members);
      
      // 생년월일이 없으면 정렬 불가
      if (birthDateA == null || birthDateB == null) return 0;
      
      // 생년월일 비교 (오래된 날짜 = 연장자가 우선)
      // 연장자가 우선이므로 더 오래된 날짜(더 작은 값)가 앞에 와야 함
      // 연장자 우선이므로 더 오래된 날짜(A)가 더 최근 날짜(B)보다 앞에 와야 함
      return birthDateA.compareTo(birthDateB); // 오름차순 정렬로 연장자 우선
    });
  }
  
  /// 회원 ID로 생년월일 찾기
  static DateTime? _findMemberBirthDate(String? memberId, List<Member> members) {
    if (memberId == null) return null;
    
    for (var member in members) {
      if (member.id == memberId) {
        return member.birthDate;
      }
    }
    
    return null;
  }
  
  /// 프레임 점수의 최고점과 최저점 차이 계산
  static int _calculateScoreGap(List<int> frames) {
    if (frames.isEmpty) return 0;
    final int maxFrame = frames.reduce((max, score) => score > max ? score : max);
    final int minFrame = frames.reduce((min, score) => score < min ? score : min);
    
    final int scoreGap = maxFrame - minFrame;
    return scoreGap;
  }
  
  /// 모든 점수가 서로 다른지 확인
  static bool _allScoresHaveDifferentValues(List<Score> scores, bool includeHandicap) {
    Set<int> uniqueScores = {};
    
    for (var score in scores) {
      int totalScore = includeHandicap 
          ? (score.totalScore + (score.handicap ?? 0))
          : score.totalScore;
      
      if (uniqueScores.contains(totalScore)) {
        return false;
      }
      
      uniqueScores.add(totalScore);
    }
    
    return true;
  }
  
  /// 정렬된 동점자 그룹으로 원래 목록 업데이트
  static void _updateScoresWithResolvedTies(List<Score> originalScores, List<Score> resolvedTiedScores) {
    // 원래 목록에서 동점자 그룹에 해당하는 항목 찾아 업데이트
    for (int i = 0; i < originalScores.length; i++) {
      for (int j = 0; j < resolvedTiedScores.length; j++) {
        if (originalScores[i].id == resolvedTiedScores[j].id) {
          originalScores[i] = resolvedTiedScores[j];
          break;
        }
      }
    }
  }
  
  /// 순위 계산
  /// 
  /// [scores] 정렬된 점수 목록
  /// [includeHandicap] 핸디캅 포함 여부
  /// [options] 적용된 동점자 처리 옵션 목록
  /// 
  /// 반환값: 점수 ID를 키로 하고 순위를 값으로 하는 맵
  static Map<String, int> calculateRanks(
    List<Score> scores, 
    bool includeHandicap, 
    {List<TieBreakerOption>? options}
  ) {
    // 점수 목록이 비어있으면 빈 맵 반환
    if (scores.isEmpty) return {};
    
    Map<String, int> ranks = {};
    
    // 옵션 확인
    bool hasOlderAgeOption = options?.contains(TieBreakerOption.olderAge) ?? false;
    bool hasLowerScoreGapOption = options?.contains(TieBreakerOption.lowerScoreGap) ?? false;
    bool hasLowerHandicapOption = options?.contains(TieBreakerOption.lowerHandicap) ?? false;
    
    // 순위 부여 방식 결정
    // 1. lowerScoreGap 옵션이 있을 때는 기본적으로 동점자 동일 순위 부여
    //    - 단, lowerScoreGap -> lowerHandicap -> olderAge 조합에서는 특별 처리
    // 2. olderAge 옵션이 있고 lowerScoreGap 옵션이 없을 때는 순차적 순위 부여
    // 3. 그 외의 경우는 동점자 동일 순위 부여
    
    // 기본 순위 계산 로직
    // 점수별 그룹화
    Map<int, List<Score>> scoreGroups = {};
    
    // 점수와 ID 맵핑
    for (var score in scores) {
      int totalScore = includeHandicap 
          ? (score.totalScore + (score.handicap ?? 0))
          : score.totalScore;
      
      if (!scoreGroups.containsKey(totalScore)) {
        scoreGroups[totalScore] = [];
      }
      scoreGroups[totalScore]!.add(score);
    }
    
    // 점수 내림차순 정렬
    List<int> sortedScores = scoreGroups.keys.toList()..sort((a, b) => b.compareTo(a));
    
    int currentRank = 1;
    
    // lowerScoreGap -> lowerHandicap -> olderAge 조합 특별 처리
    if (hasLowerScoreGapOption && hasLowerHandicapOption && hasOlderAgeOption) {
      // 정렬된 점수 목록에서 점수 격차 그룹화
      Map<int, List<Score>> scoreGapGroups = {};
      
      // 점수 격차 계산 및 그룹화
      for (var score in scores) {
        int scoreGap = _calculateScoreGap(score.frames);
        
        if (!scoreGapGroups.containsKey(scoreGap)) {
          scoreGapGroups[scoreGap] = [];
        }
        scoreGapGroups[scoreGap]!.add(score);
      }
      
      // 점수 격차 오름차순 정렬 (격차가 적은 것이 우선)
      List<int> sortedGaps = scoreGapGroups.keys.toList()..sort((a, b) => a.compareTo(b));
      
      // 각 점수 격차 그룹에 대해 처리
      for (int gap in sortedGaps) {
        List<Score> gapGroup = scoreGapGroups[gap]!;
        
        // 동일 점수 격차 그룹 내에서는 동일 순위 부여 (lowerScoreGap 옵션에 의해)
        for (var score in gapGroup) {
          ranks[score.id] = currentRank;
        }
        
        // 다음 그룹은 이전 그룹의 크기만큼 순위 증가
        currentRank += gapGroup.length;
      }
    }
    // lowerScoreGap 옵션이 있고 위 특별 케이스가 아닌 경우 동점자에게 동일 순위 부여
    else if (hasLowerScoreGapOption) {
      for (int scoreValue in sortedScores) {
        List<Score> group = scoreGroups[scoreValue]!;
        
        // 동일 순위 부여
        for (var score in group) {
          ranks[score.id] = currentRank;
        }
        
        currentRank += group.length;
      }
    }
    // olderAge 옵션이 있고 lowerScoreGap 옵션이 없는 경우 순차적 순위 부여
    else if (hasOlderAgeOption && !hasLowerScoreGapOption) {
      for (int scoreValue in sortedScores) {
        List<Score> group = scoreGroups[scoreValue]!;
        List<Score> orderedGroup = [];
        
        // scores 리스트의 순서를 유지하면서 현재 그룹의 점수들을 추출
        for (var score in scores) {
          if (group.any((groupScore) => groupScore.id == score.id)) {
            orderedGroup.add(score);
          }
        }
        
        // 정렬된 순서대로 순차적 순위 부여
        for (int i = 0; i < orderedGroup.length; i++) {
          ranks[orderedGroup[i].id] = currentRank + i;
        }
        
        currentRank += orderedGroup.length;
      }
    }
    // 그 외의 경우는 동점자 동일 순위 부여
    else {
      for (int scoreValue in sortedScores) {
        List<Score> group = scoreGroups[scoreValue]!;
        
        // 동일 순위 부여
        for (var score in group) {
          ranks[score.id] = currentRank;
        }
        
        currentRank += group.length;
      }
    }
    
    return ranks;
  }
}
