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

//go:build linux

package service

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"text/template"
)

// systemdManager implements Manager for systemd (Linux).
type systemdManager struct{}

const systemdServiceTemplate = `[Unit]
Description={{.Description}}
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart={{.ExecStart}}
Restart=on-failure
RestartSec=10
{{- if .WorkingDirectory}}
WorkingDirectory={{.WorkingDirectory}}
{{- end}}
{{- range $key, $val := .Environment}}
Environment="{{$key}}={{$val}}"
{{- end}}
{{- if .LogPath}}
StandardOutput=append:{{.LogPath}}
StandardError=append:{{.LogPath}}
{{- end}}

# Hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
PrivateTmp=true
{{- if .ReadWritePaths}}
ReadWritePaths={{.ReadWritePaths}}
{{- end}}

[Install]
WantedBy={{.WantedBy}}
`

type systemdTemplateData struct {
	Description      string
	ExecStart        string
	WorkingDirectory string
	Environment      map[string]string
	LogPath          string
	ReadWritePaths   string
	WantedBy         string
}

func (m *systemdManager) InitSystem() InitSystem {
	return InitSystemd
}

// unitFilePath returns the path to the .service unit file for the given level.
func (m *systemdManager) unitFilePath(level ServiceLevel) string {
	if level == LevelSystem {
		return filepath.Join("/etc/systemd/system", ServiceName+".service")
	}
	homeDir, _ := os.UserHomeDir()
	return filepath.Join(homeDir, ".config", "systemd", "user", ServiceName+".service")
}

// installedLevel attempts to determine which level the service is installed at.
func (m *systemdManager) installedLevel() ServiceLevel {
	if _, err := os.Stat(m.unitFilePath(LevelSystem)); err == nil {
		return LevelSystem
	}
	return LevelUser
}

func (m *systemdManager) IsInstalled() bool {
	if _, err := os.Stat(m.unitFilePath(LevelSystem)); err == nil {
		return true
	}
	if _, err := os.Stat(m.unitFilePath(LevelUser)); err == nil {
		return true
	}
	return false
}

func (m *systemdManager) Install(cfg Config) error {
	if m.IsInstalled() {
		return ErrServiceAlreadyInstalled
	}

	if cfg.Level == LevelSystem && !isRoot() {
		return ErrInsufficientPrivileges
	}

	unitPath := m.unitFilePath(cfg.Level)

	// Ensure parent directory exists (especially for user-level).
	if err := os.MkdirAll(filepath.Dir(unitPath), 0755); err != nil {
		return fmt.Errorf("failed to create unit directory: %w", err)
	}

	// Build ExecStart line.
	execParts := []string{cfg.BinaryPath}
	execParts = append(execParts, cfg.Args...)
	execStart := strings.Join(execParts, " ")

	// Build ReadWritePaths from config and log paths.
	homeDir, _ := os.UserHomeDir()
	configDir := filepath.Join(homeDir, ".config", "privatebackup")
	rwPaths := configDir
	if cfg.LogPath != "" {
		rwPaths += " " + filepath.Dir(cfg.LogPath)
	}

	wantedBy := "default.target"
	if cfg.Level == LevelSystem {
		wantedBy = "multi-user.target"
	}

	data := systemdTemplateData{
		Description:      cfg.Description,
		ExecStart:        execStart,
		WorkingDirectory: cfg.WorkingDirectory,
		Environment:      cfg.Environment,
		LogPath:          cfg.LogPath,
		ReadWritePaths:   rwPaths,
		WantedBy:         wantedBy,
	}

	tmpl, err := template.New("unit").Parse(systemdServiceTemplate)
	if err != nil {
		return fmt.Errorf("failed to parse unit template: %w", err)
	}

	var buf bytes.Buffer
	if err := tmpl.Execute(&buf, data); err != nil {
		return fmt.Errorf("failed to render unit file: %w", err)
	}

	if err := os.WriteFile(unitPath, buf.Bytes(), 0644); err != nil {
		return fmt.Errorf("failed to write unit file: %w", err)
	}

	// Reload systemd configuration.
	if err := m.systemctl(cfg.Level, "daemon-reload"); err != nil {
		return fmt.Errorf("daemon-reload failed: %w", err)
	}

	// Enable the service (start on boot).
	if err := m.systemctl(cfg.Level, "enable", ServiceName+".service"); err != nil {
		return fmt.Errorf("enable failed: %w", err)
	}

	// For user-level, enable lingering so the service runs even without a login session.
	if cfg.Level == LevelUser {
		user := cfg.User
		if user == "" {
			user = currentUsername()
		}
		if user != "" {
			// loginctl enable-linger may require root or polkit permission;
			// ignore errors since it's not strictly required.
			_ = exec.Command("loginctl", "enable-linger", user).Run()
		}
	}

	return nil
}

func (m *systemdManager) Uninstall() error {
	if !m.IsInstalled() {
		return ErrServiceNotInstalled
	}

	level := m.installedLevel()

	// Stop first (ignore errors — may already be stopped).
	_ = m.systemctl(level, "stop", ServiceName+".service")

	// Disable.
	_ = m.systemctl(level, "disable", ServiceName+".service")

	// Remove unit file.
	unitPath := m.unitFilePath(level)
	if err := os.Remove(unitPath); err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("failed to remove unit file: %w", err)
	}

	// Reload.
	_ = m.systemctl(level, "daemon-reload")

	return nil
}

func (m *systemdManager) Start() error {
	if !m.IsInstalled() {
		return ErrServiceNotInstalled
	}
	level := m.installedLevel()
	return m.systemctl(level, "start", ServiceName+".service")
}

func (m *systemdManager) Stop() error {
	if !m.IsInstalled() {
		return ErrServiceNotInstalled
	}
	level := m.installedLevel()
	return m.systemctl(level, "stop", ServiceName+".service")
}

func (m *systemdManager) Restart() error {
	if !m.IsInstalled() {
		return ErrServiceNotInstalled
	}
	level := m.installedLevel()
	return m.systemctl(level, "restart", ServiceName+".service")
}

func (m *systemdManager) Status() (Status, error) {
	st := Status{
		InitSystem: InitSystemd,
	}

	if !m.IsInstalled() {
		return st, nil
	}
	st.Installed = true

	level := m.installedLevel()
	if level == LevelSystem {
		st.Level = "system"
	} else {
		st.Level = "user"
	}
	st.UnitName = ServiceName + ".service"

	// Check if active.
	out, err := m.systemctlOutput(level, "is-active", ServiceName+".service")
	if err == nil && strings.TrimSpace(out) == "active" {
		st.Running = true
	}

	// Check if enabled.
	out, err = m.systemctlOutput(level, "is-enabled", ServiceName+".service")
	if err == nil && strings.TrimSpace(out) == "enabled" {
		st.Enabled = true
	}

	// Get PID if running.
	if st.Running {
		out, err = m.systemctlOutput(level, "show", "--property=MainPID", ServiceName+".service")
		if err == nil {
			out = strings.TrimSpace(out)
			if strings.HasPrefix(out, "MainPID=") {
				if pid, err := strconv.Atoi(strings.TrimPrefix(out, "MainPID=")); err == nil {
					st.PID = pid
				}
			}
		}
	}

	return st, nil
}

// systemctl runs a systemctl command at the appropriate level.
func (m *systemdManager) systemctl(level ServiceLevel, args ...string) error {
	if level == LevelUser {
		args = append([]string{"--user"}, args...)
	}
	cmd := exec.Command("systemctl", args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

// systemctlOutput runs a systemctl command and captures stdout.
func (m *systemdManager) systemctlOutput(level ServiceLevel, args ...string) (string, error) {
	if level == LevelUser {
		args = append([]string{"--user"}, args...)
	}
	out, err := exec.Command("systemctl", args...).Output()
	return string(out), err
}
