package main

import (
	"bufio"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"

	"gopkg.in/ini.v1"
	"gopkg.in/yaml.v2"
)

// Variables описывает работу с .ini файлами
type Variables struct {
	iniFileName string
	config      *ini.File
}

// NewVariables конструктор для работы с .ini файлами
func NewVariables(iniFileName string) *Variables {
	config, err := ini.LooseLoad(iniFileName)
	if err != nil {
		log.Fatalf("Ошибка загрузки ini-файла: %v", err)
	}

	return &Variables{
		iniFileName: iniFileName,
		config:      config,
	}
}

// GetVar читает или запрашивает переменную из .ini
func (v *Variables) GetVar(sectionName, varName string, interactive bool) string {
	section := v.config.Section(sectionName)
	value := section.Key(varName).String()

	if interactive || value == "" {
		reader := bufio.NewReader(os.Stdin)
		fmt.Printf("Введите %s (%s): ", varName, value)
		newValue, _ := reader.ReadString('\n')
		newValue = strings.TrimSpace(newValue)

		if newValue != "" {
			section.Key(varName).SetValue(newValue)
			v.config.SaveTo(v.iniFileName)
			return newValue
		}
	}
	if !interactive && value == "" {
		fmt.Printf("Переменная %s не найдена", varName)
		os.Exit(1)
	}

	return value
}

// MenuItem описывает структуру меню из YAML
type MenuItem struct {
	Title    string           `yaml:"title"`
	File     string           `yaml:"file,omitempty"`
	Items    map[int]MenuItem `yaml:"items,omitempty"`
	Commands []string         `yaml:"commands,omitempty"`
	Catch    []string         `yaml:"catch,omitempty"`
	Vars     []string         `yaml:"vars,omitempty"`
	Env      string           `yaml:"env,omitempty"`
	Hint     string           `yaml:"hint,omitempty"` // Добавляем hint в MenuItem
}

// Menu управляет отображением меню
type Menu struct {
	hint      string
	items     map[int]MenuItem
	variables *Variables
	baseDir   string
}

// NewMenu инициализирует меню из YAML
func NewMenu(yamlFile string) *Menu {
	currDir, _ := os.Getwd()
	baseDir := currDir
	yamlPath := resolvePath(baseDir, yamlFile)

	// Читаем YAML файл
	file, err := os.Open(yamlPath)
	if err != nil {
		log.Fatalf("Ошибка чтения YAML файла %s: %v", yamlPath, err)
	}
	defer file.Close()

	decoder := yaml.NewDecoder(file)
	var menuData struct {
		Hint  string           `yaml:"hint,omitempty"` // Добавляем поле hint
		Items map[int]MenuItem `yaml:"items"`
	}

	if err := decoder.Decode(&menuData); err != nil {
		log.Fatalf("Ошибка парсинга YAML: %v", err)
	}

	return &Menu{
		hint:      menuData.Hint,
		items:     menuData.Items,
		variables: NewVariables("env.ini"),
		baseDir:   baseDir,
	}
}

// resolvePath корректно формирует путь к файлу
func resolvePath(baseDir, yamlFile string) string {
	// Если путь абсолютный, используем его как есть
	if filepath.IsAbs(yamlFile) {
		return yamlFile
	}

	// Если путь уже содержит сегмент "menu", оставляем всё как есть
	if strings.HasPrefix(baseDir, "menu"+string(os.PathSeparator)) {
		return filepath.Join(baseDir, yamlFile)
	}

	// Обычно добавляем сегмент "menu" перед файлом
	return filepath.Join(baseDir, string(os.PathSeparator)+"menu", yamlFile)
}

// ShowItems отображает главное меню
func (m *Menu) ShowItems() {
	// Выводим подсказку, если она есть
	if m.hint != "" {
		fmt.Println(m.hint)
		// fmt.Println() // Пустая строка после подсказки
	} else {
		fmt.Println()
	}

	fmt.Println("0. Выход")

	// Извлекаем ключи из мапы
	keys := make([]int, 0, len(m.items))
	for key := range m.items {
		keys = append(keys, key)
	}

	// Сортируем ключи
	sort.Ints(keys)

	for _, key := range keys {
		fmt.Printf("%s\n", m.items[key].Title)
	}
}

// Loop запускает интерактивный цикл меню
func (m *Menu) Loop() {
	reader := bufio.NewReader(os.Stdin)

	for {
		m.ShowItems()
		fmt.Print("\n")
		fmt.Print("Выберите действие: ")
		input, _ := reader.ReadString('\n')
		input = strings.TrimSpace(input)

		if input == "0" {
			break
		}

		if itemNum, err := strconv.Atoi(input); err == nil {
			m.ChooseItem(itemNum)
		} else {
			fmt.Println("Неверный ввод")
		}
	}
}

// ChooseItem обрабатывает выбор элемента меню
func (m *Menu) ChooseItem(itemNum int) {
	item, found := m.items[itemNum]
	if !found {
		fmt.Println("Элемент не найден")
		return
	}

	if len(item.File) > 0 {
		// Подгружаем дочернее меню
		f := strings.TrimPrefix(item.File, string(os.PathSeparator))
		filePath := resolvePath(m.baseDir, f)
		subMenu := NewMenu(filePath)
		subMenu.Loop()
	} else if len(item.Items) > 0 {
		// Создаем подменю из вложенных items
		subMenu := &Menu{
			hint:      item.Hint, // Используем hint из MenuItem
			items:     item.Items,
			variables: m.variables,
			baseDir:   m.baseDir,
		}
		subMenu.Loop()
	} else if len(item.Commands) > 0 {
		vars := m.getVars(item, true)
		if !m.runCommands(item, vars) {
			m.catch(item, vars)
		}
	}
}

func (m *Menu) ExecItem(itemNum int) *Menu {
	item, found := m.items[itemNum]
	if !found {
		fmt.Println("Элемент не найден")
		return nil
	}

	if len(item.File) > 0 {
		// Подгружаем дочернее меню
		f := strings.TrimPrefix(item.File, string(os.PathSeparator))
		filePath := resolvePath(m.baseDir, f)
		subMenu := NewMenu(filePath)
		return subMenu
	} else if len(item.Items) > 0 {
		subMenu := &Menu{items: item.Items, variables: m.variables, baseDir: m.baseDir}
		return subMenu
	} else if len(item.Commands) > 0 {
		vars := m.getVars(item, false)
		if !m.runCommands(item, vars) {
			m.catch(item, vars)
		}
	}
	return nil
}

// getVars получает переменные из .ini
func (m *Menu) getVars(item MenuItem, interactive bool) map[string]string {
	vars := make(map[string]string)
	vars["currentDir"], _ = os.Getwd()

	for _, varName := range item.Vars {
		vars[varName] = m.variables.GetVar(item.Env, varName, interactive)
	}

	return vars
}

// runCommands выполняет команды
func (m *Menu) runCommands(item MenuItem, vars map[string]string) bool {
	for _, cmd := range item.Commands {
		// Здесь можно вызвать реальное выполнение, используя os/exec
		re := regexp.MustCompile(`%\(([a-zA-Z0-9\-]*)\)s`)
		command := re.ReplaceAllStringFunc(cmd, func(match string) string {
			// Извлекаем ключ внутри %( и )s
			key := re.FindStringSubmatch(match)[1]

			// Проверяем, есть ли значение для ключа в map
			if val, exists := vars[key]; exists {
				return val // Заменяем найденной строкой
			}
			// Если ключа нет в map, оставляем как есть
			return match
		})
		fmt.Printf("Выполнение: %s\n", command)

		// Разделяем на части
		parts := strings.Split(command, " ")

		// Проверяем, является ли первая часть установкой переменной окружения
		envs := []string{} // Хранилище для переменных окружения
		for len(parts) > 0 && strings.Contains(parts[0], "=") {
			envs = append(envs, parts[0]) // Добавляем переменную в массив env
			parts = parts[1:]             // Убираем первую часть
		}

		cmd := exec.Command("sh", "-c", command)

		// Добавляем переменные окружения к текущим
		cmd.Env = append(os.Environ(), envs...)
		// Передаём текущее окружение
		// cmd.Env = os.Environ()
		cmd.Stdin = os.Stdin   // Ввод
		cmd.Stdout = os.Stdout // Вывод
		cmd.Stderr = os.Stderr // Вывод ошибок

		// Запускаем команду
		if err := cmd.Run(); err != nil {
			os.Stderr.WriteString("Ошибка выполнения: " + err.Error() + "\n")
		}
	}
	return true
}

// catch выполняет обработку ошибок команд
func (m *Menu) catch(item MenuItem, vars map[string]string) {
	for _, cmd := range item.Catch {
		fmt.Printf("Обработка ошибки: %s\n", cmd)
		// Здесь можно вызвать реальное выполнение, используя os/exec
		re := regexp.MustCompile(`%\(([a-zA-Z0-9]*)\)s`)
		command := re.ReplaceAllStringFunc(cmd, func(match string) string {
			// Извлекаем ключ внутри %( и )s
			key := re.FindStringSubmatch(match)[1]

			// Проверяем, есть ли значение для ключа в map
			if val, exists := vars[key]; exists {
				return val // Заменяем найденной строкой
			}
			// Если ключа нет в map, оставляем как есть
			return match
		})
		fmt.Printf("Выполнение: %s\n", command)

		// Разделяем на части
		parts := strings.Split(command, " ")

		// Проверяем, является ли первая часть установкой переменной окружения
		envs := []string{} // Хранилище для переменных окружения
		for len(parts) > 0 && strings.Contains(parts[0], "=") {
			envs = append(envs, parts[0]) // Добавляем переменную в массив env
			parts = parts[1:]             // Убираем первую часть
		}

		// Теперь `parts` содержит только саму команду и её аргументы
		cmd := exec.Command(parts[0], parts[1:]...)

		// Добавляем переменные окружения к текущим
		cmd.Env = append(os.Environ(), envs...)
		// Передаём текущее окружение
		// cmd.Env = os.Environ()
		cmd.Stdin = os.Stdin   // Ввод
		cmd.Stdout = os.Stdout // Вывод
		cmd.Stderr = os.Stderr // Вывод ошибок

		// Запускаем команду
		if err := cmd.Run(); err != nil {
			os.Stderr.WriteString("Ошибка выполнения: " + err.Error() + "\n")
		}
	}
}

func main() {
	// Инициализация флагов
	configFile := flag.String("c", "menu.yml", "Путь к файлу конфигурации меню")
	execFlag := flag.String("exec", "", "Execution parameter")
	flag.Parse() // Парсим аргументы

	// Инициализация главного меню с использованием флага -c
	// По умолчанию "menu.yml", что с учетом resolvePath превратится в "menu/menu.yml"
	menu := NewMenu(*configFile)

	if *execFlag != "" { // Проверяем, задан ли флаг --exec
		items := strings.Split(*execFlag, "-") // Разделяем строку по "-"
		for _, item := range items {           // Итерируемся по элементам
			num, _ := strconv.Atoi(item)
			menu = menu.ExecItem(num)
		}
		os.Exit(0) // Завершаем выполнение программы
	}

	menu.Loop()
}
