package qlog

import (
	"bytes"
	"encoding/json"
	"io"
	"testing"
	"time"

	"github.com/quic-go/quic-go"
	"github.com/quic-go/quic-go/internal/synctest"
	"github.com/quic-go/quic-go/qlogwriter"

	"github.com/stretchr/testify/require"
)

type nopWriteCloserImpl struct{ io.Writer }

func (nopWriteCloserImpl) Close() error { return nil }

func nopWriteCloser(w io.Writer) io.WriteCloser {
	return &nopWriteCloserImpl{Writer: w}
}

func testEventEncoding(t *testing.T, ev qlogwriter.Event) (string, map[string]any) {
	t.Helper()
	var buf bytes.Buffer

	synctest.Test(t, func(t *testing.T) {
		tr := qlogwriter.NewConnectionFileSeq(
			nopWriteCloser(&buf),
			true,
			quic.ConnectionIDFromBytes([]byte{1, 2, 3, 4}),
			[]string{"http3"},
		)
		go tr.Run()
		producer := tr.AddProducer()

		synctest.Wait()
		time.Sleep(42 * time.Second)

		producer.RecordEvent(ev)
		producer.Close()
	})

	return decode(t, buf.String())
}

func decode(t *testing.T, data string) (string, map[string]any) {
	t.Helper()

	var result map[string]any

	lines := bytes.Split([]byte(data), []byte{'\n'})
	require.Len(t, lines, 3) // the first line is the trace header, the second line is the event, the third line is empty
	require.Empty(t, lines[2])
	require.Equal(t, qlogwriter.RecordSeparator, lines[1][0], "expected record separator at start of line")
	require.NoError(t, json.Unmarshal(lines[1][1:], &result))
	require.Equal(t, 42*time.Second, time.Duration(result["time"].(float64)*1e6)*time.Nanosecond)

	return result["name"].(string), result["data"].(map[string]any)
}

func TestFrameParsedEvent(t *testing.T) {
	name, ev := testEventEncoding(t, FrameParsed{
		StreamID: quic.StreamID(4),
		Raw: RawInfo{
			Length:        1500,
			PayloadLength: 100,
		},
		Frame: Frame{Frame: &DataFrame{}},
	})

	require.Equal(t, "http3:frame_parsed", name)
	require.Equal(t, float64(4), ev["stream_id"])
	require.NotContains(t, ev, "name")
	require.Contains(t, ev, "frame")
}

func TestFrameCreatedEvent(t *testing.T) {
	name, ev := testEventEncoding(t, FrameCreated{
		StreamID: quic.StreamID(8),
		Raw: RawInfo{
			PayloadLength: 200,
		},
		Frame: Frame{Frame: &HeadersFrame{
			HeaderFields: []HeaderField{
				{Name: ":status", Value: "200"},
				{Name: "content-type", Value: "text/html"},
			},
		}},
	})

	require.Equal(t, "http3:frame_created", name)
	require.Equal(t, float64(8), ev["stream_id"])
	require.NotContains(t, ev, "name")
	require.Contains(t, ev, "frame")
}
