import pandas as pd
import subprocess
from tqdm import tqdm
from datetime import datetime
import os
from concurrent.futures import ThreadPoolExecutor

# Repository- und Datei-Pfade
repo_path = r'C:\Users\Kon\Documents\geoserver'
file_path = r'C:\Users\Kon\Documents\geoserver\src\wms\src\main\java\org\geoserver\wms\map\AbstractOpenLayersMapOutputFormat.java'




# Schlüsselwörter zum Filtern von Commits
ignore_keywords = ["refactor", "reformat"]

# Dateiinhalt lesen
try:
    with open(file_path, "r", encoding="utf-8") as f:
        lines = f.readlines()
except FileNotFoundError:
    print(f"Datei nicht gefunden: {file_path}")
    exit()

# Filtere Zeilen, die keine Kommentare enthalten oder leer sind, und speichere die originalen Zeilennummern
filtered_lines_with_numbers = [
    (i + 1, line) for i, line in enumerate(lines)
    if line.strip() and not line.strip().startswith(("//", "/*", "*", "*/"))
]

# Originale Zeilennummern und Code extrahieren
original_line_numbers, filtered_lines = zip(*filtered_lines_with_numbers) if filtered_lines_with_numbers else ([], [])

# DataFrame mit den gefilterten Zeilen und originalen Zeilennummern erstellen
df = pd.DataFrame({"Original Line Number": original_line_numbers, "Code Line": filtered_lines})

def get_line_commits(repo_path, file_path, line_number):
    """Ruft die Commits für eine bestimmte Codezeile ab."""
    command = [
        "git",
        "-C",
        repo_path,
        "log",
        "-L",
        f"{line_number},{line_number}:{file_path}",
        "--pretty=format:%h|%s|%ci"
    ]
    try:
        result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        if result.returncode == 0:
            commits = result.stdout.strip().splitlines()
            valid_commits = []
            for commit in commits:
                # Überprüfen, ob der Commit eine Diff-Zeile enthält, und diese überspringen
                if commit.startswith("diff --git") or commit.startswith("@@") or commit.startswith("+") or commit.startswith("-"):
                    continue
                parts = commit.split('|')
                if len(parts) == 3:
                    commit_hash, message, commit_date = parts
                    # Überspringe Commits, die bestimmte Schlüsselwörter enthalten
                    if any(keyword in message.lower() for keyword in ignore_keywords):
                        continue
                    valid_commits.append((commit_hash, message, commit_date))
            return valid_commits
        else:
            print(f"Fehler bei Zeile {line_number}: {result.stderr}")
            return []
    except Exception as e:
        print(f"Fehler bei Zeile {line_number}: {e}")
        return []

# Funktion zur Verarbeitung von Git-Commits für eine Gruppe von Zeilen
def process_line_commits(line_number):
    """Verarbeitet alle Commits für eine bestimmte Zeile."""
    commits = get_line_commits(repo_path, file_path, line_number)
    return (line_number, commits)

def safe_parse_datetime(commit_key, line_number):
    """Versucht, das Datum aus dem Commit-Key zu extrahieren. Gibt die Zeile bei Fehlern aus."""
    try:
        if commit_key.strip().startswith(('+', '-')):
            # Diff-Zeilen sind keine Commits und werden ignoriert
            return None
        # Extrahiere das Datum am Ende des Commit-Keys
        return datetime.strptime(commit_key.split(", ")[-1].strip(")"), "%Y-%m-%d %H:%M:%S %z")
    except ValueError:
        print(f"Fehler beim Parsen des Datums für Commit in Zeile {line_number}: {commit_key}")
        return None

# Fortschrittsanzeige und Commit-Sammlung
print("Starte die Analyse aller relevanten Zeilen...")
commit_info = {}

# Wir nutzen ThreadPoolExecutor, um die Abfragen parallel auszuführen
with ThreadPoolExecutor(max_workers=25) as executor:
    # Liste der Futures, die die Bearbeitung der Zeilen durchführen
    futures = [executor.submit(process_line_commits, line_number) for line_number in original_line_numbers]
    
    # Warten auf die Ergebnisse und Sammeln der Commit-Daten
    for future in tqdm(futures, desc="Verarbeite relevante Zeilen"):
        line_number, commits = future.result()
        for commit in commits:
            commit_hash, message, commit_date = commit
            commit_key = f"{commit_hash} ({message}, {commit_date})"
            if commit_key not in commit_info:
                commit_info[commit_key] = []
            commit_info[commit_key].append(line_number)

# Sortiere Commits nach Datum (fehlerhafte Commits überspringen)
sorted_commits = []
for commit_key, lines in commit_info.items():
    for line_number in lines:
        commit_date = safe_parse_datetime(commit_key, line_number)
        if commit_date:
            sorted_commits.append((commit_key, commit_date))
            break  # Einmal hinzufügen, um Duplikate zu vermeiden

sorted_commits.sort(key=lambda x: x[1], reverse=True)

# Füge Commits als Spalten hinzu
for commit, _ in sorted_commits:
    df[commit] = ""

# Markiere Änderungen mit "X"
for commit, lines_changed in commit_info.items():
    for line_number in lines_changed:
        # Suche die Zeile mit der entsprechenden Originalnummer
        row_index = df.index[df["Original Line Number"] == line_number]
        if not row_index.empty:
            df.loc[row_index, commit] = "X"

# Zähle die Anzahl der "X" pro Zeile
df["Change Count"] = df.apply(lambda row: sum(1 for value in row if value == "X"), axis=1)

# Dateiname ohne die .java-Endung extrahieren
base_filename = os.path.basename(file_path)
file_name_without_extension = os.path.splitext(base_filename)[0]

# Ergebnis speichern
output_path = os.path.join(r"C:\Users\Kon\Documents\gitAnalyse", f"code_line_changes_in_{file_name_without_extension}.xlsx")
try:
    df.to_excel(output_path, index=False, engine="openpyxl")
    print(f"Analyse abgeschlossen. Ergebnisse gespeichert unter: {output_path}")
except PermissionError:
    print(f"Fehler: Keine Berechtigung zum Schreiben der Datei. Bitte sicherstellen, dass die Datei nicht geöffnet ist und versuchen Sie es erneut.")
