// Copyright IBM Corp. 2020, 2026
// SPDX-License-Identifier: BUSL-1.1

package convert

import (
	"bytes"
	"context"
	"errors"
	"io"
	"os"
	"testing"
	"time"

	"github.com/hashicorp/boundary/internal/bsr"
	sshv1 "github.com/hashicorp/boundary/internal/bsr/gen/ssh/v1"
	"github.com/hashicorp/boundary/internal/bsr/internal/fstest"
	"github.com/hashicorp/boundary/internal/bsr/ssh"
	"github.com/stretchr/testify/require"
)

func Test_sshChannelToAsciicast(t *testing.T) {
	ctx := context.Background()

	ts := time.Date(2023, time.March, 16, 10, 47, 3, 14, time.UTC)
	newW := func() io.ReadWriteSeeker {
		f, err := os.CreateTemp("", "*.asciicast")
		require.NoError(t, err)
		t.Cleanup(func() {
			os.Remove(f.Name())
		})
		return f
	}
	newScanner := func(chunks ...bsr.Chunk) *bsr.ChunkScanner {
		buf, err := fstest.NewTempBuffer()
		require.NoError(t, err)
		buf.Write(bsr.Magic.Bytes())
		enc, err := bsr.NewChunkEncoder(ctx, buf, bsr.NoCompression, bsr.NoEncryption)
		require.NoError(t, err)

		for _, c := range chunks {
			_, err := enc.Encode(ctx, c)
			require.NoError(t, err)
		}
		s, err := bsr.NewChunkScanner(ctx, bytes.NewBuffer(buf.Bytes()))
		require.NoError(t, err)
		return s
	}
	cases := []struct {
		name           string
		requestScanner *bsr.ChunkScanner
		messageScanner *bsr.ChunkScanner
		w              io.ReadWriteSeeker
		opts           []Option
		want           []byte
		wantErr        error
	}{
		{
			"no-pty-no-env-no-messages",
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newW(),
			nil,
			[]byte(`{"version":2,"width":80,"height":24,"timestamp":1678963623,"env":{"SHELL":"/bin/bash","TERM":"xterm"}}
`),
			nil,
		},
		{
			"pty-no-env-no-messages",
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&ssh.PtyRequest{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Microsecond)),
						Type:      ssh.PtyReqChunkType,
					},
					PtyRequest: &sshv1.PtyRequest{
						RequestType:             ssh.PtyRequestType,
						WantReply:               false,
						TermEnvVar:              "kitty",
						TerminalWidthCharacters: 160,
						TerminalHeightRows:      200,
						TerminalWidthPixels:     0,
						TerminalHeightPixels:    0,
						EncodedTerminalMode:     []byte{},
					},
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newW(),
			nil,
			[]byte(`{"version":2,"width":160,"height":200,"timestamp":1678963623,"env":{"SHELL":"/bin/bash","TERM":"kitty"}}
`),
			nil,
		},
		{
			"tiny-pty-no-env-no-messages",
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&ssh.PtyRequest{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Microsecond)),
						Type:      ssh.PtyReqChunkType,
					},
					PtyRequest: &sshv1.PtyRequest{
						RequestType:             ssh.PtyRequestType,
						WantReply:               false,
						TermEnvVar:              "kitty",
						TerminalWidthCharacters: 2,
						TerminalHeightRows:      2,
						TerminalWidthPixels:     0,
						TerminalHeightPixels:    0,
						EncodedTerminalMode:     []byte{},
					},
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newW(),
			nil,
			[]byte(`{"version":2,"width":2,"height":2,"timestamp":1678963623,"env":{"SHELL":"/bin/bash","TERM":"kitty"}}
`),
			nil,
		},
		{
			"tiny-pty-no-env-no-messages-min-width-min-height",
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&ssh.PtyRequest{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Microsecond)),
						Type:      ssh.PtyReqChunkType,
					},
					PtyRequest: &sshv1.PtyRequest{
						RequestType:             ssh.PtyRequestType,
						WantReply:               false,
						TermEnvVar:              "kitty",
						TerminalWidthCharacters: 2,
						TerminalHeightRows:      2,
						TerminalWidthPixels:     0,
						TerminalHeightPixels:    0,
						EncodedTerminalMode:     []byte{},
					},
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newW(),
			[]Option{WithMinWidth(60), WithMinHeight(26)},
			[]byte(`{"version":2,"width":60,"height":26,"timestamp":1678963623,"env":{"SHELL":"/bin/bash","TERM":"kitty"}}
`),
			nil,
		},
		{
			"pty-no-env-no-messages-window-change",
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&ssh.PtyRequest{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Microsecond)),
						Type:      ssh.PtyReqChunkType,
					},
					PtyRequest: &sshv1.PtyRequest{
						RequestType:             ssh.PtyRequestType,
						WantReply:               false,
						TermEnvVar:              "kitty",
						TerminalWidthCharacters: 160,
						TerminalHeightRows:      200,
						TerminalWidthPixels:     0,
						TerminalHeightPixels:    0,
						EncodedTerminalMode:     []byte{},
					},
				},
				&ssh.WindowChangeRequest{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Microsecond)),
						Type:      ssh.WindowChangeReqChunkType,
					},
					WindowChangeRequest: &sshv1.WindowChangeRequest{
						RequestType:          ssh.WindowChangeRequestType,
						WantReply:            false,
						TerminalWidthColumns: 220,
						TerminalHeightRows:   100,
					},
				},
				&ssh.WindowChangeRequest{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Microsecond)),
						Type:      ssh.WindowChangeReqChunkType,
					},
					WindowChangeRequest: &sshv1.WindowChangeRequest{
						RequestType:          ssh.WindowChangeRequestType,
						WantReply:            false,
						TerminalWidthColumns: 100,
						TerminalHeightRows:   500,
					},
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newW(),
			nil,
			[]byte(`{"version":2,"width":220,"height":500,"timestamp":1678963623,"env":{"SHELL":"/bin/bash","TERM":"kitty"}}
`),
			nil,
		},
		{
			"pty-env-no-messages",
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&ssh.PtyRequest{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Microsecond)),
						Type:      ssh.PtyReqChunkType,
					},
					PtyRequest: &sshv1.PtyRequest{
						RequestType:             ssh.PtyRequestType,
						WantReply:               false,
						TermEnvVar:              "kitty",
						TerminalWidthCharacters: 160,
						TerminalHeightRows:      200,
						TerminalWidthPixels:     0,
						TerminalHeightPixels:    0,
						EncodedTerminalMode:     []byte{},
					},
				},
				&ssh.EnvRequest{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Microsecond + time.Nanosecond)),
						Type:      ssh.EnvReqChunkType,
					},
					EnvRequest: &sshv1.EnvRequest{
						RequestType:   ssh.EnvRequestType,
						WantReply:     false,
						VariableName:  "SHELL",
						VariableValue: "/bin/fish",
					},
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newW(),
			nil,
			[]byte(`{"version":2,"width":160,"height":200,"timestamp":1678963623,"env":{"SHELL":"/bin/fish","TERM":"kitty"}}
`),
			nil,
		},
		{
			"no-pty-no-env-messages",
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&ssh.DataChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Microsecond)),
						Type:      ssh.DataChunkType,
					},
					Data: []byte("ls -lash"),
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(2 * time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newW(),
			nil,
			[]byte(`{"version":2,"width":80,"height":24,"timestamp":1678963623,"env":{"SHELL":"/bin/bash","TERM":"xterm"}}
[0.000001,"o","ls -lash"]
`),
			nil,
		},
		{
			"no-pty-no-env-multiple-messages",
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&ssh.DataChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Microsecond)),
						Type:      ssh.DataChunkType,
					},
					Data: []byte("ls -lash"),
				},
				&ssh.DataChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(2 * time.Microsecond)),
						Type:      ssh.DataChunkType,
					},
					Data: []byte("foo\r\n"),
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(2 * time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newW(),
			nil,
			[]byte(`{"version":2,"width":80,"height":24,"timestamp":1678963623,"env":{"SHELL":"/bin/bash","TERM":"xterm"}}
[0.000001,"o","ls -lash"]
[0.000002,"o","foo\r\n"]
`),
			nil,
		},
		{
			"nil-requestScanner",
			nil,
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newW(),
			nil,
			nil,
			errors.New("convert.sshChannelToAsciicast: missing request scanner: invalid parameter"),
		},
		{
			"nil-messageScanner",
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			nil,
			newW(),
			nil,
			nil,
			errors.New("convert.sshChannelToAsciicast: missing message scanner: invalid parameter"),
		},
		{
			"nil-w",
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			nil,
			nil,
			nil,
			errors.New("convert.sshChannelToAsciicast: missing read write seeker: invalid parameter"),
		},
		{
			"data-before-header",
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newScanner(
				&ssh.DataChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Microsecond)),
						Type:      ssh.DataChunkType,
					},
					Data: []byte("ls -lash"),
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(2 * time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newW(),
			nil,
			nil,
			errors.New("convert.sshChannelToAsciicast: bsr.ChunkWalk: data chunk before header: malformed bsr data file"),
		},
		{
			"multiple-header",
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newScanner(
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&bsr.HeaderChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts),
						Type:      bsr.ChunkHeader,
					},
					Compression: bsr.NoCompression,
					Encryption:  bsr.NoEncryption,
					SessionId:   "sess_123456789",
				},
				&bsr.EndChunk{
					BaseChunk: &bsr.BaseChunk{
						Protocol:  ssh.Protocol,
						Direction: bsr.Inbound,
						Timestamp: bsr.NewTimestamp(ts.Add(time.Second)),
						Type:      bsr.ChunkEnd,
					},
				},
			),
			newW(),
			nil,
			nil,
			errors.New("convert.sshChannelToAsciicast: bsr.ChunkWalk: multiple header chunks: malformed bsr data file"),
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			r, err := sshChannelToAsciicast(
				ctx,
				tc.requestScanner,
				tc.messageScanner,
				tc.w,
				tc.opts...,
			)
			if tc.wantErr != nil {
				require.EqualError(t, err, tc.wantErr.Error())
				return
			}
			require.NoError(t, err)
			got, err := io.ReadAll(r)
			require.NoError(t, err)
			require.Equal(t, string(tc.want), string(got))

			err = r.Close()
			require.NoError(t, err)
		})
	}
}
