package launcher

import (
	"archive/zip"
	"crypto/rand"
	"crypto/sha1"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"
)

// URL манифеста версий Minecraft
const versionManifestURL = "https://launchermeta.mojang.com/mc/game/version_manifest.json"

// Структура для хранения манифеста версий
type VersionManifest struct {
	Latest struct {
		Release  string `json:"release"`
		Snapshot string `json:"snapshot"`
	} `json:"latest"`
	Versions []struct {
		ID          string `json:"id"`
		Type        string `json:"type"`
		URL         string `json:"url"`
		Time        string `json:"time"`
		ReleaseTime string `json:"releaseTime"`
	} `json:"versions"`
}

// Структура для хранения данных версии
type VersionData map[string]interface{}

// Структура лаунчера
type Launcher struct {
	minelauncherPath string
	versionManifest  *VersionManifest
}

// Создает новый экземпляр лаунчера
func NewLauncher() *Launcher {
	return &Launcher{
		minelauncherPath: "./minecraft", // Задаем путь по умолчанию
	}
}

// Устанавливает пользовательский путь
func (l *Launcher) SetMinecraftPath(path string) {
	l.minelauncherPath = path
}

// Получает текущий пользовательский путь
func (l *Launcher) GetMinecraftPath() string {
	return l.minelauncherPath
}

// Получает манифест версий
func (l *Launcher) getVersionManifest() (*VersionManifest, error) {
	fmt.Println("Получение манифеста версий...")
	resp, err := http.Get(versionManifestURL)
	if err != nil {
		return nil, fmt.Errorf("не удалось получить манифест версий: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("не удалось получить манифест версий, статус код: %d", resp.StatusCode)
	}

	bodyBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("не удалось прочитать тело ответа манифеста версий: %v", err)
	}

	var manifest VersionManifest
	err = json.Unmarshal(bodyBytes, &manifest)
	if err != nil {
		return nil, fmt.Errorf("ошибка при разборе манифеста версий: %v", err)
	}
	return &manifest, nil
}

// Возвращает список доступных версий
func (l *Launcher) GetMinecraftVersions() ([]string, error) {
	if l.versionManifest == nil {
		manifest, err := l.getVersionManifest()
		if err != nil {
			return nil, err
		}
		l.versionManifest = manifest
	}

	versions := make([]string, len(l.versionManifest.Versions))
	for i, v := range l.versionManifest.Versions {
		versions[i] = fmt.Sprintf("%s (%s)", v.ID, v.Type)
	}
	return versions, nil
}

// Получает данные выбранной версии
func (l *Launcher) getVersionDetails(versionID string) (VersionData, error) {
	fmt.Printf("Получение данных для версии: %s\n", versionID)
	// Ищем URL версии в манифесте
	var versionURL string
	for _, version := range l.versionManifest.Versions {
		if version.ID == versionID {
			versionURL = version.URL
			break
		}
	}
	if versionURL == "" {
		return nil, fmt.Errorf("версия %s не найдена в манифесте", versionID)
	}

	resp, err := http.Get(versionURL)
	if err != nil {
		return nil, fmt.Errorf("не удалось получить данные версии: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("не удалось получить данные версии, статус код: %d", resp.StatusCode)
	}

	bodyBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("не удалось прочитать тело ответа версии: %v", err)
	}

	var versionData VersionData
	err = json.Unmarshal(bodyBytes, &versionData)
	if err != nil {
		return nil, fmt.Errorf("ошибка при разборе данных версии: %v", err)
	}
	return versionData, nil
}

// Функция для скачивания файла по URL с повторными попытками
func (l *Launcher) downloadFile(url, filepath string) error {
	fmt.Printf("Скачивание файла: %s\n", url)
	client := http.Client{
		Timeout: 30 * time.Second,
	}

	resp, err := client.Get(url)
	if err != nil {
		return fmt.Errorf("ошибка при выполнении GET-запроса: %v", err)
	}
	defer resp.Body.Close()

	// Проверяем код ответа
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("не удалось скачать файл: %s, статус код: %d", url, resp.StatusCode)
	}

	out, err := os.Create(filepath)
	if err != nil {
		return fmt.Errorf("не удалось создать файл %s: %v", filepath, err)
	}
	defer out.Close()

	_, err = io.Copy(out, resp.Body)
	if err != nil {
		return fmt.Errorf("ошибка при записи файла %s: %v", filepath, err)
	}

	fmt.Printf("Файл скачан и сохранён: %s\n", filepath)
	return nil
}

// Скачивание файла с повторами
func (l *Launcher) downloadFileWithRetry(url, filepath string, retries int) error {
	for i := 0; i <= retries; i++ {
		err := l.downloadFile(url, filepath)
		if err == nil {
			return nil
		}
		fmt.Printf("Попытка %d скачать файл %s не удалась, ошибка: %v\n", i+1, url, err)
		time.Sleep(2 * time.Second) // Ждем перед повторной попыткой
	}
	return fmt.Errorf("не удалось скачать файл после %d попыток: %s", retries+1, url)
}

// Проверка SHA1 хэша файла
func (l *Launcher) verifyFileChecksum(filePath, expectedHash string) bool {
	file, err := os.Open(filePath)
	if err != nil {
		fmt.Printf("Не удалось открыть файл для проверки хэша: %s, ошибка: %v\n", filePath, err)
		return false
	}
	defer file.Close()

	hash := sha1.New()
	if _, err := io.Copy(hash, file); err != nil {
		fmt.Printf("Не удалось вычислить хэш для файла: %s, ошибка: %v\n", filePath, err)
		return false
	}
	calculatedHash := hex.EncodeToString(hash.Sum(nil))
	if expectedHash != calculatedHash {
		fmt.Printf("Хэш не совпадает для файла: %s (ожидалось: %s, получено: %s)\n", filePath, expectedHash, calculatedHash)
		return false
	}
	return true
}

// Функция для проверки, следует ли включать библиотеку согласно правилам
func (l *Launcher) shouldIncludeLibrary(libInfo map[string]interface{}) bool {
	// По умолчанию предполагаем, что библиотека должна быть включена
	allowed := true
	if rules, ok := libInfo["rules"].([]interface{}); ok {
		for _, ruleInterface := range rules {
			rule, ok := ruleInterface.(map[string]interface{})
			if !ok {
				continue
			}
			action, ok := rule["action"].(string)
			if !ok {
				continue
			}

			applies := true

			// Проверяем соответствие ОС, если указано
			if osDict, ok := rule["os"].(map[string]interface{}); ok {
				osName := runtime.GOOS
				if osName == "darwin" {
					osName = "osx"
				}
				if name, ok := osDict["name"].(string); ok && name != osName {
					applies = false
				}
				// Здесь можно добавить дополнительные проверки, например, версия ОС
			}

			// Если правило применяется, обновляем флаг allowed
			if applies {
				if action == "allow" {
					allowed = true
				} else if action == "disallow" {
					allowed = false
				}
				// Правила применяются по порядку, последнее применимое определяет итоговое действие
			}
		}
	}
	return allowed
}

// Функция для получения имени нативного классификатора в зависимости от ОС и архитектуры
func (l *Launcher) getNatives(libInfo map[string]interface{}) string {
	archType := "64"
	if runtime.GOARCH == "386" || runtime.GOARCH == "arm" {
		archType = "32"
	}

	natives, ok := libInfo["natives"].(map[string]interface{})
	if !ok {
		return ""
	}

	var classifierName string
	switch runtime.GOOS {
	case "windows":
		if val, exists := natives["windows"].(string); exists {
			classifierName = strings.ReplaceAll(val, "${arch}", archType)
		}
	case "darwin":
		if val, exists := natives["osx"].(string); exists {
			classifierName = strings.ReplaceAll(val, "${arch}", archType)
		}
	case "linux":
		if val, exists := natives["linux"].(string); exists {
			classifierName = strings.ReplaceAll(val, "${arch}", archType)
		}
	default:
		return ""
	}

	return classifierName
}

// Функция для извлечения файлов из ZIP-архива
func (l *Launcher) unzip(src, dest string) error {
	fmt.Printf("Распаковка архива: %s в %s\n", src, dest)
	r, err := zip.OpenReader(src)
	if err != nil {
		return fmt.Errorf("не удалось открыть ZIP-архив %s: %v", src, err)
	}
	defer r.Close()

	for _, f := range r.File {
		// Пропускаем файлы с META-INF
		if strings.HasPrefix(f.Name, "META-INF") {
			continue
		}

		fpath := filepath.Join(dest, f.Name)

		// Проверка на безопасность пути
		if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
			return fmt.Errorf("недопустимый путь в архиве: %s", fpath)
		}

		if f.FileInfo().IsDir() {
			err := os.MkdirAll(fpath, os.ModePerm)
			if err != nil {
				return fmt.Errorf("не удалось создать директорию %s: %v", fpath, err)
			}
			continue
		}

		if err := os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
			return fmt.Errorf("не удалось создать директорию %s: %v", filepath.Dir(fpath), err)
		}

		outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
		if err != nil {
			return fmt.Errorf("не удалось открыть файл для записи %s: %v", fpath, err)
		}

		rc, err := f.Open()
		if err != nil {
			outFile.Close()
			return fmt.Errorf("не удалось открыть файл внутри архива %s: %v", f.Name, err)
		}

		_, err = io.Copy(outFile, rc)
		outFile.Close()
		rc.Close()

		if err != nil {
			return fmt.Errorf("ошибка при копировании файла %s: %v", fpath, err)
		}
	}
	return nil
}

// Функция для проверки наличия основного класса в client.jar
func (l *Launcher) verifyMainClass(clientJarPath string) bool {
	r, err := zip.OpenReader(clientJarPath)
	if err != nil {
		fmt.Printf("Не удалось открыть client.jar для проверки main class: %v\n", err)
		return false
	}
	defer r.Close()

	target := "net/minecraft/client/main/Main.class"
	for _, f := range r.File {
		if f.Name == target {
			return true
		}
	}
	fmt.Println("Main class не найден в client.jar")
	return false
}

// Скачивание клиентского JAR-файла
func (l *Launcher) downloadClientJar(versionData VersionData, versionID string) error {
	fmt.Println("Скачивание клиентского JAR-файла...")
	downloads, ok := versionData["downloads"].(map[string]interface{})
	if !ok {
		return fmt.Errorf("не удалось найти downloads в данных версии")
	}
	client, ok := downloads["client"].(map[string]interface{})
	if !ok {
		return fmt.Errorf("не удалось найти client в downloads")
	}
	url, ok := client["url"].(string)
	if !ok {
		return fmt.Errorf("не удалось получить URL клиента")
	}
	expectedHash, ok := client["sha1"].(string)
	if !ok {
		return fmt.Errorf("не удалось получить sha1 клиента")
	}

	// Путь для сохранения JAR-файла
	dirPath := filepath.Join(l.minelauncherPath, "versions", versionID)
	os.MkdirAll(dirPath, os.ModePerm)
	filePath := filepath.Join(dirPath, versionID+".jar")

	// Проверяем, существует ли файл и совпадает ли хэш
	if _, err := os.Stat(filePath); err == nil {
		if l.verifyFileChecksum(filePath, expectedHash) {
			fmt.Println("Клиентский JAR-файл уже существует и корректен, пропускаем скачивание.")
			return nil
		} else {
			fmt.Println("Хэш клиентского JAR-файла не совпадает, перезагружаем файл.")
			os.Remove(filePath)
		}
	}

	// Формируем абсолютный путь к файлу
	absFilePath, err := filepath.Abs(filePath)
	if err != nil {
		return fmt.Errorf("не удалось получить абсолютный путь к файлу %s: %v", filePath, err)
	}

	err = l.downloadFileWithRetry(url, absFilePath, 4)
	if err != nil {
		return fmt.Errorf("ошибка при скачивании клиентского JAR-файла: %v", err)
	}

	// Проверяем хэш после загрузки
	if !l.verifyFileChecksum(absFilePath, expectedHash) {
		return fmt.Errorf("хэш клиентского JAR-файла не совпадает после загрузки")
	}

	// Проверяем наличие основного класса
	if !l.verifyMainClass(absFilePath) {
		return fmt.Errorf("файл client.jar не содержит необходимого основного класса")
	}

	return nil
}

// Скачивание библиотек
func (l *Launcher) downloadLibraries(versionData VersionData) error {
	fmt.Println("Скачивание библиотек...")
	libraries, ok := versionData["libraries"].([]interface{})
	if !ok {
		return fmt.Errorf("не удалось получить библиотеки из данных версии")
	}
	var wg sync.WaitGroup
	semaphore := make(chan struct{}, 10) // Ограничиваем количество одновременных загрузок

	for _, lib := range libraries {
		libInfo, ok := lib.(map[string]interface{})
		if !ok {
			continue
		}

		// Проверяем, следует ли включать эту библиотеку
		if !l.shouldIncludeLibrary(libInfo) {
			continue
		}

		downloads, ok := libInfo["downloads"].(map[string]interface{})
		if !ok {
			continue
		}

		artifact, ok := downloads["artifact"].(map[string]interface{})
		if !ok {
			continue
		}

		url, ok := artifact["url"].(string)
		if !ok {
			continue
		}
		path, ok := artifact["path"].(string)
		if !ok {
			continue
		}
		expectedHash, ok := artifact["sha1"].(string)
		if !ok {
			continue
		}

		// Путь для сохранения файла
		filePath := filepath.Join(l.minelauncherPath, "libraries", filepath.FromSlash(path))
		absFilePath, err := filepath.Abs(filePath)
		if err != nil {
			fmt.Printf("Не удалось получить абсолютный путь для файла %s: %v\n", filePath, err)
			continue
		}
		dir := filepath.Dir(absFilePath)
		os.MkdirAll(dir, os.ModePerm)

		// Проверяем, существует ли файл и совпадает ли хэш
		if _, err := os.Stat(absFilePath); err == nil {
			if l.verifyFileChecksum(absFilePath, expectedHash) {
				// Файл существует и корректен, пропускаем скачивание
				continue
			} else {
				fmt.Printf("Хэш библиотеки не совпадает, перезагружаем файл: %s\n", absFilePath)
				os.Remove(absFilePath)
			}
		}

		wg.Add(1)
		semaphore <- struct{}{} // блокирует, если семафор заполнен

		go func(url, absFilePath, expectedHash string) {
			defer wg.Done()
			defer func() { <-semaphore }()

			err := l.downloadFileWithRetry(url, absFilePath, 4)
			if err != nil {
				fmt.Printf("Ошибка при скачивании библиотеки %s: %v\n", url, err)
				return
			}

			if !l.verifyFileChecksum(absFilePath, expectedHash) {
				fmt.Printf("Хэш библиотеки не совпадает после загрузки: %s\n", absFilePath)
				os.Remove(absFilePath)
			}
		}(url, absFilePath, expectedHash)
	}
	wg.Wait()
	return nil
}

// Скачивание ассетов с параллельной загрузкой
func (l *Launcher) downloadAssets(versionData VersionData) error {
	fmt.Println("Скачивание ассетов...")
	assetIndex, ok := versionData["assetIndex"].(map[string]interface{})
	if !ok {
		return fmt.Errorf("не удалось найти assetIndex в данных версии")
	}
	url, ok := assetIndex["url"].(string)
	if !ok {
		return fmt.Errorf("не удалось получить URL assetIndex")
	}
	expectedHash, ok := assetIndex["sha1"].(string)
	if !ok {
		return fmt.Errorf("не удалось получить sha1 assetIndex")
	}

	// Скачиваем индекс ассетов
	fmt.Println("Скачивание индекса ассетов...")
	resp, err := http.Get(url)
	if err != nil {
		return fmt.Errorf("не удалось скачать индекс ассетов: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("не удалось скачать индекс ассетов, статус код: %d", resp.StatusCode)
	}

	bodyBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("не удалось прочитать индекс ассетов: %v", err)
	}

	// Проверяем хэш индекса ассетов
	hash := sha1.New()
	hash.Write(bodyBytes)
	calculatedHash := hex.EncodeToString(hash.Sum(nil))
	if expectedHash != calculatedHash {
		return fmt.Errorf("хэш индекса ассетов не совпадает (ожидалось: %s, получено: %s)", expectedHash, calculatedHash)
	}

	var assetIndexData map[string]interface{}
	err = json.Unmarshal(bodyBytes, &assetIndexData)
	if err != nil {
		return fmt.Errorf("ошибка при разборе индекса ассетов: %v", err)
	}

	// Сохраняем индекс ассетов
	fmt.Println("Сохранение индекса ассетов...")
	assetsDir := filepath.Join(l.minelauncherPath, "assets", "indexes")
	os.MkdirAll(assetsDir, os.ModePerm)
	indexFilePath := filepath.Join(assetsDir, fmt.Sprintf("%s.json", assetIndex["id"].(string)))
	err = os.WriteFile(indexFilePath, bodyBytes, 0644)
	if err != nil {
		return fmt.Errorf("ошибка при сохранении индекса ассетов: %v", err)
	}

	objects, ok := assetIndexData["objects"].(map[string]interface{})
	if !ok {
		return fmt.Errorf("не удалось получить объекты ассетов")
	}

	totalAssets := len(objects)
	fmt.Printf("Всего ассетов для загрузки: %d\n", totalAssets)

	var wg sync.WaitGroup
	semaphore := make(chan struct{}, 20) // Увеличиваем количество одновременных загрузок для ассетов

	currentAsset := 0
	mutex := &sync.Mutex{}

	for _, obj := range objects {
		wg.Add(1)
		semaphore <- struct{}{} // блокирует, если семафор заполнен

		go func(obj interface{}) {
			defer wg.Done()
			defer func() { <-semaphore }() // освобождаем место в семафоре

			objInfo, ok := obj.(map[string]interface{})
			if !ok {
				return
			}
			hashStr, ok := objInfo["hash"].(string)
			if !ok {
				return
			}
			sizeFloat, ok := objInfo["size"].(float64)
			if !ok {
				return
			}
			size := int64(sizeFloat)
			if len(hashStr) < 2 {
				fmt.Printf("Некорректный хэш ассета: %s\n", hashStr)
				return
			}
			subHash := hashStr[:2]

			assetURL := fmt.Sprintf("https://resources.download.minecraft.net/%s/%s", subHash, hashStr)
			filePath := filepath.Join(l.minelauncherPath, "assets", "objects", subHash, hashStr)
			absFilePath, err := filepath.Abs(filePath)
			if err != nil {
				fmt.Printf("Не удалось получить абсолютный путь для ассета %s: %v\n", filePath, err)
				return
			}
			dir := filepath.Dir(absFilePath)
			os.MkdirAll(dir, os.ModePerm)

			// Проверяем, существует ли файл и его размер
			if fi, err := os.Stat(absFilePath); err == nil && fi.Size() == size {
				// Файл существует и его размер соответствует, пропускаем
			} else {
				err := l.downloadFileWithRetry(assetURL, absFilePath, 4)
				if err != nil {
					fmt.Printf("\nОшибка при скачивании ассета %s: %v\n", assetURL, err)
					return
				}
				// Дополнительно можно проверить хэш ассета здесь
				// Можно реализовать verifyFileChecksum для ассетов, если необходимо
			}

			mutex.Lock()
			currentAsset++
			progress := float64(currentAsset) / float64(totalAssets) * 100
			fmt.Printf("\rСкачивание ассетов: %d/%d (%.2f%%)", currentAsset, totalAssets, progress)
			mutex.Unlock()
		}(obj)
	}

	wg.Wait()
	fmt.Println("\nЗагрузка ассетов завершена.")
	return nil
}

// Функция для извлечения нативных библиотек
func (l *Launcher) extractNatives(versionData VersionData, nativesDir string) error {
	fmt.Println("Извлечение нативных библиотек...")
	libraries, ok := versionData["libraries"].([]interface{})
	if !ok {
		return fmt.Errorf("не удалось получить библиотеки для распаковки нативов")
	}

	for _, lib := range libraries {
		libInfo, ok := lib.(map[string]interface{})
		if !ok {
			continue
		}

		// Проверяем, следует ли включать эту библиотеку
		if !l.shouldIncludeLibrary(libInfo) {
			continue
		}

		downloads, ok := libInfo["downloads"].(map[string]interface{})
		if !ok {
			continue
		}

		classifiers, ok := downloads["classifiers"].(map[string]interface{})
		if !ok {
			continue
		}

		classifierName := l.getNatives(libInfo)
		if classifierName == "" {
			continue
		}

		native, ok := classifiers[classifierName].(map[string]interface{})
		if !ok {
			continue
		}

		url, ok := native["url"].(string)
		if !ok {
			continue
		}
		path, ok := native["path"].(string)
		if !ok {
			continue
		}
		expectedHash, ok := native["sha1"].(string)
		if !ok {
			continue
		}

		// Путь для сохранения нативного JAR-файла
		filePath := filepath.Join(l.minelauncherPath, "libraries", filepath.FromSlash(path))
		absFilePath, err := filepath.Abs(filePath)
		if err != nil {
			fmt.Printf("Не удалось получить абсолютный путь для файла %s: %v\n", filePath, err)
			continue
		}
		dir := filepath.Dir(absFilePath)
		os.MkdirAll(dir, os.ModePerm)

		// Проверяем, существует ли файл и совпадает ли хэш
		if _, err := os.Stat(absFilePath); err == nil {
			if l.verifyFileChecksum(absFilePath, expectedHash) {
				// Файл существует и корректен, пропускаем скачивание
				fmt.Printf("Нативная библиотека уже существует и корректна: %s\n", absFilePath)
			} else {
				fmt.Printf("Хэш нативной библиотеки не совпадает, перезагружаем файл: %s\n", absFilePath)
				os.Remove(absFilePath)
			}
		}

		if _, err := os.Stat(absFilePath); os.IsNotExist(err) {
			fmt.Printf("Скачивание нативной библиотеки: %s\n", absFilePath)
			err := l.downloadFileWithRetry(url, absFilePath, 4)
			if err != nil {
				fmt.Printf("Ошибка при скачивании нативной библиотеки %s: %v\n", url, err)
				return err
			}
		}

		// Проверяем хэш после загрузки
		if !l.verifyFileChecksum(absFilePath, expectedHash) {
			return fmt.Errorf("хэш нативной библиотеки не совпадает после загрузки: %s", absFilePath)
		}

		// Распаковываем нативы
		fmt.Printf("Распаковка нативных библиотек из %s в %s\n", absFilePath, nativesDir)
		err = l.unzip(absFilePath, nativesDir)
		if err != nil {
			return fmt.Errorf("ошибка при распаковке нативной библиотеки %s: %v", absFilePath, err)
		}
	}

	return nil
}

// Построение classpath для запуска игры
func (l *Launcher) buildClasspath(versionData VersionData, versionID string) string {
	var classpath []string

	// Добавляем библиотеки
	libraries, ok := versionData["libraries"].([]interface{})
	if !ok {
		fmt.Println("Не удалось получить библиотеки для classpath")
		return ""
	}
	for _, lib := range libraries {
		libInfo, ok := lib.(map[string]interface{})
		if !ok {
			continue
		}
		if !l.shouldIncludeLibrary(libInfo) {
			continue
		}
		downloads, ok := libInfo["downloads"].(map[string]interface{})
		if !ok {
			continue
		}
		artifact, ok := downloads["artifact"].(map[string]interface{})
		if !ok {
			continue
		}
		path, ok := artifact["path"].(string)
		if !ok {
			continue
		}
		libPath := filepath.Join(l.minelauncherPath, "libraries", filepath.FromSlash(path))
		absLibPath, err := filepath.Abs(libPath)
		if err != nil {
			fmt.Printf("Не удалось получить абсолютный путь для библиотеки %s: %v\n", libPath, err)
			continue
		}
		classpath = append(classpath, absLibPath)
	}

	// Добавляем клиентский JAR
	clientJar := filepath.Join(l.minelauncherPath, "versions", versionID, versionID+".jar")
	absClientJar, err := filepath.Abs(clientJar)
	if err != nil {
		fmt.Printf("Не удалось получить абсолютный путь для client.jar %s: %v\n", clientJar, err)
		return ""
	}
	classpath = append(classpath, absClientJar)

	return strings.Join(classpath, string(os.PathListSeparator))
}

// Функция для замены переменных в аргументах
func (l *Launcher) replaceVariables(arg, username, versionID string, versionData VersionData, uuid, authToken string) string {
	assetIndex := l.versionDataGetAssetIndex(versionData)

	arg = strings.ReplaceAll(arg, "${auth_player_name}", username)
	arg = strings.ReplaceAll(arg, "${version_name}", versionID)
	arg = strings.ReplaceAll(arg, "${game_directory}", l.minelauncherPath)
	arg = strings.ReplaceAll(arg, "${assets_root}", filepath.Join(l.minelauncherPath, "assets"))
	arg = strings.ReplaceAll(arg, "${assets_index_name}", l.getAssetIndexName(assetIndex))
	arg = strings.ReplaceAll(arg, "${auth_uuid}", uuid)
	arg = strings.ReplaceAll(arg, "${auth_access_token}", authToken)
	arg = strings.ReplaceAll(arg, "${clientid}", "")
	arg = strings.ReplaceAll(arg, "${auth_xuid}", "")
	arg = strings.ReplaceAll(arg, "${version_type}", l.versionDataGetVersionType(versionData))
	// Эмулируем пиратский режим
	arg = strings.ReplaceAll(arg, "${user_type}", "legacy")
	// Убираем демо режим
	arg = strings.ReplaceAll(arg, "--demo", "")
	// Заменяем остальные переменные на пустые строки
	arg = strings.ReplaceAll(arg, "${resolution_width}", "")
	arg = strings.ReplaceAll(arg, "${resolution_height}", "")
	arg = strings.ReplaceAll(arg, "${natives_directory}", filepath.Join(l.minelauncherPath, "versions", versionID, "natives"))
	return arg
}

// Получение имени индекса ассетов
func (l *Launcher) getAssetIndexName(assetIndex map[string]interface{}) string {
	if id, ok := assetIndex["id"].(string); ok {
		return id
	}
	return "legacy"
}

// Получение assetIndex из versionData
func (l *Launcher) versionDataGetAssetIndex(versionData VersionData) map[string]interface{} {
	if assetIndex, ok := versionData["assetIndex"].(map[string]interface{}); ok {
		return assetIndex
	}
	return map[string]interface{}{}
}

// Получение версии типа из versionData
func (l *Launcher) versionDataGetVersionType(versionData VersionData) string {
	if versionType, ok := versionData["type"].(string); ok {
		return versionType
	}
	return "release"
}

// Функция для проверки правил
func (l *Launcher) checkRule(rules []interface{}) bool {
	allowed := false
	for _, ruleInterface := range rules {
		rule, ok := ruleInterface.(map[string]interface{})
		if !ok {
			continue
		}
		action, ok := rule["action"].(string)
		if !ok {
			continue
		}

		applies := true

		// Проверяем соответствие ОС, если указано
		if osDict, ok := rule["os"].(map[string]interface{}); ok {
			osName := runtime.GOOS
			if osName == "darwin" {
				osName = "osx"
			}
			if name, ok := osDict["name"].(string); ok && name != osName {
				applies = false
			}
		}

		if applies {
			if action == "allow" {
				allowed = true
			} else if action == "disallow" {
				allowed = false
			}
		}
	}
	return allowed
}

// Построение аргументов игры без QuickPlay
func (l *Launcher) buildGameArguments(versionData VersionData, username, versionID, uuid, authToken string) []string {
	var args []string

	if arguments, ok := versionData["arguments"].(map[string]interface{}); ok {
		if gameArgsList, ok := arguments["game"].([]interface{}); ok {
			for _, arg := range gameArgsList {
				switch arg := arg.(type) {
				case string:
					// Проверяем и исключаем аргументы, связанные с QuickPlay
					if strings.Contains(strings.ToLower(arg), "quickplay") {
						continue
					}
					arg = l.replaceVariables(arg, username, versionID, versionData, uuid, authToken)
					args = append(args, arg)
				case map[string]interface{}:
					// Проверяем наличие правил
					if rules, ok := arg["rules"].([]interface{}); ok {
						if l.checkRule(rules) {
							if value, ok := arg["value"].(interface{}); ok {
								// Исключаем значения, связанные с QuickPlay
								args = l.appendArgumentValueWithoutQuickPlay(args, value, username, versionID, versionData, uuid, authToken)
							}
						}
					} else {
						// Если нет правил, сразу добавляем значение, если оно не связано с QuickPlay
						if value, ok := arg["value"].(interface{}); ok {
							args = l.appendArgumentValueWithoutQuickPlay(args, value, username, versionID, versionData, uuid, authToken)
						}
					}
				}
			}
		}
	} else if minecraftArguments, ok := versionData["minecraftArguments"].(string); ok {
		splitArgs := strings.Split(minecraftArguments, " ")
		for _, arg := range splitArgs {
			// Проверяем и исключаем аргументы, связанные с QuickPlay
			if strings.Contains(strings.ToLower(arg), "quickplay") {
				continue
			}
			arg = l.replaceVariables(arg, username, versionID, versionData, uuid, authToken)
			args = append(args, arg)
		}
	}

	return args
}

// Вспомогательная функция для добавления аргументов без QuickPlay
func (l *Launcher) appendArgumentValueWithoutQuickPlay(args []string, value interface{}, username, versionID string, versionData VersionData, uuid, authToken string) []string {
	switch v := value.(type) {
	case string:
		// Проверяем и исключаем аргументы, связанные с QuickPlay
		if strings.Contains(strings.ToLower(v), "quickplay") {
			return args
		}
		v = l.replaceVariables(v, username, versionID, versionData, uuid, authToken)
		args = append(args, v)
	case []interface{}:
		for _, item := range v {
			if sv, ok := item.(string); ok {
				// Проверяем и исключаем аргументы, связанные с QuickPlay
				if strings.Contains(strings.ToLower(sv), "quickplay") {
					continue
				}
				sv = l.replaceVariables(sv, username, versionID, versionData, uuid, authToken)
				args = append(args, sv)
			}
		}
	}
	return args
}

// Функция для запуска Minecraft
func (l *Launcher) launchMinecraft(versionData VersionData, authToken, username, uuid, versionID string) error {
	fmt.Println("Запуск Minecraft...")
	mainClass, ok := versionData["mainClass"].(string)
	if !ok {
		return fmt.Errorf("не удалось найти mainClass в данных версии")
	}
	fmt.Println("Main class:", mainClass)
	classpath := l.buildClasspath(versionData, versionID)
	fmt.Println("Classpath:", classpath)

	if classpath == "" {
		return fmt.Errorf("classpath пустой")
	}

	// Путь к директории natives
	nativesDir := filepath.Join(l.minelauncherPath, "versions", versionID, "natives")
	absNativesDir, err := filepath.Abs(nativesDir)
	if err != nil {
		return fmt.Errorf("не удалось получить абсолютный путь для nativesDir %s: %v", nativesDir, err)
	}
	os.MkdirAll(absNativesDir, os.ModePerm)

	// Распаковка нативных библиотек
	err = l.extractNatives(versionData, absNativesDir)
	if err != nil {
		return fmt.Errorf("ошибка при извлечении нативных библиотек: %v", err)
	}

	// Аргументы JVM
	jvmArgs := []string{
		"-Djava.library.path=" + absNativesDir,
		"-cp", classpath,
		"-Xmx1G",
		"-Xms512M",
	}

	// Аргументы игры
	gameArgs := l.buildGameArguments(versionData, username, versionID, uuid, authToken)

	if len(gameArgs) == 0 {
		return fmt.Errorf("аргументы игры пустые")
	}

	// Объединяем аргументы
	args := append(jvmArgs, mainClass)
	args = append(args, gameArgs...)

	// Команда запуска
	cmd := exec.Command("java", args...)
	cmd.Dir = l.minelauncherPath // Устанавливаем рабочую директорию
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	// Выводим команду запуска для отладки
	fmt.Println("Команда запуска:", strings.Join(cmd.Args, " "))

	// Запускаем игру
	err = cmd.Run()
	if err != nil {
		return fmt.Errorf("ошибка при запуске Minecraft: %v", err)
	}

	return nil
}

// Проверка наличия всех необходимых файлов перед запуском
func (l *Launcher) verifyAllFiles(versionData VersionData, versionID string) error {
	fmt.Println("Проверка наличия всех необходимых файлов...")
	// Проверка клиентского JAR
	clientJar := filepath.Join(l.minelauncherPath, "versions", versionID, versionID+".jar")
	if _, err := os.Stat(clientJar); os.IsNotExist(err) {
		return fmt.Errorf("клиентский JAR-файл отсутствует: %s", clientJar)
	}

	// Проверка библиотек
	libraries, ok := versionData["libraries"].([]interface{})
	if !ok {
		return fmt.Errorf("не удалось получить библиотеки для проверки")
	}
	for _, lib := range libraries {
		libInfo, ok := lib.(map[string]interface{})
		if !ok {
			continue
		}
		if !l.shouldIncludeLibrary(libInfo) {
			continue
		}
		downloads, ok := libInfo["downloads"].(map[string]interface{})
		if !ok {
			continue
		}
		artifact, ok := downloads["artifact"].(map[string]interface{})
		if !ok {
			continue
		}
		path, ok := artifact["path"].(string)
		if !ok {
			continue
		}
		libPath := filepath.Join(l.minelauncherPath, "libraries", filepath.FromSlash(path))
		if _, err := os.Stat(libPath); os.IsNotExist(err) {
			return fmt.Errorf("отсутствует библиотека: %s", libPath)
		}
	}

	// Проверка ассетов (опционально)
	// Здесь можно добавить дополнительные проверки, если необходимо

	fmt.Println("Все необходимые файлы присутствуют.")
	return nil
}

// Метод для генерации офлайн UUID
func (l *Launcher) generateOfflineUUID() string {
	uuid := make([]byte, 16)
	_, err := rand.Read(uuid)
	if err != nil {
		panic(err)
	}

	// Устанавливаем версию UUID на 4
	uuid[6] = (uuid[6] & 0x0F) | 0x40
	// Устанавливаем вариант UUID на RFC 4122
	uuid[8] = (uuid[8] & 0x3F) | 0x80

	// Форматируем UUID в строку
	return fmt.Sprintf("%x-%x-%x-%x-%x",
		uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:])
}

// Метод для запуска указанной версии Minecraft
func (l *Launcher) PlayMinecraftVersion(versionID string) error {
	// Получаем путь и создаем необходимые директории
	absPath, err := filepath.Abs(l.minelauncherPath)
	if err != nil {
		return fmt.Errorf("не удалось получить абсолютный путь для %s: %v", l.minelauncherPath, err)
	}
	l.minelauncherPath = absPath

	// Создаем директорию, если ее нет
	err = os.MkdirAll(l.minelauncherPath, os.ModePerm)
	if err != nil {
		return fmt.Errorf("не удалось создать директорию %s: %v", l.minelauncherPath, err)
	}

	// Получаем данные версии
	versionData, err := l.getVersionDetails(versionID)
	if err != nil {
		return err
	}

	// Сохраняем данные версии в файл version.json
	versionDir := filepath.Join(l.minelauncherPath, "versions", versionID)
	err = os.MkdirAll(versionDir, os.ModePerm)
	if err != nil {
		return fmt.Errorf("не удалось создать директорию %s: %v", versionDir, err)
	}
	versionJsonPath := filepath.Join(versionDir, versionID+".json")

	versionDataBytes, err := json.MarshalIndent(versionData, "", "  ")
	if err != nil {
		return fmt.Errorf("ошибка при сериализации данных версии: %v", err)
	}

	err = os.WriteFile(versionJsonPath, versionDataBytes, 0644)
	if err != nil {
		return fmt.Errorf("ошибка при сохранении файла version.json: %v", err)
	}

	// Скачиваем необходимые файлы
	err = l.downloadClientJar(versionData, versionID)
	if err != nil {
		return fmt.Errorf("ошибка при скачивании клиентского JAR-файла: %v", err)
	}

	err = l.downloadLibraries(versionData)
	if err != nil {
		return fmt.Errorf("ошибка при скачивании библиотек: %v", err)
	}

	err = l.downloadAssets(versionData)
	if err != nil {
		return fmt.Errorf("ошибка при скачивании ассетов: %v", err)
	}

	// Проверяем наличие всех необходимых файлов
	err = l.verifyAllFiles(versionData, versionID)
	if err != nil {
		return fmt.Errorf("ошибка валидации файлов: %v", err)
	}

	// Создаем необходимые директории и файлы
	dirs := []string{
		filepath.Join(l.minelauncherPath, "saves"),
		filepath.Join(l.minelauncherPath, "resourcepacks"),
		filepath.Join(l.minelauncherPath, "logs"),
		filepath.Join(l.minelauncherPath, "runtime"),
	}
	for _, dir := range dirs {
		err := os.MkdirAll(dir, os.ModePerm)
		if err != nil {
			return fmt.Errorf("не удалось создать директорию %s: %v", dir, err)
		}
	}

	optionsFilePath := filepath.Join(l.minelauncherPath, "options.txt")
	if _, err := os.Stat(optionsFilePath); os.IsNotExist(err) {
		err := os.WriteFile(optionsFilePath, []byte(""), 0644)
		if err != nil {
			return fmt.Errorf("не удалось создать файл %s: %v", optionsFilePath, err)
		}
	}

	// Проверяем наличие Java
	javaVersionCmd := exec.Command("java", "-version")
	javaVersionOutput, err := javaVersionCmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("ошибка при проверке версии Java: %v", err)
	}
	fmt.Println("Java версия:", string(javaVersionOutput))

	// Эмулируем офлайн-режим
	username := "Player"
	uuid := l.generateOfflineUUID()
	authToken := ""

	// Запускаем игру
	err = l.launchMinecraft(versionData, authToken, username, uuid, versionID)
	if err != nil {
		return fmt.Errorf("ошибка при запуске Minecraft: %v", err)
	}

	fmt.Println("Minecraft запущен успешно!")
	return nil
}


сделай поддержку сторонних версий например с fabric installer

типо как fabric-loader-0.15.5-1.20.1