package kas

import (
	"bytes"
	"errors"
	"log/slog"
	"path"
	"runtime/debug"
	"runtime/pprof"
	"time"

	"github.com/getsentry/sentry-go"
)

type stacktraceCollector struct {
	log               *slog.Logger
	hub               *sentry.Hub
	interval          time.Duration
	start             time.Time
	excludeWithFrames [][]byte
}

func startStacktraceCollector(log *slog.Logger, hub *sentry.Hub, interval time.Duration) {
	var gomodPath string
	if buildInfo, ok := debug.ReadBuildInfo(); ok {
		// we should always have the build info available, but lets still account for it.
		// It's not a big deal if the path is not available, the kubernetes_api module path
		// should be unique enough already.
		gomodPath = buildInfo.Main.Path
	}
	s := &stacktraceCollector{
		log:      log,
		hub:      hub,
		interval: interval,
		start:    time.Now().UTC(),
		excludeWithFrames: [][]byte{
			// Kubernetes API server proxying a request
			[]byte(path.Join(gomodPath, "internal/module/kubernetes_api/server.(*kubernetesAPIProxy).proxy")),
			// Private API server proxying a request. This may be a request coming from another kas instance, so
			// the "Kubernetes API server proxying a request" frame may not be present.
			[]byte("/tunserver.(*TunnelImpl[...]).ForwardStream"),
		},
	}
	s.Start()
}

func (s *stacktraceCollector) Start() {
	time.AfterFunc(s.interval, s.capture)
}

func (s *stacktraceCollector) capture() {
	defer s.Start() // restart for periodic capturing

	var b bytes.Buffer
	_ = pprof.Lookup("goroutine").WriteTo(&b, 1)
	goroutines := b.Bytes()

	for _, frame := range s.excludeWithFrames {
		if bytes.Contains(goroutines, frame) {
			s.log.Debug("Captured goroutines that contain an excluded frame. Not sending goroutines to Sentry",
				slog.String("frame", string(frame)))
			return
		}
	}

	delayedBySec := time.Since(s.start) / time.Second //nolint:staticcheck
	s.log.Warn("Capturing goroutines because of delayed shutdown",
		slog.Time("start", s.start),
		slog.Int64("delayed_by_sec", int64(delayedBySec)),
	)

	e := sentry.NewEvent()
	e.Level = sentry.LevelWarning
	e.SetException(errors.New("delayed shutdown"), -1)
	e.Attachments = []*sentry.Attachment{
		{
			Filename:    "goroutines.txt",
			ContentType: "text/plain",
			Payload:     goroutines,
		},
	}
	e.Extra = map[string]any{
		"start":          s.start,
		"delayed_by_sec": delayedBySec,
	}
	s.hub.CaptureEvent(e)
}
