//go:build linux

package runtimeproc

import (
	"context"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

type platformManager struct{}

// NewPlatformManager returns the Linux process manager used by Janitor.
func NewPlatformManager() Manager { return platformManager{} }

func (platformManager) ListProcesses(context.Context) ([]ProcessInfo, error) {
	bootTime := linuxBootTime()
	entries, err := os.ReadDir("/proc")
	if err != nil {
		return nil, err
	}
	processes := make([]ProcessInfo, 0, len(entries))
	for _, entry := range entries {
		pid, err := strconv.Atoi(entry.Name())
		if err != nil || pid <= 0 {
			continue
		}
		proc := ProcessInfo{PID: pid}
		if cwd, err := os.Readlink(filepath.Join("/proc", entry.Name(), "cwd")); err == nil {
			proc.CWD = cwd
		}
		if exe, err := os.Readlink(filepath.Join("/proc", entry.Name(), "exe")); err == nil {
			proc.Exe = exe
		}
		if data, err := os.ReadFile(filepath.Join("/proc", entry.Name(), "cmdline")); err == nil {
			proc.Cmdline = strings.Trim(strings.ReplaceAll(string(data), "\x00", " "), " ")
		}
		if data, err := os.ReadFile(filepath.Join("/proc", entry.Name(), "stat")); err == nil {
			fillLinuxStat(&proc, string(data), bootTime)
		}
		processes = append(processes, proc)
	}
	return processes, nil
}

const linuxClockTicksPerSecond = 100

func fillLinuxStat(proc *ProcessInfo, stat string, bootTime time.Time) {
	end := strings.LastIndex(stat, ")")
	if end < 0 || end+2 >= len(stat) {
		return
	}
	fields := strings.Fields(stat[end+2:])
	// fields[0] is state, fields[1] is ppid, fields[2] is pgrp.
	if len(fields) > 1 {
		proc.PPID, _ = strconv.Atoi(fields[1])
	}
	if len(fields) > 2 {
		proc.PGID, _ = strconv.Atoi(fields[2])
	}
	if len(fields) > 19 && !bootTime.IsZero() {
		if ticks, err := strconv.ParseInt(fields[19], 10, 64); err == nil && ticks >= 0 {
			proc.StartedAt = bootTime.Add(time.Duration(ticks) * time.Second / linuxClockTicksPerSecond)
		}
	}
}

func linuxBootTime() time.Time {
	data, err := os.ReadFile("/proc/stat")
	if err != nil {
		return time.Time{}
	}
	for _, line := range strings.Split(string(data), "\n") {
		fields := strings.Fields(line)
		if len(fields) == 2 && fields[0] == "btime" {
			sec, err := strconv.ParseInt(fields[1], 10, 64)
			if err == nil && sec > 0 {
				return time.Unix(sec, 0)
			}
		}
	}
	return time.Time{}
}
