// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause

//go:build !plan9

// Package fakes contains mocks used for testing 'kubectl exec' session
// recording functionality.
package fakes

import (
	"bytes"
	"encoding/json"
	"fmt"
	"math/rand"
	"net"
	"sync"
	"testing"
	"time"

	"tailscale.com/sessionrecording"
	"tailscale.com/tstime"
)

func New(conn net.Conn, wb bytes.Buffer, rb bytes.Buffer, closed bool) net.Conn {
	return &TestConn{
		Conn:     conn,
		writeBuf: wb,
		readBuf:  rb,
		closed:   closed,
	}
}

type TestConn struct {
	net.Conn
	// writeBuf contains whatever was send to the conn via Write.
	writeBuf bytes.Buffer
	// readBuf contains whatever was sent to the conn via Read.
	readBuf      bytes.Buffer
	sync.RWMutex // protects the following
	closed       bool
}

var _ net.Conn = &TestConn{}

func (tc *TestConn) Read(b []byte) (int, error) {
	return tc.readBuf.Read(b)
}

func (tc *TestConn) Write(b []byte) (int, error) {
	return tc.writeBuf.Write(b)
}

func (tc *TestConn) Close() error {
	tc.Lock()
	defer tc.Unlock()
	tc.closed = true
	return nil
}

func (tc *TestConn) IsClosed() bool {
	tc.Lock()
	defer tc.Unlock()
	return tc.closed
}

func (tc *TestConn) WriteBufBytes() []byte {
	return tc.writeBuf.Bytes()
}

func (tc *TestConn) ResetReadBuf() {
	tc.readBuf.Reset()
}

func (tc *TestConn) WriteReadBufBytes(b []byte) error {
	_, err := tc.readBuf.Write(b)
	return err
}

type TestSessionRecorder struct {
	// buf holds data that was sent to the session recorder.
	buf bytes.Buffer
}

func (t *TestSessionRecorder) Write(b []byte) (int, error) {
	return t.buf.Write(b)
}

func (t *TestSessionRecorder) Close() error {
	t.buf.Reset()
	return nil
}

func (t *TestSessionRecorder) Bytes() []byte {
	return t.buf.Bytes()
}

func CastLine(t *testing.T, p []byte, clock tstime.Clock) []byte {
	t.Helper()
	j, err := json.Marshal([]any{
		clock.Now().Sub(clock.Now()).Seconds(),
		"o",
		string(p),
	})
	if err != nil {
		t.Fatalf("error marshalling cast line: %v", err)
	}
	return append(j, '\n')
}

func AsciinemaCastResizeMsg(t *testing.T, width, height int) []byte {
	msg := fmt.Sprintf(`[0,"r","%dx%d"]`, height, width)

	return append([]byte(msg), '\n')
}

func AsciinemaCastHeaderMsg(t *testing.T, width, height int) []byte {
	t.Helper()
	ch := sessionrecording.CastHeader{
		Width:  width,
		Height: height,
	}
	bs, err := json.Marshal(ch)
	if err != nil {
		t.Fatalf("error marshalling CastHeader: %v", err)
	}
	return append(bs, '\n')
}

func RandomBytes(t *testing.T) [][]byte {
	t.Helper()
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	n := r.Intn(4096)
	b := make([]byte, n)
	t.Logf("RandomBytes: generating byte slice of length %d", n)
	_, err := r.Read(b)
	if err != nil {
		t.Fatalf("error generating random byte slice: %v", err)
	}
	if len(b) < 2 {
		return [][]byte{b}
	}
	split := r.Intn(len(b) - 1)
	return [][]byte{b[:split], b[split:]}
}
