// SPDX-License-Identifier: BSD-3-Clause
//go:build darwin && !cgo

package process

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"github.com/shirou/gopsutil/v4/cpu"
	"github.com/shirou/gopsutil/v4/internal/common"
)

func (p *Process) CwdWithContext(ctx context.Context) (string, error) {
	return "", common.ErrNotImplementedError
}

func (p *Process) ExeWithContext(ctx context.Context) (string, error) {
	out, err := invoke.CommandWithContext(ctx, "lsof", "-p", strconv.Itoa(int(p.Pid)), "-Fpfn")
	if err != nil {
		return "", fmt.Errorf("bad call to lsof: %w", err)
	}
	txtFound := 0
	lines := strings.Split(string(out), "\n")
	fallback := ""
	for i := 1; i < len(lines); i++ {
		if lines[i] == "ftxt" {
			txtFound++
			if txtFound == 1 {
				fallback = lines[i-1][1:]
			}
			if txtFound == 2 {
				return lines[i-1][1:], nil
			}
		}
	}
	if fallback != "" {
		return fallback, nil
	}
	return "", fmt.Errorf("missing txt data returned by lsof")
}

func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) {
	r, err := callPsWithContext(ctx, "command", p.Pid, false, false)
	if err != nil {
		return "", err
	}
	return strings.Join(r[0], " "), err
}

func (p *Process) cmdNameWithContext(ctx context.Context) (string, error) {
	r, err := callPsWithContext(ctx, "command", p.Pid, false, true)
	if err != nil {
		return "", err
	}
	if len(r) > 0 && len(r[0]) > 0 {
		return r[0][0], err
	}

	return "", err
}

// CmdlineSliceWithContext returns the command line arguments of the process as a slice with each
// element being an argument. Because of current deficiencies in the way that the command
// line arguments are found, single arguments that have spaces in the will actually be
// reported as two separate items. In order to do something better CGO would be needed
// to use the native darwin functions.
func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) {
	r, err := callPsWithContext(ctx, "command", p.Pid, false, false)
	if err != nil {
		return nil, err
	}
	return r[0], err
}

func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) {
	r, err := callPsWithContext(ctx, "utime,stime", p.Pid, true, false)
	if err != nil {
		return 0, err
	}
	return int32(len(r)), nil
}

func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) {
	r, err := callPsWithContext(ctx, "utime,stime", p.Pid, false, false)
	if err != nil {
		return nil, err
	}

	utime, err := convertCPUTimes(r[0][0])
	if err != nil {
		return nil, err
	}
	stime, err := convertCPUTimes(r[0][1])
	if err != nil {
		return nil, err
	}

	ret := &cpu.TimesStat{
		CPU:    "cpu",
		User:   utime,
		System: stime,
	}
	return ret, nil
}

func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) {
	r, err := callPsWithContext(ctx, "rss,vsize,pagein", p.Pid, false, false)
	if err != nil {
		return nil, err
	}
	rss, err := strconv.ParseInt(r[0][0], 10, 64)
	if err != nil {
		return nil, err
	}
	vms, err := strconv.ParseInt(r[0][1], 10, 64)
	if err != nil {
		return nil, err
	}
	pagein, err := strconv.ParseInt(r[0][2], 10, 64)
	if err != nil {
		return nil, err
	}

	ret := &MemoryInfoStat{
		RSS:  uint64(rss) * 1024,
		VMS:  uint64(vms) * 1024,
		Swap: uint64(pagein),
	}

	return ret, nil
}
