import requests
import os
import json
import tkinter as tk
from tkinter import messagebox, Toplevel
from tkinter.ttk import Progressbar, Notebook
from threading import Thread

CONFIG_FILE = 'config.json'

# === Загрузка и сохранение конфигурации ===
def load_config():
    if not os.path.exists(CONFIG_FILE):
        default_config = {
            "mc_version": "",
            "mod_loader": "Forge",
            "default_keywords": [],
            "download_folder": "downloads",
            "user_agent": "ModPackBuilder/1.0 (+contact@yourdomain.com)",
        }
        with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
            json.dump(default_config, f, indent=4, ensure_ascii=False)
    with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
        return json.load(f)

def save_config(config):
    with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
        json.dump(config, f, indent=4, ensure_ascii=False)

# === Modrinth API запросы и обработка ===
def search_projects_modrinth(description, project_type, mc_version=None, modloader=None, user_agent="ModPackBuilder"):
    url = 'https://api.modrinth.com/v2/search'
    headers = {'User-Agent': user_agent}
    facets = [
        [f"project_type:{project_type}"],
    ]
    if mc_version:
        facets.append([f"versions:{mc_version}"])
    if modloader:
        facets.append([f"categories:{modloader.lower()}"])
    params = {'query': description, 'facets': json.dumps(facets), 'limit': 100}
    response = requests.get(url, params=params, headers=headers)
    if response.status_code == 200:
        return response.json().get('hits', [])
    else:
        messagebox.showerror("Ошибка", f"Modrinth API Ошибка: {response.status_code}")
        return []

def get_latest_compatible_version_modrinth(project_id, mc_version=None, modloader=None, user_agent="ModPackBuilder"):
    url = f'https://api.modrinth.com/v2/project/{project_id}/version'
    headers = {'User-Agent': user_agent}
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        versions = response.json()
        if mc_version and modloader:
            compatible_versions = [v for v in versions if mc_version in v['game_versions'] and modloader.lower() in [loader.lower() for loader in v['loaders']]]
        elif mc_version:
            compatible_versions = [v for v in versions if mc_version in v['game_versions']]
        else:
            compatible_versions = versions

        if compatible_versions:
            for version in compatible_versions:
                if version['version_type'] == 'release':
                    return version
            return compatible_versions[0]
    return None

def get_mod_dependencies(version):
    dependencies = version.get('dependencies', [])
    required = []
    for dep in dependencies:
        if dep['dependency_type'] == 'required':
            required.append(dep['project_id'])
    return required

def get_mod_by_id(mod_id, user_agent="ModPackBuilder"):
    url = f'https://api.modrinth.com/v2/project/{mod_id}'
    headers = {'User-Agent': user_agent}
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        return response.json()
    return None

# === Spiget API для плагинов ===
def search_plugins_spiget(query, user_agent="ModPackBuilder"):
    url = f'https://api.spiget.org/v2/search/resources/{query}'
    headers = {'User-Agent': user_agent}
    params = {'size': 100}
    response = requests.get(url, params=params, headers=headers)
    if response.status_code == 200:
        return response.json()
    else:
        messagebox.showerror("Ошибка", f"Spiget API Ошибка: {response.status_code}")
        return []

def get_plugin_info_spiget(plugin_id, user_agent="ModPackBuilder"):
    url = f'https://api.spiget.org/v2/resources/{plugin_id}'
    headers = {'User-Agent': user_agent}
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        return response.json()
    return None

# === Функция загрузки файла ===
def download_file(url, dest_folder, filename, progress_bar=None, user_agent="ModPackBuilder"):
    local_filename = os.path.join(dest_folder, filename)
    headers = {'User-Agent': user_agent}
    with requests.get(url, stream=True, headers=headers) as r:
        r.raise_for_status()
        total_size = int(r.headers.get('content-length', 0))
        with open(local_filename, 'wb') as f:
            downloaded = 0
            for chunk in r.iter_content(chunk_size=8192):
                if chunk:
                    f.write(chunk)
                    downloaded += len(chunk)
                    if progress_bar and total_size > 0:
                        progress = (downloaded / total_size) * 100
                        progress_bar['value'] = progress
                        progress_bar.update()
    return local_filename

# === GUI ===
class ModPackBuilder:
    def __init__(self, root, config):
        self.root = root
        self.config = config
        self.root.title("ModPack Builder")

        # Разрешаем изменение размера окна
        self.root.resizable(True, True)

        # Создаем общий фрейм для размещения виджетов
        self.main_frame = tk.Frame(root)
        self.main_frame.pack(fill=tk.BOTH, expand=True)

        # === Переменные ===
        self.mc_version_var = tk.StringVar(value=config["mc_version"])
        self.modloader_var = tk.StringVar(value=config.get("mod_loader", "Forge"))
        self.description_var = tk.StringVar(value=", ".join(config["default_keywords"]))
        self.build_type_var = tk.StringVar(value="client")

        # === Создаем вкладки ===
        self.tab_control = Notebook(self.main_frame)
        self.tab_control.pack(expand=1, fill="both")

        # Вкладки
        self.mods_tab = tk.Frame(self.tab_control)
        self.resourcepacks_tab = tk.Frame(self.tab_control)
        self.shaders_tab = tk.Frame(self.tab_control)
        self.plugins_tab = tk.Frame(self.tab_control)

        self.tab_control.add(self.mods_tab, text='Моды')
        self.tab_control.add(self.resourcepacks_tab, text='Ресурс-паки')
        self.tab_control.add(self.shaders_tab, text='Шейдеры')
        self.tab_control.add(self.plugins_tab, text='Плагины')

        # === Моды ===
        self.setup_mods_tab()

        # === Ресурс-паки ===
        self.setup_resourcepacks_tab()

        # === Шейдеры ===
        self.setup_shaders_tab()

        # === Плагины ===
        self.setup_plugins_tab()

        # === Настройки сборки ===
        self.setup_build_settings()

        # === Кнопка скачивания ===
        tk.Button(self.main_frame, text="Скачать сборку", command=self.download_build).pack(pady=5)

        # При изменении размера окна обновляем размеры виджетов
        self.root.bind('<Configure>', self.on_resize)

        # Словари для хранения выбранных элементов
        self.selected_mods_info = {}
        self.selected_resourcepacks_info = {}
        self.selected_shaders_info = {}
        self.selected_plugins_info = {}

    def setup_mods_tab(self):
        tk.Label(self.mods_tab, text="Версия Minecraft:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        tk.Entry(self.mods_tab, textvariable=self.mc_version_var).grid(row=0, column=1, sticky="ew", padx=5, pady=5)

        tk.Label(self.mods_tab, text="Mod Loader:").grid(row=1, column=0, sticky="w", padx=5, pady=5)
        tk.OptionMenu(self.mods_tab, self.modloader_var, "Forge", "Fabric", "Quilt", "Liteloader", "Cauldron").grid(row=1, column=1, sticky="ew", padx=5, pady=5)

        tk.Label(self.mods_tab, text="Описание (ключевые слова):").grid(row=2, column=0, sticky="w", padx=5, pady=5)
        tk.Entry(self.mods_tab, textvariable=self.description_var).grid(row=2, column=1, sticky="ew", padx=5, pady=5)

        tk.Button(self.mods_tab, text="Поиск модов", command=self.search_mods).grid(row=3, column=0, columnspan=2, pady=5)

        # Список модов
        self.mods_list = tk.Listbox(self.mods_tab, selectmode="multiple")
        self.mods_list.grid(row=4, column=0, columnspan=2, sticky="nsew", padx=5, pady=5)
        self.mods_tab.rowconfigure(4, weight=1)
        self.mods_tab.columnconfigure(1, weight=1)

        self.mods_scrollbar = tk.Scrollbar(self.mods_tab, orient="vertical", command=self.mods_list.yview)
        self.mods_scrollbar.grid(row=4, column=2, sticky="ns")
        self.mods_list.config(yscrollcommand=self.mods_scrollbar.set)

        tk.Button(self.mods_tab, text="Проверить совместимость", command=self.check_compatibility).grid(row=5, column=0, columnspan=2, pady=5)

    def setup_resourcepacks_tab(self):
        self.resourcepack_description_var = tk.StringVar()
        tk.Label(self.resourcepacks_tab, text="Описание (ключевые слова):").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        tk.Entry(self.resourcepacks_tab, textvariable=self.resourcepack_description_var).grid(row=0, column=1, sticky="ew", padx=5, pady=5)
        tk.Button(self.resourcepacks_tab, text="Поиск ресурс-паков", command=self.search_resourcepacks).grid(row=1, column=0, columnspan=2, pady=5)

        # Список ресурс-паков
        self.resourcepacks_list = tk.Listbox(self.resourcepacks_tab, selectmode="multiple")
        self.resourcepacks_list.grid(row=2, column=0, columnspan=2, sticky="nsew", padx=5, pady=5)
        self.resourcepacks_tab.rowconfigure(2, weight=1)
        self.resourcepacks_tab.columnconfigure(1, weight=1)

        self.resourcepacks_scrollbar = tk.Scrollbar(self.resourcepacks_tab, orient="vertical", command=self.resourcepacks_list.yview)
        self.resourcepacks_scrollbar.grid(row=2, column=2, sticky="ns")
        self.resourcepacks_list.config(yscrollcommand=self.resourcepacks_scrollbar.set)

    def setup_shaders_tab(self):
        self.shader_description_var = tk.StringVar()
        tk.Label(self.shaders_tab, text="Описание (ключевые слова):").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        tk.Entry(self.shaders_tab, textvariable=self.shader_description_var).grid(row=0, column=1, sticky="ew", padx=5, pady=5)
        tk.Button(self.shaders_tab, text="Поиск шейдеров", command=self.search_shaders).grid(row=1, column=0, columnspan=2, pady=5)

        # Список шейдеров
        self.shaders_list = tk.Listbox(self.shaders_tab, selectmode="multiple")
        self.shaders_list.grid(row=2, column=0, columnspan=2, sticky="nsew", padx=5, pady=5)
        self.shaders_tab.rowconfigure(2, weight=1)
        self.shaders_tab.columnconfigure(1, weight=1)

        self.shaders_scrollbar = tk.Scrollbar(self.shaders_tab, orient="vertical", command=self.shaders_list.yview)
        self.shaders_scrollbar.grid(row=2, column=2, sticky="ns")
        self.shaders_list.config(yscrollcommand=self.shaders_scrollbar.set)

    def setup_plugins_tab(self):
        self.plugin_description_var = tk.StringVar()
        tk.Label(self.plugins_tab, text="Поиск плагинов:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        tk.Entry(self.plugins_tab, textvariable=self.plugin_description_var).grid(row=0, column=1, sticky="ew", padx=5, pady=5)
        tk.Button(self.plugins_tab, text="Поиск плагинов", command=self.search_plugins).grid(row=1, column=0, columnspan=2, pady=5)

        # Список плагинов
        self.plugins_list = tk.Listbox(self.plugins_tab, selectmode="multiple")
        self.plugins_list.grid(row=2, column=0, columnspan=2, sticky="nsew", padx=5, pady=5)
        self.plugins_tab.rowconfigure(2, weight=1)
        self.plugins_tab.columnconfigure(1, weight=1)

        self.plugins_scrollbar = tk.Scrollbar(self.plugins_tab, orient="vertical", command=self.plugins_list.yview)
        self.plugins_scrollbar.grid(row=2, column=2, sticky="ns")
        self.plugins_list.config(yscrollcommand=self.plugins_scrollbar.set)

    def setup_build_settings(self):
        settings_frame = tk.Frame(self.main_frame)
        settings_frame.pack(fill=tk.X, padx=5, pady=5)

        tk.Label(settings_frame, text="Тип сборки:").grid(row=0, column=0, sticky="w", padx=5)
        tk.OptionMenu(settings_frame, self.build_type_var, "client", "server", "both").grid(row=0, column=1, sticky="w", padx=5)

    def on_resize(self, event):
        # Обновляем размеры виджетов при изменении размера окна
        self.main_frame.update_idletasks()

    # === Функции для вкладки модов ===
    def search_mods(self):
        description = self.description_var.get()
        mc_version = self.mc_version_var.get()
        modloader = self.modloader_var.get()

        self.config["mc_version"] = mc_version
        self.config["mod_loader"] = modloader
        self.config["default_keywords"] = description.split(", ")
        save_config(self.config)

        self.mods_list.delete(0, tk.END)
        self.mods = []

        mods = search_projects_modrinth(description, "mod", mc_version, modloader, self.config["user_agent"])
        for mod in mods:
            self.mods.append({"source": "Modrinth", "data": mod})
            self.mods_list.insert(tk.END, f"{mod['title']} - {mod['description']}")

    def check_compatibility(self):
        selected_indices = self.mods_list.curselection()
        if not selected_indices:
            messagebox.showwarning("Предупреждение", "Выберите хотя бы один мод для проверки.")
            return

        mc_version = self.config["mc_version"]
        modloader = self.config["mod_loader"].lower()

        incompatible_mods = []
        mod_map = {}

        # Получение информации о выбранных модах и их версиях
        for idx in selected_indices:
            mod_entry = self.mods[idx]
            mod = mod_entry["data"]
            mod_id = mod['project_id']
            mod_title = mod['title']
            version = get_latest_compatible_version_modrinth(mod_id, mc_version, modloader, self.config["user_agent"])
            if version:
                mod_map[mod_id] = {
                    'title': mod_title,
                    'version': version,
                    'dependencies': get_mod_dependencies(version),
                }
            else:
                incompatible_mods.append(mod_title)

        if incompatible_mods:
            messagebox.showerror("Несовместимые моды", f"Следующие моды не совместимы с версией {mc_version} и загрузчиком {modloader.capitalize()}:\n" + "\n".join(incompatible_mods))
            return

        # Проверка зависимостей и автоматическое добавление недостающих
        missing_dependencies = []
        processed_mods = set()
        mods_to_process = list(mod_map.keys())

        while mods_to_process:
            current_mods = mods_to_process
            mods_to_process = []
            for mod_id in current_mods:
                if mod_id in processed_mods:
                    continue
                mod_info = mod_map[mod_id]
                processed_mods.add(mod_id)
                for dep_id in mod_info['dependencies']:
                    if dep_id not in mod_map:
                        dep_project = get_mod_by_id(dep_id, self.config["user_agent"])
                        if dep_project:
                            dep_title = dep_project['title']
                            dep_version = get_latest_compatible_version_modrinth(dep_id, mc_version, modloader, self.config["user_agent"])
                            if dep_version:
                                # Добавляем зависимость в список модов
                                mod_map[dep_id] = {
                                    'title': dep_title,
                                    'version': dep_version,
                                    'dependencies': get_mod_dependencies(dep_version),
                                }
                                self.mods.append({"source": "Modrinth", "data": dep_project})
                                self.mods_list.insert(tk.END, f"{dep_title} - {dep_project['description']}")
                                # Отмечаем зависимость как выбранную
                                self.mods_list.select_set(tk.END)
                                # Добавляем в список для дальнейшей обработки зависимостей этой зависимости
                                mods_to_process.append(dep_id)
                            else:
                                missing_dependencies.append(dep_title)
                        else:
                            missing_dependencies.append(dep_id)

        if missing_dependencies:
            messagebox.showwarning("Отсутствующие зависимости", f"Следующие зависимости отсутствуют или несовместимы:\n" + "\n".join(set(missing_dependencies)))
            return

        # Сохраняем информацию о выбранных модах и их зависимостях для дальнейшего использования при скачивании
        self.selected_mods_info = mod_map

        messagebox.showinfo("Совместимость", "Выбранные моды совместимы между собой и с текущей версией Minecraft. Все необходимые зависимости будут скачаны.")

    # === Функции для вкладки ресурс-паков ===
    def search_resourcepacks(self):
        description = self.resourcepack_description_var.get()

        self.resourcepacks_list.delete(0, tk.END)
        self.resourcepacks = []

        resourcepacks = search_projects_modrinth(description, "resourcepack", user_agent=self.config["user_agent"])
        for rp in resourcepacks:
            self.resourcepacks.append({"source": "Modrinth", "data": rp})
            self.resourcepacks_list.insert(tk.END, f"{rp['title']} - {rp['description']}")

    # === Функции для вкладки шейдеров ===
    def search_shaders(self):
        description = self.shader_description_var.get()

        self.shaders_list.delete(0, tk.END)
        self.shaders = []

        shaders = search_projects_modrinth(description, "shader", user_agent=self.config["user_agent"])
        for shader in shaders:
            self.shaders.append({"source": "Modrinth", "data": shader})
            self.shaders_list.insert(tk.END, f"{shader['title']} - {shader['description']}")

    # === Функции для вкладки плагинов ===
    def search_plugins(self):
        query = self.plugin_description_var.get()

        self.plugins_list.delete(0, tk.END)
        self.plugins = []

        plugins = search_plugins_spiget(query, self.config["user_agent"])
        for plugin in plugins:
            self.plugins.append(plugin)
            self.plugins_list.insert(tk.END, f"{plugin['name']} - {plugin.get('tag', '')}")

    # === Функция скачивания сборки ===
    def download_build(self):
        build_type = self.build_type_var.get()

        if build_type in ["client", "both"]:
            self.download_client_build()

        if build_type in ["server", "both"]:
            self.download_server_build()

    def download_client_build(self):
        if not os.path.exists(self.config["download_folder"]):
            os.makedirs(self.config["download_folder"])

        self.progress_window = Toplevel(self.root)
        self.progress_window.title("Прогресс скачивания клиента")

        progress_bar = Progressbar(self.progress_window, orient="horizontal", length=300, mode="determinate")
        progress_bar.pack(pady=20)

        def download():
            # Скачивание модов
            mod_items = list(self.selected_mods_info.items())
            total_mods = len(mod_items)
            for idx, (mod_id, mod_info) in enumerate(mod_items):
                mod_title = mod_info['title']
                version = mod_info['version']
                files = [file for file in version['files'] if file['primary']]
                if files:
                    file_info = files[0]
                    filename = file_info['filename']
                    url = file_info['url']
                    dest_folder = os.path.join(self.config["download_folder"], "mods")
                    if not os.path.exists(dest_folder):
                        os.makedirs(dest_folder)
                    download_file(url, dest_folder, filename, progress_bar, self.config["user_agent"])
                else:
                    messagebox.showinfo("Информация", f"Нет файлов для мода {mod_title}.")

                progress = ((idx + 1) / total_mods) * 100
                progress_bar['value'] = progress
                progress_bar.update()

            # Скачивание ресурс-паков
            selected_indices = self.resourcepacks_list.curselection()
            for idx in selected_indices:
                rp_entry = self.resourcepacks[idx]
                rp = rp_entry["data"]
                version = get_latest_compatible_version_modrinth(rp['project_id'], user_agent=self.config["user_agent"])
                if version:
                    files = version['files']
                    if files:
                        file_info = files[0]
                        filename = file_info['filename']
                        url = file_info['url']
                        dest_folder = os.path.join(self.config["download_folder"], "resourcepacks")
                        if not os.path.exists(dest_folder):
                            os.makedirs(dest_folder)
                        download_file(url, dest_folder, filename, progress_bar, self.config["user_agent"])

            # Скачивание шейдеров
            selected_indices = self.shaders_list.curselection()
            for idx in selected_indices:
                shader_entry = self.shaders[idx]
                shader = shader_entry["data"]
                version = get_latest_compatible_version_modrinth(shader['project_id'], user_agent=self.config["user_agent"])
                if version:
                    files = version['files']
                    if files:
                        file_info = files[0]
                        filename = file_info['filename']
                        url = file_info['url']
                        dest_folder = os.path.join(self.config["download_folder"], "shaderpacks")
                        if not os.path.exists(dest_folder):
                            os.makedirs(dest_folder)
                        download_file(url, dest_folder, filename, progress_bar, self.config["user_agent"])

            messagebox.showinfo("Готово", "Клиентская сборка успешно скачана!")
            self.progress_window.destroy()

        Thread(target=download).start()

    def download_server_build(self):
        if not os.path.exists(self.config["download_folder"]):
            os.makedirs(self.config["download_folder"])

        self.progress_window = Toplevel(self.root)
        self.progress_window.title("Прогресс скачивания сервера")

        progress_bar = Progressbar(self.progress_window, orient="horizontal", length=300, mode="determinate")
        progress_bar.pack(pady=20)

        def download():
            # Скачивание серверных файлов
            server_folder = os.path.join(self.config["download_folder"], "server")
            if not os.path.exists(server_folder):
                os.makedirs(server_folder)

            mc_version = self.config["mc_version"]
            modloader = self.config["mod_loader"]
            user_agent = self.config["user_agent"]

            if modloader.lower() == "forge":
                # Скачиваем Forge Installer
                forge_version = self.get_forge_version(mc_version)
                if forge_version:
                    url = f"https://maven.minecraftforge.net/net/minecraftforge/forge/{mc_version}-{forge_version}/forge-{mc_version}-{forge_version}-installer.jar"
                    filename = f"forge-{mc_version}-{forge_version}-installer.jar"
                    download_file(url, server_folder, filename, progress_bar, user_agent)
                    # Запускаем установщик сервера
                    installer_path = os.path.join(server_folder, filename)
                    os.system(f'java -jar "{installer_path}" --installServer')
                    # Принимаем EULA
                    self.accept_eula(server_folder)
                else:
                    messagebox.showerror("Ошибка", "Не удалось найти версию Forge для данной версии Minecraft.")
                    return
            else:
                messagebox.showwarning("Внимание", f"Автоматическая установка сервера для {modloader} не поддерживается.")
                return

            # Скачивание модов
            mod_items = list(self.selected_mods_info.items())
            total_mods = len(mod_items)
            for idx, (mod_id, mod_info) in enumerate(mod_items):
                mod_title = mod_info['title']
                version = mod_info['version']
                files = [file for file in version['files'] if file['primary']]
                if files:
                    file_info = files[0]
                    filename = file_info['filename']
                    url = file_info['url']
                    dest_folder = os.path.join(server_folder, "mods")
                    if not os.path.exists(dest_folder):
                        os.makedirs(dest_folder)
                    download_file(url, dest_folder, filename, progress_bar, user_agent)
                else:
                    messagebox.showinfo("Информация", f"Нет файлов для мода {mod_title}.")

                progress = ((idx + 1) / total_mods) * 100
                progress_bar['value'] = progress
                progress_bar.update()

            # Скачивание плагинов
            selected_indices = self.plugins_list.curselection()
            for idx in selected_indices:
                plugin = self.plugins[idx]
                plugin_id = plugin['id']
                url = f'https://api.spiget.org/v2/resources/{plugin_id}/download'
                dest_folder = os.path.join(server_folder, "plugins")
                if not os.path.exists(dest_folder):
                    os.makedirs(dest_folder)
                filename = f"{plugin['name']}.jar"
                download_file(url, dest_folder, filename, progress_bar, user_agent)

            messagebox.showinfo("Готово", "Серверная сборка успешно скачана!")
            self.progress_window.destroy()

        Thread(target=download).start()

    def get_forge_version(self, mc_version):
        # Получение последней версии Forge для заданной версии Minecraft
        url = f"https://files.minecraftforge.net/maven/net/minecraftforge/forge/promotions_slim.json"
        response = requests.get(url)
        if response.status_code == 200:
            data = response.json()
            promotions = data.get('promos', {})
            key = f"{mc_version}-latest"
            forge_version = promotions.get(key)
            return forge_version
        return None

    def accept_eula(self, server_folder):
        eula_path = os.path.join(server_folder, 'eula.txt')
        with open(eula_path, 'w') as file:
            file.write('eula=true\n')

# === Запуск приложения ===
if __name__ == '__main__':
    config = load_config()
    root = tk.Tk()
    app = ModPackBuilder(root, config)
    root.mainloop()

Простая форма описания

Чекбоксы, ползунки или ключевые слова для упрощенного описания (например: "магия", "технические моды", "хардкор").
Фильтры модов

Позволяют исключать определенные категории модов (например, "моды с читерными предметами").
Сообщество и отзывы

Возможность интеграции с базой данных отзывов о модах, чтобы пользователь мог узнать популярность и качество.