import os
import zipfile
import re
from datetime import datetime
from packaging import version  # pip install packaging
import platform
import shutil

# --------------------------
# Terminal Colors
# --------------------------
class Colors:
    HEADER = '\033[95m'
    OKBLUE = '\033[94m'
    OKCYAN = '\033[96m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    ORANGE = '\033[33m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'

def color_print(msg, color):
    print(f"{color}{msg}{Colors.ENDC}")

# --------------------------
# Utilities
# --------------------------
def timestamp():
    return datetime.now().strftime("%d-%m-%Y (%I-%M%p)")

def get_desktop_path():
    system = platform.system()
    if system == "Windows":
        return os.path.join(os.path.expanduser("~"), "Desktop")
    elif system in ["Linux", "Darwin"]:  # Linux or Mac
        return os.path.join(os.path.expanduser("~"), "Desktop")
    else:
        return os.getcwd()

def get_downloads_path():
    system = platform.system()
    if system == "Windows":
        return os.path.join(os.path.expanduser("~"), "Downloads")
    else:
        return get_desktop_path()

# --------------------------
# Get User Inputs
# --------------------------
print("Welcome to the Minecraft Mod Scanner!")

# Recursive Search Option
print("Do you want a recursive search? 1=Yes 2=No")
recursive_input = input("> ")
recursive = recursive_input.strip() == "1"
input("Press ENTER to confirm and continue...")

# Game version
game_version = input("Enter your Minecraft game version (e.g., 1.21.1): ").strip()

# Loader choice
print("What loader are you using? 1=NEOForge 2=Forge 3=Fabric")
loader_choice = input("> ").strip()
loader_choice = loader_choice if loader_choice in ["1","2","3"] else "1"

# Root folder
root_folder = os.getcwd()

# Output root
output_root = os.path.join(get_downloads_path(), "Duplicates Found")
os.makedirs(output_root, exist_ok=True)

# --------------------------
# File scanning
# --------------------------
mods_info = {}  # mod_name -> list of dict {filename, path, mc_version, mod_version, loader}

for dirpath, dirnames, filenames in os.walk(root_folder):
    for file in filenames:
        if file.lower().endswith(".jar"):
            file_path = os.path.join(dirpath, file)
            mc_ver, mod_ver, mod_name, loader = "Unknown", "Unknown", os.path.splitext(file)[0], "Unknown"
            # Attempt to read metadata inside the jar
            try:
                with zipfile.ZipFile(file_path, 'r') as jar:
                    # Check mods.toml (NeoForge / Forge)
                    if "META-INF/mods.toml" in jar.namelist():
                        content = jar.read("META-INF/mods.toml").decode(errors='ignore')
                        name_match = re.search(r'name\s*=\s*"(.*?)"', content)
                        mc_match = re.search(r'minecraft\s*=\s*"(.*?)"', content)
                        ver_match = re.search(r'version\s*=\s*"(.*?)"', content)
                        if name_match:
                            mod_name = name_match.group(1)
                        if mc_match:
                            mc_ver = mc_match.group(1)
                        if ver_match:
                            mod_ver = ver_match.group(1)
                        loader = "NEOForge"
                    # Optionally check fabric.mod.json here if needed
            except Exception as e:
                pass
            # Store info
            mods_info.setdefault(mod_name.lower(), []).append({
                "filename": file,
                "path": file_path,
                "mc_version": mc_ver,
                "mod_version": mod_ver,
                "loader": loader
            })
    if not recursive:
        break  # Don't recurse

# --------------------------
# Game version & loader mismatch
# --------------------------
game_mismatch_folder = os.path.join(output_root, "Game Mismatch Files")
os.makedirs(game_mismatch_folder, exist_ok=True)
for mod_name, entries in mods_info.items():
    for e in entries:
        if e['mc_version'] != game_version or (loader_choice=="1" and e['loader']!="NEOForge") \
           or (loader_choice=="2" and e['loader']!="Forge") or (loader_choice=="3" and e['loader']!="Fabric"):
            dest = os.path.join(game_mismatch_folder, e['filename'])
            shutil.copy2(e['path'], dest)  # copy, don't move
            color_print(f"[Game Mismatch] {e['filename']} -> {game_mismatch_folder}", Colors.WARNING)

# --------------------------
# Metadata Missing
# --------------------------
metadata_missing_folder = os.path.join(output_root, "Metadata Missing")
os.makedirs(metadata_missing_folder, exist_ok=True)
metadata_missing_file = os.path.join(metadata_missing_folder, f"Metadata Missing Mods - {timestamp()}.txt")
with open(metadata_missing_file, 'w', encoding='utf-8') as f:
    for mod_name, entries in mods_info.items():
        for e in entries:
            if e['mc_version']=="Unknown" or e['mod_version']=="Unknown":
                f.write(e['filename']+"\n")
color_print(f"Metadata missing mods saved to {metadata_missing_file}", Colors.ORANGE)

# --------------------------
# Duplicates & Multi-version
# --------------------------
multi_version_folder = os.path.join(output_root, "Detected Multi-Version")
os.makedirs(multi_version_folder, exist_ok=True)
multi_version_file = os.path.join(multi_version_folder, f"Detection List - {timestamp()}.txt")
with open(multi_version_file, 'w', encoding='utf-8') as f:
    for mod_name, entries in mods_info.items():
        if len(entries)>1:
            f.write(f"{mod_name}:\n")
            for e in entries:
                f.write(f"  - {e['filename']} [MC:{e['mc_version']}, MOD:{e['mod_version']}]\n")
color_print(f"Multi-version mods saved to {multi_version_file}", Colors.ORANGE)

# --------------------------
# NeoForge dependency conflicts
# --------------------------
def parse_version_range(range_str):
    match = re.match(r'\[([\d\.]+),?\)?', range_str)
    if match:
        return match.group(1)
    return None

def check_neoforge_conflicts(mods_info):
    conflicts = []
    for mod_name, entries in mods_info.items():
        for e in entries:
            try:
                with zipfile.ZipFile(e['path'], 'r') as jar:
                    if "META-INF/mods.toml" in jar.namelist():
                        content = jar.read("META-INF/mods.toml").decode(errors='ignore')
                        dep_blocks = re.findall(r'\[\[dependencies\..*?\]\](.*?)\n\n', content, re.DOTALL)
                        for dep_block in dep_blocks:
                            dep_id_match = re.search(r'modId\s*=\s*"(.*?)"', dep_block)
                            dep_ver_match = re.search(r'versionRange\s*=\s*"(.*?)"', dep_block)
                            if dep_id_match and dep_ver_match:
                                dep_id = dep_id_match.group(1).lower()
                                dep_ver_range = dep_ver_match.group(1)
                                min_version = parse_version_range(dep_ver_range)
                                installed = mods_info.get(dep_id, [])
                                if installed:
                                    satisfied = False
                                    for ins in installed:
                                        try:
                                            if version.parse(ins['mod_version']) >= version.parse(min_version):
                                                satisfied = True
                                                break
                                        except:
                                            continue
                                    if not satisfied:
                                        conflicts.append({
                                            "mod": e['filename'],
                                            "dependency": dep_id,
                                            "required": dep_ver_range,
                                            "installed": [i['mod_version'] for i in installed]
                                        })
                                else:
                                    conflicts.append({
                                        "mod": e['filename'],
                                        "dependency": dep_id,
                                        "required": dep_ver_range,
                                        "installed": []
                                    })
            except:
                continue
    return conflicts

neo_conflicts = check_neoforge_conflicts(mods_info)
if neo_conflicts:
    conflicts_folder = os.path.join(output_root, "Conflicts")
    os.makedirs(conflicts_folder, exist_ok=True)
    path_conf = os.path.join(conflicts_folder, f"Conflicts - {timestamp()}.txt")
    with open(path_conf, 'w', encoding='utf-8') as f:
        for c in neo_conflicts:
            f.write(f"{c['mod']} requires {c['dependency']} {c['required']}, installed versions: {c['installed']}\n")
    color_print(f"NeoForge dependency conflicts logged to {path_conf}", Colors.ORANGE)

# --------------------------
# Terminal output capture
# --------------------------
terminal_folder = os.path.join(output_root, "Terminal Output")
os.makedirs(terminal_folder, exist_ok=True)
terminal_file = os.path.join(terminal_folder, f"Terminal Output - {timestamp()}.txt")
with open(terminal_file, 'w', encoding='utf-8') as f:
    for mod_name, entries in mods_info.items():
        f.write(f"{mod_name}:\n")
        for e in entries:
            f.write(f"  - {e['filename']} [MC:{e['mc_version']}, MOD:{e['mod_version']}]\n")
color_print(f"Full terminal output saved to {terminal_file}", Colors.OKBLUE)

# --------------------------
# Keep terminal open with confirmations
# --------------------------
while True:
    color_print("Do you want to close the terminal? 1=Yes 2=No", Colors.BOLD)
    ans = input("> ")
    if ans.strip()=="1":
        color_print("Press ENTER twice to exit...", Colors.BOLD)
        input()
        input()
        break
    elif ans.strip()=="2":
        color_print("Terminal will remain open.", Colors.OKGREEN)
        break
