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"
)

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)
	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
}

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)
}

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
				}
			}

			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
}

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 {
		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
}

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
}

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 клиента")
	}

	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
				}
			}

			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
		}

		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
}

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)
	}

	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"
}

func (l *Launcher) versionDataGetAssetIndex(versionData VersionData) map[string]interface{} {
	if assetIndex, ok := versionData["assetIndex"].(map[string]interface{}); ok {
		return assetIndex
	}
	return map[string]interface{}{}
}

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
}

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:
					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 {
								args = l.appendArgumentValueWithoutQuickPlay(args, value, username, versionID, versionData, uuid, authToken)
							}
						}
					} else {
						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 {
			if strings.Contains(strings.ToLower(arg), "quickplay") {
				continue
			}
			arg = l.replaceVariables(arg, username, versionID, versionData, uuid, authToken)
			args = append(args, arg)
		}
	}

	return args
}

func (l *Launcher) appendArgumentValueWithoutQuickPlay(args []string, value interface{}, username, versionID string, versionData VersionData, uuid, authToken string) []string {
	switch v := value.(type) {
	case string:
		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 {
				if strings.Contains(strings.ToLower(sv), "quickplay") {
					continue
				}
				sv = l.replaceVariables(sv, username, versionID, versionData, uuid, authToken)
				args = append(args, sv)
			}
		}
	}
	return args
}

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 пустой")
	}

	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)
	}

	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("Проверка наличия всех необходимых файлов...")
	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
}

func (l *Launcher) generateOfflineUUID() string {
	uuid := make([]byte, 16)
	_, err := rand.Read(uuid)
	if err != nil {
		panic(err)
	}

	uuid[6] = (uuid[6] & 0x0F) | 0x40
	uuid[8] = (uuid[8] & 0x3F) | 0x80

	return fmt.Sprintf("%x-%x-%x-%x-%x",
		uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:])
}

func (l *Launcher) PlayMinecraftVersion(versionID string, username 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
	}

	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)
		}
	}

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

	uname := username
	uuid := l.generateOfflineUUID()
	authToken := ""

	err = l.launchMinecraft(versionData, authToken, uname, 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 в папке versions и там есть json 

{"inheritsFrom":"1.20.1","releaseTime":"2024-10-09T12:46:04+0000","mainClass":"net.fabricmc.loader.impl.launch.knot.KnotClient","libraries":[{"sha1":"aa205cf0a06dbd8e04ece91c0b37c3f5d567546a","sha256":"3c6fac2424db3d4a853b669f4e3d1d9c3c552235e19a319673f887083c2303a1","size":123598,"name":"org.ow2.asm:asm:9.6","sha512":"01a5ea6f5b43bf094c52a50e18325a60af7bb02e74d24f9bc2c727d43e514578fd968b30ff22f9d2720caec071458f9ff82d11a21fbb1ebc42d8203e737c4b52","url":"https://maven.fabricmc.net/","md5":"6f8bccf756f170d4185bb24c8c2d2020"},{"sha1":"9ce6c7b174bd997fc2552dff47964546bd7a5ec3","sha256":"d92832d7c37edc07c60e2559ac6118b31d642e337a6671edcb7ba9fae68edbbb","size":34041,"name":"org.ow2.asm:asm-analysis:9.6","sha512":"e798d451addabbaeace3adf8d8e939430241d9665c81b98b0a7a705c9b91ddbb67ff86b1f7b9a7ef317f225d38da3319ed40e0fd33ddace3d2ac75c3f7b78b02","url":"https://maven.fabricmc.net/","md5":"31c84ef7cc893fb278952ae2d6a2674f"},{"sha1":"f1a9e5508eff490744144565c47326c8648be309","sha256":"7aefd0d5c0901701c69f7513feda765fb6be33af2ce7aa17c5781fc87657c511","size":72194,"name":"org.ow2.asm:asm-commons:9.6","sha512":"8739143062b0589e3c55d9a8fc67b716cc9b8ce8ecb6f4c1f968b587b3117c6806dae8dba92c79a52eeef05b10b25b01bb26f4e77fd20d2f52ddf2f56438901f","url":"https://maven.fabricmc.net/","md5":"9e317c75534bd1da8c00a67c618ab288"},{"sha1":"c0cdda9d211e965d2a4448aa3fd86110f2f8c2de","sha256":"c43ecf17b539c777e15da7b5b86553b377e2d39a683de6285567d5283888e7ef","size":51935,"name":"org.ow2.asm:asm-tree:9.6","sha512":"ae0416ae62b9260da5012a3c5b6e12f34f4a0d016ec9cc9a2a1e771af12a9d63e36315c199f85d7a9d424ae39500253def0a0a581befa26d4372b21efec44781","url":"https://maven.fabricmc.net/","md5":"6062608f1a98afe1e853d01fa1221a9e"},{"sha1":"f77caf84eb93786a749b2baa40865b9613e3eaee","sha256":"c635a7402f4aa9bf66b2f4230cea62025a0fe1cd63e8729adefc9b1994fac4c3","size":91131,"name":"org.ow2.asm:asm-util:9.6","sha512":"1b92e1fae7d0c082bccf751bdaeae3bf32d28b9e555c9989ad70d3e5156d19e9547232c5886a53af1973da51bf9f61266336f3bcc55ea2cf972b76370a0bd8ba","url":"https://maven.fabricmc.net/","md5":"bd3bc1c176a787373e9a031073c9574b"},{"sha1":"8d31fb97c3e0cd7c8dad3441851c523bcfae6d8e","sha256":"43d46bce43e1df1210970ce2e8610c4f617bb4173093eae975f7abd4de1bf0fa","size":1451874,"name":"net.fabricmc:sponge-mixin:0.12.5+mixin.0.8.5","sha512":"bc177a4c5cfb5605e627bfbd9101ec69df260fc54f0e8792a962e953df9c29a84a3deb8a243dfec91dba3845224b950fac5673e1d98d9ad78badb2394063eafd","url":"https://maven.fabricmc.net/","md5":"4cc3ff559cafdc70d9ed80e869d447f0"},{"name":"net.fabricmc:intermediary:1.20.1","url":"https://maven.fabricmc.net/"},{"name":"net.fabricmc:fabric-loader:0.15.5","url":"https://maven.fabricmc.net/"}],"arguments":{"jvm":["-DFabricMcEmu= net.minecraft.client.main.Main "],"game":[]},"id":"fabric-loader-0.15.5-1.20.1","time":"2024-10-09T12:46:04+0000","type":"release"}

то есть сделай так чтобы в манивест загружались еще версии из папки versions и в этом json файле вроде есть откуда скаивать версию fabric напиши код полностью package launcher