package lkgateway

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"os"
	"os/user"
	"path/filepath"
	"strings"
	"time"
)

// FlashContainer — найденный на смонтированной флешке контейнер КриптоПро.
// КриптоПро CSP под Linux ожидает контейнер в виде папки <name>.000 с
// файлами header.key/masks.key/name.key/primary.key/primary2.key.
type FlashContainer struct {
	// Mountpoint — путь смонтированной флешки, например /run/media/user/USB.
	Mountpoint string
	// Path — полный путь до папки <name>.000.
	Path string
	// Name — имя контейнера (без суффикса .000).
	Name string
	// Files — список файлов в контейнере (для дисплея).
	Files []string
	// AlreadyImported — true, если папка <name>.000 уже есть в локальном
	// хранилище /var/opt/cprocsp/keys/<user>/.
	AlreadyImported bool
}

// scanFlashContainers ищет контейнеры формата <name>.000 на типичных
// точках монтирования USB-носителей в Linux: /run/media/<user>/* и
// /media/<user>/* и /media/*. Возвращает список найденных контейнеров.
func scanFlashContainers() []FlashContainer {
	u, err := user.Current()
	if err != nil {
		return nil
	}
	roots := []string{
		filepath.Join("/run/media", u.Username),
		filepath.Join("/media", u.Username),
		"/media",
		"/mnt",
	}
	localKeysDir := filepath.Join("/var/opt/cprocsp/keys", u.Username)

	var out []FlashContainer
	for _, root := range roots {
		entries, err := os.ReadDir(root)
		if err != nil {
			continue
		}
		for _, e := range entries {
			if !e.IsDir() {
				continue
			}
			mountpoint := filepath.Join(root, e.Name())
			out = append(out, findContainersAt(mountpoint, localKeysDir)...)
		}
	}
	return out
}

func findContainersAt(mountpoint, localKeysDir string) []FlashContainer {
	var out []FlashContainer
	// Ищем папки <name>.000 на верхнем уровне и на 1 уровне вглубь.
	_ = filepath.Walk(mountpoint, func(p string, info os.FileInfo, err error) error {
		if err != nil {
			return nil
		}
		// Глубже 2 уровней не лезем (на флешке могут быть личные папки).
		rel, _ := filepath.Rel(mountpoint, p)
		if strings.Count(rel, string(filepath.Separator)) > 2 {
			return filepath.SkipDir
		}
		if !info.IsDir() || !strings.HasSuffix(strings.ToLower(p), ".000") {
			return nil
		}
		// Проверяем, что внутри лежат файлы вида *.key.
		entries, _ := os.ReadDir(p)
		var files []string
		hasKey := false
		for _, ent := range entries {
			files = append(files, ent.Name())
			if strings.HasSuffix(strings.ToLower(ent.Name()), ".key") {
				hasKey = true
			}
		}
		if !hasKey {
			return nil
		}
		name := strings.TrimSuffix(filepath.Base(p), ".000")
		fc := FlashContainer{
			Mountpoint: mountpoint,
			Path:       p,
			Name:       name,
			Files:      files,
		}
		// Проверка: уже скопирован в локальное хранилище?
		if _, err := os.Stat(filepath.Join(localKeysDir, name+".000")); err == nil {
			fc.AlreadyImported = true
		}
		out = append(out, fc)
		return filepath.SkipDir
	})
	return out
}

// copyContainerToLocal копирует папку <name>.000 с флешки в локальное
// хранилище КриптоПро /var/opt/cprocsp/keys/<user>/<name>.000. После
// этого контейнер виден как \\.\HDIMAGE\<name> и работает даже без
// вставленной флешки.
func copyContainerToLocal(srcDir string) (string, error) {
	u, err := user.Current()
	if err != nil {
		return "", err
	}
	localKeysDir := filepath.Join("/var/opt/cprocsp/keys", u.Username)
	if err := os.MkdirAll(localKeysDir, 0o700); err != nil {
		return "", fmt.Errorf("создать %s: %w", localKeysDir, err)
	}
	base := filepath.Base(srcDir)
	dstDir := filepath.Join(localKeysDir, base)
	if _, err := os.Stat(dstDir); err == nil {
		return "", fmt.Errorf("контейнер %s уже существует в локальном хранилище", dstDir)
	}
	if err := os.MkdirAll(dstDir, 0o700); err != nil {
		return "", fmt.Errorf("создать %s: %w", dstDir, err)
	}
	entries, err := os.ReadDir(srcDir)
	if err != nil {
		return "", err
	}
	for _, e := range entries {
		if e.IsDir() {
			continue
		}
		src, err := os.Open(filepath.Join(srcDir, e.Name()))
		if err != nil {
			return "", err
		}
		dst, err := os.OpenFile(filepath.Join(dstDir, e.Name()),
			os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
		if err != nil {
			src.Close()
			return "", err
		}
		if _, err := io.Copy(dst, src); err != nil {
			src.Close()
			dst.Close()
			return "", err
		}
		src.Close()
		dst.Close()
	}
	return dstDir, nil
}

// copyContainer — POST /admin/setup/crypto/copy-container.
// Параметр src — путь до папки <name>.000 на флешке.
func (h *setupHandlers) copyContainer(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "method", http.StatusMethodNotAllowed)
		return
	}
	src := strings.TrimSpace(r.FormValue("src"))
	if src == "" {
		setupFlash(w, r, "Копирование контейнера: не указан путь")
		return
	}
	// Минимальная защита: ожидаем .000 в конце пути.
	if !strings.HasSuffix(strings.ToLower(src), ".000") {
		setupFlash(w, r, "Копирование контейнера: путь должен заканчиваться на .000")
		return
	}
	if _, err := os.Stat(src); err != nil {
		setupFlash(w, r, "Копирование контейнера: исходная папка недоступна: "+err.Error())
		return
	}
	dst, err := copyContainerToLocal(src)
	if err != nil {
		setupFlash(w, r, "Копирование контейнера: "+err.Error())
		return
	}
	// Дадим CSP несколько мс «заметить» новый контейнер (не критично).
	_, cancel := context.WithTimeout(r.Context(), 100*time.Millisecond)
	cancel()
	setupFlash(w, r, "Контейнер скопирован в "+dst+". Теперь он виден как \\\\.\\HDIMAGE\\"+strings.TrimSuffix(filepath.Base(dst), ".000")+" и работает без вставленной флешки. Импортируйте сертификат: certmgr -inst -cont '\\\\.\\HDIMAGE\\"+strings.TrimSuffix(filepath.Base(dst), ".000")+"' -store uMy.")
}
