package containers

import (
	"bytes"
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/coroot/coroot-node-agent/ebpftracer/tracer/jattach"
	. "github.com/coroot/coroot-node-agent/utils/modelse"

	"github.com/cilium/ebpf/link"
	"github.com/coroot/coroot-node-agent/ebpftracer"
	"github.com/coroot/coroot-node-agent/proc"
	"github.com/jpillora/backoff"
	"github.com/mdlayher/taskstats"
)

type Process struct {
	Pid       uint32
	StartedAt time.Time

	netNsId string

	ctx        context.Context
	cancelFunc context.CancelFunc

	dotNetMonitor *DotNetMonitor
	isGolangApp   bool

	uprobes               []link.Link
	stackUprobes          []link.Link
	goTlsUprobesChecked   bool
	openSslUprobesChecked bool

	jvmAttachOnce    bool
	pyAttachOnce     bool
	nodejsAttachOnce bool
	nginxAttachOnce  bool
	stackAttachOnce  bool
	stackStatus      StackStatus
	versionFailed    bool
	codeType         CodeType
	cmdline          string
	pythonGilChecked bool
	l4HeaderCgroup   string
}

func NewProcess(pid uint32, stats *taskstats.Stats, tracer *ebpftracer.Tracer) *Process {
	p := &Process{Pid: pid, StartedAt: stats.BeginTime}
	p.ctx, p.cancelFunc = context.WithCancel(context.Background())
	//go p.instrument(tracer)
	return p
}

func (p *Process) NetNsId() string {
	if p.netNsId == "" {
		ns, err := proc.GetNetNs(p.Pid)
		if err != nil {
			return ""
		}
		p.netNsId = ns.UniqueId()
		_ = ns.Close()
	}
	return p.netNsId
}

func (p *Process) isHostNs() bool {
	return p.NetNsId() == hostNetNsId
}

func (p *Process) instrument(tracer *ebpftracer.Tracer) {
	b := backoff.Backoff{Factor: 2, Min: time.Second, Max: time.Minute}
	for {
		select {
		case <-p.ctx.Done():
			return
		default:
			dest, err := os.Readlink(proc.Path(p.Pid, "exe"))
			if err != nil {
				return
			}
			if dest != "/" {
				p.instrumentPython(tracer)
				if dotNetAppName, err := dotNetApp(p.Pid); err == nil {
					if dotNetAppName != "" {
						p.dotNetMonitor = NewDotNetMonitor(p.ctx, p.Pid, dotNetAppName)
					}
				}
				return
			}
			time.Sleep(b.Duration())
		}
	}
}

func (p *Process) instrumentPython(tracer *ebpftracer.Tracer) {
	if p.pythonGilChecked {
		return
	}
	p.pythonGilChecked = true
	cmdline := proc.GetCmdline(p.Pid)
	if len(cmdline) == 0 {
		return
	}
	parts := bytes.Split(cmdline, []byte{0})
	cmd := parts[0]
	if len(cmd) == 0 {
		return
	}
	cmd = bytes.TrimSuffix(bytes.Fields(cmd)[0], []byte{':'})
	if !pythonCmd.Match(cmd) {
		return
	}
	p.uprobes = append(p.uprobes, tracer.AttachPythonThreadLockProbes(p.Pid)...)
}

func (p *Process) Close() {
	p.cancelFunc()
	for _, u := range p.uprobes {
		_ = u.Close()
	}
}

func (p *Process) closeStackUprobes() error {
	for _, u := range p.stackUprobes {
		err := u.Close()
		if err != nil {
			return err
		}
	}
	p.stackUprobes = []link.Link{}
	p.stackStatus.StackUprobesClose()
	return nil
}

func (p *Process) DynamicClose(closeType int) {
	for _, u := range p.uprobes {
		_ = u.Close()
	}
	//p.goTlsUprobesChecked = false
	//p.openSslUprobesChecked = false
	//p.jvmUprobesChecked = false
	//if p.codeType.IsJvmCode() && p.stackUprobesChecked {
	//	nativeBasePath := utils.GetDefaultAgentsPath("NativeAgent")
	//	kvPairs := []string{
	//		fmt.Sprintf("%s=%s", filepath.Join(nativeBasePath, "lib", "apmAgent.jar"), nativeBasePath),
	//		"UNINSTALL",
	//	}
	//	argkv := strings.Join(kvPairs, ",")
	//	args := []string{"load", "instrument", "false", argkv}
	//	jattacher := jattach.JvmJattacher{
	//		Pid:         p.Pid,
	//		Args:        args,
	//		PrintOutput: 1,
	//	}
	//	_, err := jattacher.JDetach()
	//	if err == nil {
	//		//p.stackUprobesChecked = false
	//	}
	//}
	p.uprobes = []link.Link{}
}

func (p *Process) uninstallJavaAgent() error {
	if p.codeType.IsJvmCode() && p.stackAttachOnce {
		//nativeBasePath := utils.GetDefaultAgentsPath("NativeAgent")
		nativeBasePath := "/tmp/NativeAgent"
		kvPairs := []string{
			fmt.Sprintf("%s=%s", filepath.Join(nativeBasePath, "lib", "apmAgent.jar"), nativeBasePath),
			"UNINSTALL",
		}
		argkv := strings.Join(kvPairs, ",")
		args := []string{"load", "instrument", "false", argkv}
		jattacher := jattach.JvmJattacher{
			Pid:         p.Pid,
			Args:        args,
			PrintOutput: 1,
		}
		_, err := jattacher.JDetach()
		if err != nil {
			return err
		}
		p.stackStatus.JattachClose()
		return nil
	}
	return nil
}

func (p *Process) GetCmdline() string {
	return p.cmdline
}

func (p *Process) SetCmdline(cmdline string) {
	p.cmdline = cmdline
}
