// Package main — bj-installer.
//
// Web-инсталлятор для bj-server: на машине клиента после установки
// Debian/Astra поднимает локальный HTTP на 127.0.0.1:8181, проводит
// через 5-страничный wizard (welcome → precheck → config → install → done)
// и за кадром выполняет 20+ шагов установки Валидаты + bj-server + ИШ.
//
// Прогресс шагов прилетает в UI через Server-Sent Events. Каждый шаг
// идемпотентен — можно повторно запускать инсталлятор на уже настроенной
// машине, он пропустит то, что сделано.
//
// Запуск: sudo ./bj-installer [--addr 127.0.0.1:8181] [--no-browser]
// Артефакты ожидаются рядом с бинарём в каталоге ./artifacts/:
//
//	artifacts/ClientL_Other/zpki-*.deb
//	artifacts/ClientL_Other/zsdk-*.deb
//	artifacts/bj-server           (Go-бинарь)
//	artifacts/crypto-service.jar  (Java-сайдкар)
//	artifacts/ish/igate_*.deb     (ИШ НРД)
package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/exec"
	"os/signal"
	"runtime"
	"syscall"
	"time"
)

const banner = `
======================================================================
  bj-installer — мастер установки Bridge-and-Join-s
======================================================================
`

func main() {
	addr := flag.String("addr", "127.0.0.1:8181", "адрес web-инсталлятора")
	noBrowser := flag.Bool("no-browser", false, "не пытаться открыть браузер автоматически")
	artifactsDir := flag.String("artifacts", "./artifacts", "каталог с дистрибутивами (Validata deb, bj-server, ish)")
	flag.Parse()

	if os.Geteuid() != 0 {
		fmt.Fprintln(os.Stderr, "Установщик должен быть запущен от root (sudo).")
		os.Exit(1)
	}

	fmt.Print(banner)
	fmt.Printf("  адрес:      http://%s\n", *addr)
	fmt.Printf("  артефакты:  %s\n", *artifactsDir)
	fmt.Println("======================================================================")

	st := newState(*artifactsDir)

	srv := newServer(st)
	httpSrv := &http.Server{
		Addr:              *addr,
		Handler:           srv,
		ReadHeaderTimeout: 10 * time.Second,
	}

	// SIGINT/SIGTERM → корректный shutdown
	ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
	defer stop()

	go func() {
		if err := httpSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("HTTP-сервер упал: %v", err)
		}
	}()

	url := "http://" + *addr
	log.Printf("Откройте в браузере: %s", url)
	if !*noBrowser {
		tryOpenBrowser(url)
	}

	<-ctx.Done()
	log.Println("Завершаем работу...")
	shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	_ = httpSrv.Shutdown(shutdownCtx)
}

// tryOpenBrowser — без фанатизма. Если xdg-open/sensible-browser есть и
// $DISPLAY поднят (xrdp, Fly DE) — откроем. Иначе пользователь увидит URL
// в выводе и перейдёт сам с другого компа (типичный сценарий headless).
func tryOpenBrowser(url string) {
	if os.Getenv("DISPLAY") == "" && os.Getenv("WAYLAND_DISPLAY") == "" {
		return
	}
	var bin string
	switch runtime.GOOS {
	case "linux":
		for _, cand := range []string{"xdg-open", "sensible-browser", "x-www-browser"} {
			if p, err := exec.LookPath(cand); err == nil {
				bin = p
				break
			}
		}
	}
	if bin == "" {
		return
	}
	_ = exec.Command(bin, url).Start()
}
