// SPDX-License-Identifier: GPL-3.0-or-later

// Package service provides cross-platform system service management for
// the Private Archive client backend. It detects the host's init system and
// installs/uninstalls the backend as a persistent service that starts
// automatically on boot.
//
// Supported init systems:
//   - systemd  (Linux - most distros)
//   - OpenRC   (Linux - Alpine, Gentoo)
//   - launchd  (macOS)
//   - Windows Service Control Manager (Windows)
//   - rc.d     (FreeBSD, OpenBSD, NetBSD, DragonFly)
package service

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
)

// InitSystem identifies the host's init/service manager.
type InitSystem string

const (
	InitSystemd InitSystem = "systemd"
	InitOpenRC  InitSystem = "openrc"
	InitLaunchd InitSystem = "launchd"
	InitWindows InitSystem = "windows"
	InitRcd     InitSystem = "rcd"
	InitUnknown InitSystem = "unknown"
)

// ServiceLevel indicates whether the service runs system-wide or per-user.
type ServiceLevel int

const (
	// LevelUser installs a user-level service (no root/admin required).
	LevelUser ServiceLevel = iota

	// LevelSystem installs a system-wide service (requires root/admin).
	LevelSystem
)

// Status represents the current state of the service.
type Status struct {
	Installed  bool       `json:"installed"`
	Running    bool       `json:"running"`
	Enabled    bool       `json:"enabled"`
	InitSystem InitSystem `json:"init_system"`
	Level      string     `json:"level"`
	UnitName   string     `json:"unit_name"`
	PID        int        `json:"pid,omitempty"`
}

// Config holds parameters for service installation.
type Config struct {
	BinaryPath       string
	DisplayName      string
	Description      string
	Level            ServiceLevel
	Args             []string
	User             string
	WorkingDirectory string
	Environment      map[string]string
	LogPath          string
}

// Manager is the interface that each init-system backend implements.
type Manager interface {
	Install(cfg Config) error
	Uninstall() error
	Start() error
	Stop() error
	Restart() error
	Status() (Status, error)
	IsInstalled() bool
	InitSystem() InitSystem
}

const (
	ServiceName        = "privatebackup-backend"
	ServiceDisplayName = "Private Archive Client Backend"
	ServiceDescription = "Privacy-focused encrypted backup client daemon"
)

// DefaultConfig returns a Config with sensible defaults filled in.
func DefaultConfig() Config {
	binaryPath, _ := os.Executable()
	if binaryPath != "" {
		binaryPath, _ = filepath.Abs(binaryPath)
	}
	homeDir, _ := os.UserHomeDir()

	return Config{
		BinaryPath:       binaryPath,
		DisplayName:      ServiceDisplayName,
		Description:      ServiceDescription,
		Level:            LevelUser,
		Args:             nil,
		User:             currentUsername(),
		WorkingDirectory: homeDir,
		Environment:      map[string]string{},
	}
}

// Detect identifies the init system on the current host.
func Detect() InitSystem {
	return detectInitSystem()
}

// New creates a Manager for the detected (or specified) init system.
func New(initSys InitSystem) (Manager, error) {
	if initSys == InitUnknown || initSys == "" {
		initSys = Detect()
	}
	return newManager(initSys)
}

func currentUsername() string {
	if u := os.Getenv("USER"); u != "" {
		return u
	}
	if u := os.Getenv("USERNAME"); u != "" {
		return u
	}
	return ""
}

func commandExists(name string) bool {
	_, err := exec.LookPath(name)
	return err == nil
}

func isRoot() bool {
	if runtime.GOOS == "windows" {
		return false
	}
	return os.Geteuid() == 0
}

// ErrUnsupportedInitSystem is returned when no supported init system is found.
var ErrUnsupportedInitSystem = errors.New("no supported init system detected")

// ErrServiceNotInstalled is returned when operating on a service that is not installed.
var ErrServiceNotInstalled = errors.New("service is not installed")

// ErrServiceAlreadyInstalled is returned when installing a service that already exists.
var ErrServiceAlreadyInstalled = errors.New("service is already installed")

// ErrInsufficientPrivileges is returned when the operation requires elevated privileges.
var ErrInsufficientPrivileges = fmt.Errorf("insufficient privileges: run with sudo or choose user-level installation")

// EnsureInstalled is an idempotent helper that checks whether the backend
// service is registered with the OS init system and, if not, installs it as
// a user-level service. It is safe to call on every startup or after first
// registration — it does nothing when the service is already installed.
//
// Returns (installed, error) where installed is true only if a NEW
// installation was performed during this call.
func EnsureInstalled() (bool, error) {
	initSys := Detect()
	if initSys == InitUnknown {
		return false, ErrUnsupportedInitSystem
	}

	mgr, err := New(initSys)
	if err != nil {
		return false, fmt.Errorf("failed to create service manager: %w", err)
	}

	if mgr.IsInstalled() {
		return false, nil // already set up — nothing to do
	}

	cfg := DefaultConfig()
	// Always user-level for auto-install (no root prompts).
	cfg.Level = LevelUser

	if err := mgr.Install(cfg); err != nil {
		return false, fmt.Errorf("auto-install service failed: %w", err)
	}

	// Best-effort start — the service will start on next boot regardless.
	_ = mgr.Start()

	return true, nil
}
