package main

import (
	"encoding/json"
	"errors"
	"fmt"
	_ "image/png"
	"io"
	"os"
	"path/filepath"
	"time"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/driver/desktop"
	"fyne.io/fyne/v2/widget"

	simpledialog "github.com/sqweek/dialog"

	_ "embed"

	"github.com/emersion/go-autostart"
	"github.com/gofrs/flock"
	"github.com/gregorybednov/lbc/cli"
	"github.com/pelletier/go-toml/v2"
)

//go:embed icon.png
var iconPNG []byte

/* --------------------------- Конфиг лончера --------------------------- */

type LauncherCfg struct {
	FirstRun       bool   `json:"firstRun"`
	StartMinimized bool   `json:"startMinimized"`
	AutoStart      bool   `json:"autoStart"`
	NodeHome       string `json:"nodeHome"` // каталог узла (HOME)
	LastStarted    string `json:"lastStarted"`
}

type AppState struct {
	cfgPath string
	cfg     LauncherCfg
	lockF   *flock.Flock

	// UI
	app       fyne.App
	win       fyne.Window
	statusLbl *widget.Label
	logArea   *widget.Entry
}

/* --------------------------- TOML-модель (минимум) --------------------------- */

type ConfigTOML struct {
	Abci        string `toml:"abci"`
	DBBackend   string `toml:"db_backend"`
	DBDir       string `toml:"db_dir"`
	FilterPeers bool   `toml:"filter_peers"`
	GenesisFile string `toml:"genesis_file"`
	LogFormat   string `toml:"log_format"`
	LogLevel    string `toml:"log_level"`
	Moniker     string `toml:"moniker"`
	NodeKeyFile string `toml:"node_key_file"`
	P2P         struct {
		AddrBookFile     string `toml:"addr_book_file"`
		AddrBookStrict   bool   `toml:"addr_book_strict"`
		AllowDuplicateIP bool   `toml:"allow_duplicate_ip"`
		BootstrapPeers   string `toml:"bootstrap_peers"`
		ExternalAddress  string `toml:"external_address"`
		Laddr            string `toml:"laddr"`
		PersistentPeers  string `toml:"persistent_peers"`
		QueueType        string `toml:"queue_type"`
		UPnP             bool   `toml:"upnp"`
		UseLegacy        bool   `toml:"use_legacy"`
	} `toml:"p2p"`
	PrivValidator struct {
		KeyFile   string `toml:"key_file"`
		StateFile string `toml:"state_file"`
	} `toml:"priv_validator"`
	Ygg struct {
		AdminListen    string   `toml:"admin_listen"`
		AllowedPubKeys []string `toml:"allowed_public_keys"`
		Peers          string   `toml:"peers"`
		PrivateKeyFile string   `toml:"private_key_file"`
	} `toml:"yggdrasil"`
}

type uiWriter struct{ st *AppState }

func (w *uiWriter) Write(p []byte) (int, error) {
	w.st.appendLog(string(p))
	return len(p), nil
}

/* -------------------------------- main -------------------------------- */

func main() {
	st := &AppState{}

	err := initConfig(st)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	err = acquireSingleInstanceLock(st)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(2)
	}
	defer releaseSingleInstanceLock(st)

	exe, _ := os.Executable()
	exe, _ = filepath.EvalSymlinks(exe)
	autoApp := &autostart.App{
		Name:        "LBClient",
		DisplayName: "LBClient",
		Exec:        []string{exe},
	}

	a := app.New()
	w := a.NewWindow("LBС Launcher")

	st.app, st.win = a, w

	// Статус + логи
	st.statusLbl = widget.NewLabel("Готов")
	st.logArea = widget.NewMultiLineEntry()
	st.logArea.SetPlaceHolder("Здесь будет вывод команд lbc…")
	st.logArea.Disable() // только для чтения

	homeEntry := widget.NewEntry()
	homeEntry.SetPlaceHolder("Полный путь до каталога (там будут данные и настройки)")
	homeEntry.SetText(st.cfg.NodeHome)
	btnPickHome := widget.NewButton("Выбрать каталог…", func() {
		directory, err := simpledialog.Directory().Title("Выбрать каталог с настройками и данными").Browse()
		if err == nil {
			st.cfg.NodeHome = directory
			homeEntry.SetText(st.cfg.NodeHome)
			_ = saveConfig(st)
		}
	})

	topControls := container.NewVBox(
		widget.NewLabelWithStyle("HOME:", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
		homeEntry, btnPickHome,
	)

	// Вкладка: Init genesis
	initGenesisBtn := widget.NewButton("Инициализировать новую сеть (init genesis)", func() {
		if !st.ensurePathsOk(homeEntry.Text) {
			return
		}
		go func() {
			w := &uiWriter{st}
			if err := cli.ExecuteWithArgs([]string{"init", "genesis"}, homeEntry.Text, w, w); err != nil {
				simpledialog.Message("%s", err.Error()).Title("Произошла ошибка").Error()
			} else {
				st.notify("init genesis: готово")
			}
		}()
	})

	tabInitGenesis := container.NewBorder(nil, nil, nil, nil,
		container.NewVBox(
			widget.NewLabel("Создать новую блокчейн-сеть"),
			widget.NewLabel("Команда: lbc init genesis"),
			initGenesisBtn,
		),
	)

	// Вкладка: Export genesis.json
	exportBtn := widget.NewButton("Экспортировать genesis.json…", func() {
		// genesis лежит в HOME/config/genesis.json
		if homeEntry.Text == "" {
			simpledialog.Message("%s", "Сначала укажите домашний каталог узла").Title("Не найден HOME").Error()
			return
		}
		src := filepath.Join(homeEntry.Text, "config", "genesis.json")
		if _, err := os.Stat(src); err != nil {
			simpledialog.Message("%s", "Не найден файл"+src).Title("Файл не найден").Error()
			return
		}

		savePath, err := simpledialog.File().Filter("JSON files", "json").Title("Экспорт данных о сети").Save()
		if err != nil {
			return
		}

		// открыть исходный файл
		f, e := os.Open(src)
		if e != nil {
			simpledialog.Message("Ошибка чтения genesis: %s", e.Error()).Title("Ошибка").Error()
			return
		}
		defer f.Close()

		// создать файл для сохранения
		uw, e := os.Create(savePath)
		if e != nil {
			simpledialog.Message("Ошибка создания файла: %s", e.Error()).Title("Ошибка").Error()
			return
		}
		defer uw.Close()

		// копируем содержимое
		if _, e = io.Copy(uw, f); e != nil {
			simpledialog.Message("Ошибка записи: %s", e.Error()).Title("Ошибка").Error()
			return
		}

		st.notify(fmt.Sprintf("Готово: экспортирован genesis.json -> %s", savePath))
		//fd.SetFileName("genesis.json")
		//fd.Show()
	})
	tabExportGenesis := container.NewVBox(
		widget.NewLabel("Экспорт genesis.json из текущего узла"),
		exportBtn,
	)

	// Вкладка: Init joiner
	genesisPathEntry := widget.NewEntry()
	genesisPathEntry.SetPlaceHolder("Путь к существующему genesis.json")
	pickGenesis := widget.NewButton("Выбрать genesis.json…", func() {
		filename, err := simpledialog.File().Filter("Genesis JSON file", "json").Load()
		if err == nil {
			genesisPathEntry.SetText(filename)
		}

	})
	joinBtn := widget.NewButton("Создать узел-присоединение (init joiner)", func() {
		if !st.ensurePathsOk(homeEntry.Text) {
			return
		}
		// 1) скопировать genesis.json в HOME/config/genesis.json
		src := genesisPathEntry.Text
		if src == "" {
			simpledialog.Message("%s", "Укажите путь к genesis.json").Title("Не найден файл").Error()
			return
		}
		dest := filepath.Join(homeEntry.Text, "config")
		_ = os.MkdirAll(dest, 0o755)
		destFile := filepath.Join(dest, "genesis.json")
		if err := fileCopy(src, destFile); err != nil {
			simpledialog.Message("%s", "Не удалось скопировать genesis.json: "+err.Error()).Title("Ошибка генерации").Error()
			return
		}
		// 2) lbc init joiner
		go func() {
			w := &uiWriter{st}
			if err := cli.ExecuteWithArgs([]string{"init", "joiner", genesisPathEntry.Text}, homeEntry.Text, w, w); err != nil {
				simpledialog.Message("%s", err.Error()).Title("Произошла ошибка").Error()
			} else {
				st.notify("init joiner: готово")
			}
		}()
	})
	tabJoiner := container.NewVBox(
		widget.NewLabel("Создать ноду в существующей сети"),
		container.NewHBox(genesisPathEntry, pickGenesis),
		joinBtn,
	)

	// Вкладка: Редактор config.toml (минималка)
	// Читаем/пишем HOME/config/config.toml
	cfgPathLbl := widget.NewLabel("Файл: (HOME)/config/config.toml")
	monikerEntry := widget.NewEntry()
	laddrEntry := widget.NewEntry()
	ppeersEntry := widget.NewEntry()
	logLevelSel := widget.NewSelect([]string{"debug", "info", "warn", "error"}, nil)

	loadCfgBtn := widget.NewButton("Загрузить config.toml", func() {
		if homeEntry.Text == "" {
			simpledialog.Message("%s", "Укажите домашнюю директорию узла").Title("Ошибка сохранения").Error()
			return
		}
		cfgp := filepath.Join(homeEntry.Text, "config", "config.toml")
		if cfg, err := loadTOML(cfgp); err != nil {
			simpledialog.Message("%s", err.Error()).Title("Ошибка чтения TOML").Error()
		} else {
			cfgPathLbl.SetText("Файл: " + cfgp)
			monikerEntry.SetText(cfg.Moniker)
			laddrEntry.SetText(cfg.P2P.Laddr)
			ppeersEntry.SetText(cfg.P2P.PersistentPeers)
			if cfg.LogLevel == "" {
				cfg.LogLevel = "info"
			}
			logLevelSel.SetSelected(cfg.LogLevel)
			st.status("config.toml загружен")
		}
	})
	saveCfgBtn := widget.NewButton("Сохранить", func() {
		if homeEntry.Text == "" {
			simpledialog.Message("%s", "Укажите домашнюю директорию узла").Title("Ошибка сохранения").Error()
			return
		}
		cfgp := filepath.Join(homeEntry.Text, "config", "config.toml")
		cfg, err := loadTOML(cfgp)
		if err != nil {
			simpledialog.Message("%s", err.Error()).Title("Ошибка чтения TOML").Error()
			return
		}
		cfg.Moniker = monikerEntry.Text
		cfg.P2P.Laddr = laddrEntry.Text
		cfg.P2P.PersistentPeers = ppeersEntry.Text
		cfg.LogLevel = logLevelSel.Selected
		if err := saveTOML(cfgp, cfg); err != nil {
			simpledialog.Message("%s", err.Error()).Title("Ошибка записи TOML").Error()
			return
		}
		st.notify("config.toml сохранён")
	})
	tabCfg := container.NewVBox(
		widget.NewLabel("Редактирование основных параметров config.toml"),
		cfgPathLbl,
		widget.NewForm(
			widget.NewFormItem("moniker", monikerEntry),
			widget.NewFormItem("p2p.laddr", laddrEntry),
			widget.NewFormItem("p2p.persistent_peers", ppeersEntry),
			widget.NewFormItem("log_level", logLevelSel),
		),
		container.NewHBox(loadCfgBtn, saveCfgBtn),
	)

	// Вкладка: Логи
	clearLog := widget.NewButton("Очистить лог", func() { st.logArea.SetText("") })
	tabLogs := container.NewBorder(nil, clearLog, nil, nil, st.logArea)

	// Собираем UI
	tabs := container.NewAppTabs(
		container.NewTabItem("Init genesis", tabInitGenesis),
		container.NewTabItem("Export genesis", tabExportGenesis),
		container.NewTabItem("Init joiner", tabJoiner),
		container.NewTabItem("Config.toml", tabCfg),
		container.NewTabItem("Logs", tabLogs),
	)
	tabs.SetTabLocation(container.TabLocationTop)

	cbAutostart := widget.NewCheck("Запускать при входе в систему", func(v bool) {
		if v {
			if err := autoApp.Enable(); err != nil {
				simpledialog.Message("%s", err.Error()).Title("Произошла ошибка автозапуска").Error()
				return
			}
		} else {
			_ = autoApp.Disable()
		}
		st.cfg.AutoStart = v
		_ = saveConfig(st)
	})
	cbAutostart.SetChecked(autoApp.IsEnabled())

	cbStartMin := widget.NewCheck("Стартовать свернутым в трей", func(v bool) {
		st.cfg.StartMinimized = v
		_ = saveConfig(st)
	})
	cbStartMin.SetChecked(true)

	top := container.NewVBox(topControls, container.NewHBox(cbAutostart, cbStartMin), st.statusLbl)
	content := container.NewBorder(top, nil, nil, nil, tabs)
	w.SetContent(content)

	// Закрытие = скрытие (фон)
	w.SetCloseIntercept(func() { w.Hide(); st.notify("Лончер работает в фоне") })

	// Трей-меню
	if desk, ok := a.(desktop.App); ok {
		desk.SetSystemTrayIcon(fyne.NewStaticResource("icon.png", iconPNG))
		openItem := fyne.NewMenuItem("Открыть", func() { w.Show(); w.RequestFocus() })
		autoItem := fyne.NewMenuItem("Автозапуск", nil)
		autoItem.Checked = autoApp.IsEnabled()
		autoItem.Action = func() {
			ns := !autoItem.Checked
			if ns {
				if err := autoApp.Enable(); err != nil {
					simpledialog.Message("%s", err.Error()).Title("Произошла ошибка").Error()
					return
				}
			} else {
				_ = autoApp.Disable()
			}
			autoItem.Checked = ns
			st.cfg.AutoStart = ns
			_ = saveConfig(st)
		}
		quitItem := fyne.NewMenuItem("Выход", func() { releaseSingleInstanceLock(st); a.Quit() })
		desk.SetSystemTrayMenu(fyne.NewMenu("", openItem, autoItem, fyne.NewMenuItemSeparator(), quitItem))
	}

	// Первый запуск / показ окна
	now := time.Now().Format(time.RFC3339)
	if st.cfg.FirstRun {
		st.notify("Первый запуск: лончер будет работать в фоне")
		st.cfg.FirstRun = false
	}
	st.cfg.LastStarted = now
	_ = saveConfig(st)

	if st.cfg.StartMinimized {
		w.Hide()
	} else {
		w.Show()
	}
	a.Run()
}

/* ------------------------------ Конфиг/локи ------------------------------ */

func initConfig(st *AppState) error {
	dir, err := os.UserConfigDir()
	if err != nil {
		return err
	}
	appDir := filepath.Join(dir, "lbc_launcher")
	if err := os.MkdirAll(appDir, 0o755); err != nil {
		return err
	}
	st.cfgPath = filepath.Join(appDir, "config.json")
	// defaults
	st.cfg = LauncherCfg{
		FirstRun:       true,
		StartMinimized: true,
		AutoStart:      false,
		NodeHome:       "",
	}
	f, err := os.Open(st.cfgPath)
	if errors.Is(err, os.ErrNotExist) {
		return saveConfig(st)
	}
	if err != nil {
		return err
	}
	defer f.Close()
	data, _ := io.ReadAll(f)
	_ = json.Unmarshal(data, &st.cfg)
	return nil
}

func saveConfig(st *AppState) error {
	tmp := st.cfgPath + ".tmp"
	b, _ := json.MarshalIndent(st.cfg, "", "  ")
	if err := os.WriteFile(tmp, b, 0o644); err != nil {
		return err
	}
	return os.Rename(tmp, st.cfgPath)
}

func acquireSingleInstanceLock(st *AppState) error {
	cache, err := os.UserCacheDir()
	if err != nil {
		return err
	}
	lp := filepath.Join(cache, "lbc_launcher", "app.lock")
	if err := os.MkdirAll(filepath.Dir(lp), 0o755); err != nil {
		return err
	}
	l := flock.New(lp)
	ok, err := l.TryLock()
	if err != nil {
		return fmt.Errorf("lock error: %w", err)
	}
	if !ok {
		return errors.New("another instance is running")
	}
	st.lockF = l
	return nil
}
func releaseSingleInstanceLock(st *AppState) {
	if st.lockF != nil {
		_ = st.lockF.Unlock()
	}
}

/* ------------------------------ TOML I/O ------------------------------ */

func loadTOML(path string) (*ConfigTOML, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	var cfg ConfigTOML
	dec := toml.NewDecoder(f)
	if err := dec.Decode(&cfg); err != nil {
		return nil, err
	}
	return &cfg, nil
}
func saveTOML(path string, cfg *ConfigTOML) error {
	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
		return err
	}
	tmp := path + ".tmp"
	f, err := os.Create(tmp)
	if err != nil {
		return err
	}
	enc := toml.NewEncoder(f)
	if err := enc.Encode(cfg); err != nil {
		_ = f.Close()
		return err
	}
	_ = f.Close()
	return os.Rename(tmp, path)
}

/* ------------------------------ Утилиты/UI ------------------------------ */

func (st *AppState) status(s string) {
	st.statusLbl.SetText(s)
}
func (st *AppState) appendLog(s string) {
	go func() {
		st.win.SetContent(st.win.Content()) // гарантируем вызов из UI потока
		st.logArea.SetText(st.logArea.Text + s)
		st.logArea.CursorRow = 999999
	}()
}

func (st *AppState) notify(msg string) {
	st.app.SendNotification(&fyne.Notification{Title: "LBClient", Content: msg})
}

func (st *AppState) ensurePathsOk(home string) bool {
	if home == "" {
		simpledialog.Message("%s", "Каталог узла не найден").Title("Не найден HOME").Error()
		return false
	}
	st.cfg.NodeHome = home
	_ = saveConfig(st)
	return true
}

func fileCopy(src, dst string) error {
	in, err := os.Open(src)
	if err != nil {
		return err
	}
	defer in.Close()
	if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
		return err
	}
	out, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer out.Close()
	if _, err = io.Copy(out, in); err != nil {
		return err
	}
	return out.Close()
}
