// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package trace_test

import (
	"bytes"
	inttrace "internal/trace"
	"internal/trace/testtrace"
	"io"
	"runtime"
	"runtime/trace"
	"slices"
	"testing"
)

func TestSubscribers(t *testing.T) {
	validate := func(t *testing.T, source string, tr []byte) {
		t.Log("validating", source)
		defer func() {
			if t.Failed() {
				testtrace.Dump(t, "TestSubscribers."+source, tr, *dumpTraces)
			}
		}()

		// Prepare to read the trace snapshot.
		r, err := inttrace.NewReader(bytes.NewReader(tr))
		if err != nil {
			t.Errorf("unexpected error creating trace reader for %s: %v", source, err)
			return
		}

		v := testtrace.NewValidator()
		// These platforms can't guarantee a monotonically increasing clock reading in a short trace.
		if runtime.GOOS == "windows" || runtime.GOARCH == "wasm" {
			v.SkipClockSnapshotChecks()
		}
		// Make sure there are Sync events: at the start and end.
		var syncs []int
		evs := 0
		for {
			ev, err := r.ReadEvent()
			if err == io.EOF {
				break
			}
			if err != nil {
				t.Errorf("unexpected error reading trace for %s: %v", source, err)
				break
			}
			if err := v.Event(ev); err != nil {
				t.Errorf("event validation failed: %s", err)
				break
			}
			if ev.Kind() == inttrace.EventSync {
				syncs = append(syncs, evs)
			}
			evs++
		}
		if !t.Failed() {
			ends := []int{syncs[0], syncs[len(syncs)-1]}
			if wantEnds := []int{0, evs - 1}; !slices.Equal(wantEnds, ends) {
				t.Errorf("expected a sync event at each end of the trace, found sync events at %d instead of %d for %s",
					ends, wantEnds, source)
			}
		}
	}

	validateTraces := func(t *testing.T, trace, frTrace *bytes.Buffer) {
		validate(t, "tracer", trace.Bytes())
		validate(t, "flightRecorder", frTrace.Bytes())
	}
	startFlightRecorder := func(t *testing.T) *trace.FlightRecorder {
		fr := trace.NewFlightRecorder(trace.FlightRecorderConfig{})
		if err := fr.Start(); err != nil {
			t.Fatalf("unexpected error creating flight recorder: %v", err)
		}
		return fr
	}
	startTrace := func(t *testing.T, w io.Writer) {
		if err := trace.Start(w); err != nil {
			t.Fatalf("unexpected error starting flight recorder: %v", err)
		}
	}
	stopFlightRecorder := func(t *testing.T, fr *trace.FlightRecorder, w io.Writer) {
		if _, err := fr.WriteTo(w); err != nil {
			t.Fatalf("unexpected error writing trace from flight recorder: %v", err)
		}
		fr.Stop()
	}
	stopTrace := func() {
		trace.Stop()
	}
	t.Run("start(flight)_start(trace)_stop(trace)_stop(flight)", func(t *testing.T) {
		if trace.IsEnabled() {
			t.Skip("skipping because trace is already enabled")
		}
		frBuf := new(bytes.Buffer)
		tBuf := new(bytes.Buffer)
		fr := startFlightRecorder(t)
		defer fr.Stop()
		startTrace(t, tBuf)
		defer trace.Stop()
		stopTrace()
		stopFlightRecorder(t, fr, frBuf)
		validateTraces(t, tBuf, frBuf)
	})
	t.Run("start(trace)_start(flight)_stop(trace)_stop(flight)", func(t *testing.T) {
		if trace.IsEnabled() {
			t.Skip("skipping because trace is already enabled")
		}
		frBuf := new(bytes.Buffer)
		tBuf := new(bytes.Buffer)
		startTrace(t, tBuf)
		defer trace.Stop()
		fr := startFlightRecorder(t)
		defer fr.Stop()
		stopTrace()
		stopFlightRecorder(t, fr, frBuf)
		validateTraces(t, tBuf, frBuf)
	})
	t.Run("start(flight)_stop(flight)_start(trace)_stop(trace)", func(t *testing.T) {
		if trace.IsEnabled() {
			t.Skip("skipping because trace is already enabled")
		}
		frBuf := new(bytes.Buffer)
		tBuf := new(bytes.Buffer)
		fr := startFlightRecorder(t)
		defer fr.Stop()
		stopFlightRecorder(t, fr, frBuf)
		startTrace(t, tBuf)
		defer trace.Stop()
		stopTrace()
		validateTraces(t, tBuf, frBuf)
	})
	t.Run("start(flight)_stop(flight)_start(trace)_stop(trace)", func(t *testing.T) {
		if trace.IsEnabled() {
			t.Skip("skipping because trace is already enabled")
		}
		frBuf := new(bytes.Buffer)
		tBuf := new(bytes.Buffer)
		fr := startFlightRecorder(t)
		defer fr.Stop()
		stopFlightRecorder(t, fr, frBuf)
		startTrace(t, tBuf)
		defer trace.Stop()
		stopTrace()
		validateTraces(t, tBuf, frBuf)
	})
	t.Run("start(flight)_start(trace)_stop(flight)_stop(trace)", func(t *testing.T) {
		if trace.IsEnabled() {
			t.Skip("skipping because trace is already enabled")
		}
		frBuf := new(bytes.Buffer)
		tBuf := new(bytes.Buffer)
		fr := startFlightRecorder(t)
		defer fr.Stop()
		startTrace(t, tBuf)
		defer trace.Stop()
		stopFlightRecorder(t, fr, frBuf)
		stopTrace()
		validateTraces(t, tBuf, frBuf)
	})
}
