package main

import (
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
)

type Backend string

const (
	BackendRestic Backend = "restic"
	BackendRustic Backend = "rustic"
)

func (p Profile) backend() Backend {
	switch Backend(strings.ToLower(strings.TrimSpace(p.Backend))) {
	case "", BackendRestic:
		return BackendRestic
	case BackendRustic:
		return BackendRustic
	default:
		return Backend("")
	}
}

func (p Profile) backendName() string {
	if b := p.backend(); b != "" {
		return string(b)
	}
	return p.Backend
}

func (p Profile) backendDisplayName() string {
	switch p.backend() {
	case BackendRestic:
		return "Restic"
	case BackendRustic:
		return "Rustic"
	default:
		return p.backendName()
	}
}

func (b Backend) supportsUnlock() bool { return b == BackendRestic }

func mountSupported(prof Profile) bool {
	if prof.backend() == BackendRestic {
		return runtime.GOOS != "windows"
	}
	if prof.backend() != BackendRustic || runtime.GOOS != "linux" {
		return false
	}
	path, _ := findBackend(prof)
	if path == "" {
		return false
	}
	cmd := exec.Command(path, "mount", "--help")
	hideWindow(cmd)
	return cmd.Run() == nil
}

func backendEnvError(b Backend, vars map[string]string) string {
	prefix := strings.ToUpper(string(b))
	if vars[prefix+"_REPOSITORY"] == "" {
		return prefix + "_REPOSITORY not set in env file"
	}
	if b == BackendRustic {
		return ""
	}
	if vars["RESTIC_PASSWORD"] == "" && vars["RESTIC_PASSWORD_FILE"] == "" && vars["RESTIC_PASSWORD_COMMAND"] == "" {
		return "No password set in env file"
	}
	return ""
}

var rusticBuiltinPath string
var resticBuiltinPath string

var backendPaths = map[Backend]*backendPathState{
	BackendRestic: {},
	BackendRustic: {},
}

type backendPathState struct {
	sync.Mutex
	path    string
	managed bool
}

func findBackend(prof Profile) (string, bool) {
	state := backendPaths[prof.backend()]
	if state == nil {
		return "", false
	}
	state.Lock()
	defer state.Unlock()
	if state.path != "" {
		return state.path, state.managed
	}
	state.path, state.managed = searchBackend(prof.backend())
	return state.path, state.managed
}

func searchBackend(b Backend) (string, bool) {
	name := string(b)
	if p, err := exec.LookPath(name); err == nil {
		return p, false
	}
	if p := findBackendFromShell(name); p != "" {
		return p, false
	}
	if p := builtinBackend(b); p != "" {
		if _, err := os.Stat(p); err == nil {
			return p, false
		}
	}
	if p := managedBackend(b); p != "" {
		return p, true
	}
	if p := bundledBackend(name); p != "" {
		return p, false
	}
	return "", false
}

func builtinBackend(b Backend) string {
	switch b {
	case BackendRestic:
		return resticBuiltinPath
	case BackendRustic:
		return rusticBuiltinPath
	default:
		return ""
	}
}

func managedBackend(b Backend) string {
	switch b {
	case BackendRestic:
		return managedRestic()
	case BackendRustic:
		return managedRustic()
	default:
		return ""
	}
}

func bundledBackend(name string) string {
	if runtime.GOOS != "darwin" {
		return ""
	}
	exe, err := os.Executable()
	if err != nil {
		return ""
	}
	p := filepath.Join(filepath.Dir(exe), "..", "Resources", name)
	if _, err := os.Stat(p); err == nil {
		return p
	}
	return ""
}

func findBackendFromShell(name string) string {
	if runtime.GOOS == "windows" {
		return ""
	}
	shell := os.Getenv("SHELL")
	if shell == "" {
		if runtime.GOOS == "darwin" {
			shell = "/bin/zsh"
		} else {
			shell = "/bin/sh"
		}
	}
	out, err := exec.Command(shell, "-l", "-c", "command -v "+name).Output()
	if err == nil {
		if p := strings.TrimSpace(string(out)); p != "" {
			if _, err := os.Stat(p); err == nil {
				return p
			}
		}
	}
	for _, dir := range []string{"/opt/homebrew/bin", "/usr/local/bin", filepath.Join(os.Getenv("HOME"), ".nix-profile/bin"), "/run/current-system/sw/bin", filepath.Join(os.Getenv("HOME"), ".local/bin")} {
		p := filepath.Join(dir, name)
		if _, err := os.Stat(p); err == nil {
			return p
		}
	}
	return ""
}

func backendCmd(prof Profile, args ...string) *exec.Cmd {
	path, _ := findBackend(prof)
	args = backendCommandArgs(prof, args...)
	log.Printf("[%s] run: %s %s", prof.displayName(), prof.backendName(), strings.Join(args, " "))
	cmd := exec.Command(path, args...)
	cmd.Env = profileEnv(prof)
	hideWindow(cmd)
	return cmd
}

func backendCommandArgs(prof Profile, args ...string) []string {
	if prof.backend() == BackendRustic {
		return append([]string{"--log-level", "warn"}, args...)
	}
	return args
}

func backendUnavailable(prof Profile, operation string) string {
	if operation == "mount" && prof.backend() == BackendRustic && runtime.GOOS == "linux" {
		return "mount is unavailable because this rustic build lacks FUSE mount support"
	}
	return operation + " is not supported by " + prof.backendName() + " on " + runtime.GOOS
}
