package qlog

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

	"github.com/quic-go/quic-go/internal/protocol"
	"github.com/quic-go/quic-go/internal/qerr"
	"github.com/quic-go/quic-go/qlogwriter/jsontext"

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

func check(t *testing.T, f any, expected map[string]any) {
	var buf bytes.Buffer
	enc := jsontext.NewEncoder(&buf)
	require.NoError(t, (Frame{Frame: f}).Encode(enc))
	data := buf.Bytes()
	require.True(t, json.Valid(data))
	checkEncoding(t, data, expected)
}

func TestPingFrame(t *testing.T) {
	check(t, &PingFrame{}, map[string]any{"frame_type": "ping"})
}

func TestAckFrame(t *testing.T) {
	tests := []struct {
		name     string
		frame    *AckFrame
		expected map[string]any
	}{
		{
			name: "with delay and single packet range",
			frame: &AckFrame{
				DelayTime: 86 * time.Millisecond,
				AckRanges: []AckRange{{Smallest: 120, Largest: 120}},
			},
			expected: map[string]any{
				"frame_type":   "ack",
				"ack_delay":    86,
				"acked_ranges": [][]float64{{120}},
			},
		},
		{
			name: "without delay",
			frame: &AckFrame{
				AckRanges: []AckRange{{Smallest: 120, Largest: 120}},
			},
			expected: map[string]any{
				"frame_type":   "ack",
				"acked_ranges": [][]float64{{120}},
			},
		},
		{
			name: "with ECN counts",
			frame: &AckFrame{
				AckRanges: []AckRange{{Smallest: 120, Largest: 120}},
				ECT0:      10,
				ECT1:      100,
				ECNCE:     1000,
			},
			expected: map[string]any{
				"frame_type":   "ack",
				"acked_ranges": [][]float64{{120}},
				"ect0":         10,
				"ect1":         100,
				"ce":           1000,
			},
		},
		{
			name: "with multiple ranges",
			frame: &AckFrame{
				DelayTime: 86 * time.Millisecond,
				AckRanges: []AckRange{
					{Smallest: 5, Largest: 50},
					{Smallest: 100, Largest: 120},
				},
			},
			expected: map[string]any{
				"frame_type": "ack",
				"ack_delay":  86,
				"acked_ranges": [][]float64{
					{5, 50},
					{100, 120},
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			check(t, tt.frame, tt.expected)
		})
	}
}

func TestResetStreamFrame(t *testing.T) {
	check(t,
		&ResetStreamFrame{
			StreamID:  987,
			FinalSize: 1234,
			ErrorCode: 42,
		},
		map[string]any{
			"frame_type": "reset_stream",
			"stream_id":  987,
			"error_code": 42,
			"final_size": 1234,
		},
	)
}

func TestResetStreamAtFrame(t *testing.T) {
	check(t,
		&ResetStreamFrame{
			StreamID:     987,
			FinalSize:    1234,
			ErrorCode:    42,
			ReliableSize: 999,
		},
		map[string]any{
			"frame_type":    "reset_stream_at",
			"stream_id":     987,
			"error_code":    42,
			"final_size":    1234,
			"reliable_size": 999,
		},
	)
}

func TestAckFrequencyFrame(t *testing.T) {
	check(t,
		&AckFrequencyFrame{
			SequenceNumber:        1337,
			AckElicitingThreshold: 123,
			RequestMaxAckDelay:    42 * time.Millisecond,
			ReorderingThreshold:   1234,
		},
		map[string]any{
			"frame_type":              "ack_frequency",
			"sequence_number":         1337,
			"ack_eliciting_threshold": 123,
			"request_max_ack_delay":   42,
			"reordering_threshold":    1234,
		},
	)
}

func TestImmediateAckFrame(t *testing.T) {
	check(t,
		&ImmediateAckFrame{},
		map[string]any{
			"frame_type": "immediate_ack",
		},
	)
}

func TestStopSendingFrame(t *testing.T) {
	check(t,
		&StopSendingFrame{StreamID: 987, ErrorCode: 42},
		map[string]any{
			"frame_type": "stop_sending",
			"stream_id":  987,
			"error_code": 42,
		},
	)
}

func TestCryptoFrame(t *testing.T) {
	check(t,
		&CryptoFrame{Offset: 1337, Length: 6},
		map[string]any{
			"frame_type": "crypto",
			"offset":     1337,
			"length":     6,
		},
	)
}

func TestNewTokenFrame(t *testing.T) {
	check(t,
		&NewTokenFrame{Token: []byte{0xde, 0xad, 0xbe, 0xef}},
		map[string]any{
			"frame_type": "new_token",
			"token":      map[string]any{"data": "deadbeef"},
		},
	)
}

func TestStreamFrame(t *testing.T) {
	tests := []struct {
		name     string
		frame    *StreamFrame
		expected map[string]any
	}{
		{
			name: "with FIN",
			frame: &StreamFrame{
				StreamID: 42,
				Offset:   1337,
				Fin:      true,
				Length:   9876,
			},
			expected: map[string]any{
				"frame_type": "stream",
				"stream_id":  42,
				"offset":     1337,
				"fin":        true,
				"length":     9876,
			},
		},
		{
			name: "without FIN",
			frame: &StreamFrame{
				StreamID: 42,
				Offset:   1337,
				Length:   3,
			},
			expected: map[string]any{
				"frame_type": "stream",
				"stream_id":  42,
				"offset":     1337,
				"length":     3,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			check(t, tt.frame, tt.expected)
		})
	}
}

func TestMaxDataFrame(t *testing.T) {
	check(t,
		&MaxDataFrame{MaximumData: 1337},
		map[string]any{
			"frame_type": "max_data",
			"maximum":    1337,
		},
	)
}

func TestMaxStreamDataFrame(t *testing.T) {
	check(t,
		&MaxStreamDataFrame{StreamID: 1234, MaximumStreamData: 1337},
		map[string]any{
			"frame_type": "max_stream_data",
			"stream_id":  1234,
			"maximum":    1337,
		},
	)
}

func TestMaxStreamsFrame(t *testing.T) {
	check(t,
		&MaxStreamsFrame{
			Type:         protocol.StreamTypeBidi,
			MaxStreamNum: 42,
		},
		map[string]any{
			"frame_type":  "max_streams",
			"stream_type": "bidirectional",
			"maximum":     42,
		},
	)
}

func TestDataBlockedFrame(t *testing.T) {
	check(t,
		&DataBlockedFrame{MaximumData: 1337},
		map[string]any{
			"frame_type": "data_blocked",
			"limit":      1337,
		},
	)
}

func TestStreamDataBlockedFrame(t *testing.T) {
	check(t,
		&StreamDataBlockedFrame{
			StreamID:          42,
			MaximumStreamData: 1337,
		},
		map[string]any{
			"frame_type": "stream_data_blocked",
			"stream_id":  42,
			"limit":      1337,
		},
	)
}

func TestStreamsBlockedFrame(t *testing.T) {
	check(t,
		&StreamsBlockedFrame{
			Type:        protocol.StreamTypeUni,
			StreamLimit: 123,
		},
		map[string]any{
			"frame_type":  "streams_blocked",
			"stream_type": "unidirectional",
			"limit":       123,
		},
	)
}

func TestNewConnectionIDFrame(t *testing.T) {
	check(t,
		&NewConnectionIDFrame{
			SequenceNumber:      42,
			RetirePriorTo:       24,
			ConnectionID:        protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}),
			StatelessResetToken: protocol.StatelessResetToken{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf},
		},
		map[string]any{
			"frame_type":            "new_connection_id",
			"sequence_number":       42,
			"retire_prior_to":       24,
			"length":                4,
			"connection_id":         "deadbeef",
			"stateless_reset_token": "000102030405060708090a0b0c0d0e0f",
		},
	)
}

func TestRetireConnectionIDFrame(t *testing.T) {
	check(t,
		&RetireConnectionIDFrame{SequenceNumber: 1337},
		map[string]any{
			"frame_type":      "retire_connection_id",
			"sequence_number": 1337,
		},
	)
}

func TestPathChallengeFrame(t *testing.T) {
	check(t,
		&PathChallengeFrame{Data: [8]byte{0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xc0, 0x01}},
		map[string]any{
			"frame_type": "path_challenge",
			"data":       "deadbeefcafec001",
		},
	)
}

func TestPathResponseFrame(t *testing.T) {
	check(t,
		&PathResponseFrame{Data: [8]byte{0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xc0, 0x01}},
		map[string]any{
			"frame_type": "path_response",
			"data":       "deadbeefcafec001",
		},
	)
}

func TestConnectionCloseFrame(t *testing.T) {
	tests := []struct {
		name     string
		frame    *ConnectionCloseFrame
		expected map[string]any
	}{
		{
			name: "application error code",
			frame: &ConnectionCloseFrame{
				IsApplicationError: true,
				ErrorCode:          1337,
				ReasonPhrase:       "lorem ipsum",
			},
			expected: map[string]any{
				"frame_type":     "connection_close",
				"error_space":    "application",
				"error_code":     1337,
				"raw_error_code": 1337,
				"reason":         "lorem ipsum",
			},
		},
		{
			name: "transport error code",
			frame: &ConnectionCloseFrame{
				ErrorCode:    uint64(qerr.FlowControlError),
				ReasonPhrase: "lorem ipsum",
			},
			expected: map[string]any{
				"frame_type":     "connection_close",
				"error_space":    "transport",
				"error_code":     "flow_control_error",
				"raw_error_code": int(qerr.FlowControlError),
				"reason":         "lorem ipsum",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			check(t, tt.frame, tt.expected)
		})
	}
}

func TestHandshakeDoneFrame(t *testing.T) {
	check(t,
		&HandshakeDoneFrame{},
		map[string]any{
			"frame_type": "handshake_done",
		},
	)
}

func TestDatagramFrame(t *testing.T) {
	check(t,
		&DatagramFrame{Length: 1337},
		map[string]any{
			"frame_type": "datagram",
			"length":     1337,
		},
	)
}
