//go:build windows

package runtimeproc

import (
	"context"
	"encoding/csv"
	"encoding/json"
	"os/exec"
	"strconv"
	"strings"
	"syscall"
	"time"
)

const createNoWindow = 0x08000000

type platformManager struct{}

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

type windowsProcessRow struct {
	ProcessID       int    `json:"ProcessId"`
	ParentProcessID int    `json:"ParentProcessId"`
	ExecutablePath  string `json:"ExecutablePath"`
	CommandLine     string `json:"CommandLine"`
	CreationDate    string `json:"CreationDate"`
}

func (platformManager) ListProcesses(ctx context.Context) ([]ProcessInfo, error) {
	processes, err := listWindowsProcessesWMIC(ctx)
	if err == nil {
		return processes, nil
	}
	return listWindowsProcessesPowerShell(ctx)
}

func listWindowsProcessesWMIC(ctx context.Context) ([]ProcessInfo, error) {
	cmd := exec.CommandContext(ctx, "wmic", "process", "get", "ProcessId,ParentProcessId,ExecutablePath,CommandLine,CreationDate", "/format:csv")
	configureProcessCommand(cmd)
	out, err := cmd.Output()
	if err != nil {
		return nil, err
	}
	reader := csv.NewReader(strings.NewReader(string(out)))
	reader.FieldsPerRecord = -1
	records, err := reader.ReadAll()
	if err != nil {
		return nil, err
	}
	if len(records) == 0 {
		return nil, nil
	}
	var header []string
	var processes []ProcessInfo
	for _, record := range records {
		if recordEmpty(record) {
			continue
		}
		if header == nil {
			header = record
			continue
		}
		values := map[string]string{}
		for i, name := range header {
			if i < len(record) {
				values[strings.TrimSpace(name)] = strings.TrimSpace(record[i])
			}
		}
		pid, err := strconv.Atoi(values["ProcessId"])
		if err != nil || pid <= 0 {
			continue
		}
		ppid, _ := strconv.Atoi(values["ParentProcessId"])
		processes = append(processes, ProcessInfo{
			PID:       pid,
			PPID:      ppid,
			Exe:       values["ExecutablePath"],
			Cmdline:   values["CommandLine"],
			StartedAt: parseWindowsCreationTime(values["CreationDate"]),
		})
	}
	return processes, nil
}

func listWindowsProcessesPowerShell(ctx context.Context) ([]ProcessInfo, error) {
	cmd := exec.CommandContext(ctx, "powershell", "-NoProfile", "-NonInteractive", "-Command",
		"Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,ExecutablePath,CommandLine,CreationDate | ConvertTo-Json -Compress")
	configureProcessCommand(cmd)
	out, err := cmd.Output()
	if err != nil {
		return nil, err
	}
	var rows []windowsProcessRow
	if err := json.Unmarshal(out, &rows); err != nil {
		var row windowsProcessRow
		if singleErr := json.Unmarshal(out, &row); singleErr != nil {
			return nil, err
		}
		rows = []windowsProcessRow{row}
	}
	processes := make([]ProcessInfo, 0, len(rows))
	for _, row := range rows {
		if row.ProcessID <= 0 {
			continue
		}
		processes = append(processes, ProcessInfo{
			PID:       row.ProcessID,
			PPID:      row.ParentProcessID,
			Exe:       row.ExecutablePath,
			Cmdline:   row.CommandLine,
			StartedAt: parseWindowsCreationTime(row.CreationDate),
		})
	}
	return processes, nil
}

func parseWindowsCreationTime(value string) time.Time {
	value = strings.TrimSpace(value)
	if value == "" {
		return time.Time{}
	}
	if strings.HasPrefix(value, "/Date(") {
		end := strings.Index(value, ")/")
		if end > len("/Date(") {
			msText := value[len("/Date("):end]
			if plus := strings.IndexAny(msText, "+-"); plus > 0 {
				msText = msText[:plus]
			}
			if ms, err := strconv.ParseInt(msText, 10, 64); err == nil {
				return time.UnixMilli(ms)
			}
		}
	}
	if len(value) >= 14 && value[0] >= '0' && value[0] <= '9' {
		if parsed, err := time.ParseInLocation("20060102150405", value[:14], time.Local); err == nil {
			return parsed
		}
	}
	for _, layout := range []string{time.RFC3339Nano, time.RFC3339, "2006-01-02T15:04:05"} {
		if parsed, err := time.Parse(layout, value); err == nil {
			return parsed
		}
	}
	return time.Time{}
}

func recordEmpty(record []string) bool {
	for _, field := range record {
		if strings.TrimSpace(field) != "" {
			return false
		}
	}
	return true
}

func (platformManager) KillTree(ctx context.Context, pid int) error {
	if pid <= 0 {
		return nil
	}
	cmd := exec.CommandContext(ctx, "taskkill", "/T", "/F", "/PID", strconv.Itoa(pid))
	configureProcessCommand(cmd)
	out, err := cmd.CombinedOutput()
	if err != nil && !strings.Contains(strings.ToLower(string(out)), "not found") {
		return err
	}
	return nil
}

func configureProcessCommand(cmd *exec.Cmd) {
	if cmd == nil {
		return
	}
	if cmd.SysProcAttr == nil {
		cmd.SysProcAttr = &syscall.SysProcAttr{}
	}
	cmd.SysProcAttr.HideWindow = true
	cmd.SysProcAttr.CreationFlags |= createNoWindow
}
