package ptytest_test

import (
	"fmt"
	"runtime"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/coder/coder/v2/cli/clibase"
	"github.com/coder/coder/v2/pty/ptytest"
	"github.com/coder/coder/v2/testutil"
)

func TestPtytest(t *testing.T) {
	t.Parallel()
	t.Run("Echo", func(t *testing.T) {
		t.Parallel()
		pty := ptytest.New(t)
		pty.Output().Write([]byte("write"))
		pty.ExpectMatch("write")
		pty.WriteLine("read")
	})

	t.Run("ReadLine", func(t *testing.T) {
		t.Parallel()
		if runtime.GOOS == "windows" {
			t.Skip("ReadLine is glitchy on windows when it comes to the final line of output it seems")
		}

		ctx := testutil.Context(t, testutil.WaitLong)
		pty := ptytest.New(t)

		// The PTY expands these to \r\n (even on linux).
		pty.Output().Write([]byte("line 1\nline 2\nline 3\nline 4\nline 5"))
		require.Equal(t, "line 1", pty.ReadLine(ctx))
		require.Equal(t, "line 2", pty.ReadLine(ctx))
		require.Equal(t, "line 3", pty.ReadLine(ctx))
		require.Equal(t, "line 4", pty.ReadLine(ctx))
		require.Equal(t, "line 5", pty.ExpectMatch("5"))
	})

	// See https://github.com/coder/coder/issues/2122 for the motivation
	// behind this test.
	t.Run("Ptytest should not hang when output is not consumed", func(t *testing.T) {
		t.Parallel()

		tests := []struct {
			name          string
			output        string
			isPlatformBug bool
		}{
			{name: "1024 is safe (does not exceed macOS buffer)", output: strings.Repeat(".", 1024)},
			{name: "1025 exceeds macOS buffer (must not hang)", output: strings.Repeat(".", 1025)},
			{name: "10241 large output", output: strings.Repeat(".", 10241)}, // 1024 * 10 + 1
		}
		for _, tt := range tests {
			tt := tt
			// nolint:paralleltest // Avoid parallel test to more easily identify the issue.
			t.Run(tt.name, func(t *testing.T) {
				cmd := &clibase.Cmd{
					Use: "test",
					Handler: func(inv *clibase.Invocation) error {
						fmt.Fprint(inv.Stdout, tt.output)
						return nil
					},
				}

				inv := cmd.Invoke()
				pty := ptytest.New(t)
				pty.Attach(inv)
				err := inv.Run()
				require.NoError(t, err)
			})
		}
	})
}
