// Package emitter: drift_path.go implements the systemd Path-unit
// emitter for critical drift paths (P10b, v0.11, REQ-105, R-018).
//
// For each critical path two units are emitted:
//
//   - <orca-drift-<name>.path>: PathChanged=<path>,
//     RateLimitIntervalSec=1s, RateLimitBurst=5 — systemd watches the
//     path for changes and triggers the matching .service.
//   - <orca-drift-<name>.service>: Type=oneshot,
//     ExecStart=/usr/local/bin/orca-drift-notify.sh %f, User=orca,
//     security-hardened (NoNewPrivileges=yes, ProtectSystem=strict,
//     ReadWritePaths=/etc/orca/state/drift-events, ProtectHome=yes).
//
// The emitter takes a list of critical PathSpecs (from drift.Config)
// and produces the unit File artifacts. Unit names are derived from a
// slug of the path pattern: non-alphanumerics collapse to '-' so the
// name is systemd-friendly (no slashes, spaces, or brace chars).
package emitter

import (
	"fmt"
	"strings"
)

// DriftPathUnit is the systemd Path + service unit emitter for critical
// drift paths (REQ-105).
type DriftPathUnit struct{}

// driftEventsDir is the directory the oneshot services write to
// (ReadWritePaths).
const driftEventsDir = "/etc/orca/state/drift-events"

// driftNotifyBin is the oneshot ExecStart binary.
const driftNotifyBin = "/usr/local/bin/orca-drift-notify.sh"

// RenderDriftUnits renders a pair of systemd units (a .path and a
// .service) for each critical path in specs. Returns the File slice
// ready for SSH-push to the peer.
func (DriftPathUnit) RenderDriftUnits(specs []DriftPathSpec) ([]File, error) {
	if len(specs) == 0 {
		return nil, nil
	}
	seen := make(map[string]bool, len(specs))
	var files []File
	for _, s := range specs {
		if s.Pattern == "" {
			return nil, fmt.Errorf("emitter/drift_path: empty pattern")
		}
		name := driftUnitName(s.Pattern)
		if seen[name] {
			continue
		}
		seen[name] = true
		files = append(files, File{
			Path:    fmt.Sprintf("/etc/systemd/system/%s.path", name),
			Content: renderDriftPathUnit(name, s.Pattern),
			Mode:    "0644",
		})
		files = append(files, File{
			Path:    fmt.Sprintf("/etc/systemd/system/%s.service", name),
			Content: renderDriftServiceUnit(name),
			Mode:    "0644",
		})
	}
	return files, nil
}

// DriftPathSpec is the minimal description the emitter needs: the
// pattern to watch and whether systemd Path units are requested (when
// false, no units are emitted — the path falls back to polling).
type DriftPathSpec struct {
	Pattern         string
	SystemdPathUnit bool
}

// driftUnitName derives a systemd-friendly unit-name slug from a path
// pattern. Non-alphanumeric runes collapse to '-'. The result carries
// the orca-drift- prefix so the units are identifiable as orca-owned.
func driftUnitName(pattern string) string {
	s := pattern
	s = strings.TrimPrefix(s, "/")
	var b strings.Builder
	prevDash := false
	for _, r := range s {
		if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') {
			b.WriteRune(r)
			prevDash = false
		} else {
			if !prevDash {
				b.WriteByte('-')
				prevDash = true
			}
		}
	}
	name := strings.Trim(b.String(), "-")
	if name == "" {
		name = "root"
	}
	return "orca-drift-" + name
}

// renderDriftPathUnit renders the .path unit. PathChanged re-fires on
// every modification (inotify IN_MODIFY), RateLimitIntervalSec +
// RateLimitBurst bound the burst so a thrashing file does not spawn
// thousands of services (R-018).
func renderDriftPathUnit(name, path string) string {
	var b strings.Builder
	b.WriteString("[Unit]\n")
	b.WriteString(fmt.Sprintf("Description=orca drift watch for %s\n", path))
	b.WriteString("\n[Path]\n")
	b.WriteString(fmt.Sprintf("PathChanged=%s\n", path))
	b.WriteString("RateLimitIntervalSec=1s\n")
	b.WriteString("RateLimitBurst=5\n")
	b.WriteString("\n[Install]\n")
	b.WriteString("WantedBy=multi-user.target\n")
	return b.String()
}

// renderDriftServiceUnit renders the oneshot .service triggered by
// the .path unit. ExecStart receives the changed path via %f. Security
// hardening runs the service as User=orca with NoNewPrivileges=yes,
// ProtectSystem=strict (read-only /), and ReadWritePaths scoped to the
// drift-events dir so the script can write its event JSON. ProtectHome
// hides /root and /home (the orca user has no business there).
func renderDriftServiceUnit(name string) string {
	var b strings.Builder
	b.WriteString("[Unit]\n")
	b.WriteString(fmt.Sprintf("Description=orca drift notify for %s\n", name))
	b.WriteString(fmt.Sprintf("After=%s.path\n", name))
	b.WriteString("\n[Service]\n")
	b.WriteString("Type=oneshot\n")
	b.WriteString(fmt.Sprintf("ExecStart=%s %%f\n", driftNotifyBin))
	b.WriteString("User=orca\n")
	b.WriteString("Group=orca\n")
	b.WriteString("NoNewPrivileges=yes\n")
	b.WriteString("ProtectSystem=strict\n")
	b.WriteString(fmt.Sprintf("ReadWritePaths=%s\n", driftEventsDir))
	b.WriteString("ProtectHome=yes\n")
	b.WriteString("PrivateTmp=yes\n")
	b.WriteString("ProtectKernelTunables=yes\n")
	b.WriteString("ProtectKernelModules=yes\n")
	b.WriteString("ProtectControlGroups=yes\n")
	b.WriteString("RestrictSUIDSGID=yes\n")
	b.WriteString("\n[Install]\n")
	b.WriteString("WantedBy=multi-user.target\n")
	return b.String()
}
