package iostreams

import (
	"bytes"
	"io"
	"os"
	"testing"

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

func Test_HelperFunctions(t *testing.T) {
	// Base ios object that is modified as required
	ios := &IOStreams{
		In:     os.Stdin,
		StdOut: NewColorable(os.Stdout),
		StdErr: NewColorable(os.Stderr),

		IsaTTY:         IsTerminal(os.Stdout),
		IsErrTTY:       IsTerminal(os.Stderr),
		IsInTTY:        IsTerminal(os.Stdin),
		promptDisabled: false,

		pagerCommand: os.Getenv("PAGER"),
	}

	t.Run("InitIOStream()", func(t *testing.T) {
		t.Run("PAGER=", func(t *testing.T) {
			os.Unsetenv("PAGER")

			got := Init()

			assert.Equal(t, ios.In, got.In)
			assert.Equal(t, ios.IsaTTY, got.IsaTTY)
			assert.Equal(t, ios.IsErrTTY, got.IsErrTTY)
			assert.Equal(t, ios.IsInTTY, got.IsInTTY)
			assert.Equal(t, ios.promptDisabled, got.promptDisabled)
			assert.Equal(t, ios.pagerCommand, got.pagerCommand)
		})
		t.Run("GLAB_PAGER=", func(t *testing.T) {
			t.Setenv("GLAB_PAGER", "more")

			got := Init()

			assert.Equal(t, ios.In, got.In)
			assert.Equal(t, ios.IsaTTY, got.IsaTTY)
			assert.Equal(t, ios.IsErrTTY, got.IsErrTTY)
			assert.Equal(t, ios.IsInTTY, got.IsInTTY)
			assert.Equal(t, ios.promptDisabled, got.promptDisabled)
			assert.Equal(t, "more", got.pagerCommand)
		})
	})

	t.Run("IsOutputTTY()", func(t *testing.T) {
		t.Run("true", func(t *testing.T) {
			ios := *ios

			ios.IsaTTY = true
			ios.IsErrTTY = true

			got := ios.IsOutputTTY()
			assert.True(t, got)
		})
		t.Run("false", func(t *testing.T) {
			t.Run("IsaTTY=false", func(t *testing.T) {
				ios := *ios

				ios.IsaTTY = false
				ios.IsErrTTY = true

				got := ios.IsOutputTTY()
				assert.False(t, got)
			})
			t.Run("IsErrTTY=false", func(t *testing.T) {
				ios := *ios

				ios.IsaTTY = true
				ios.IsErrTTY = false

				got := ios.IsOutputTTY()
				assert.False(t, got)
			})
		})
	})

	t.Run("SetPager()", func(t *testing.T) {
		t.Run("more", func(t *testing.T) {
			ios := *ios
			ios.SetPager("more")
			assert.Equal(t, "more", ios.pagerCommand)
		})
	})

	t.Run("PromptEnabled()", func(t *testing.T) {
		t.Run("true", func(t *testing.T) {
			var got bool
			ios := *ios

			ios.promptDisabled = false
			ios.IsaTTY = true
			ios.IsErrTTY = true

			got = ios.PromptEnabled()
			assert.True(t, got)
		})

		t.Run("false", func(t *testing.T) {
			t.Run("promptDisabled=true", func(t *testing.T) {
				var got bool
				ios := *ios

				ios.promptDisabled = true
				got = ios.PromptEnabled()
				assert.False(t, got)
			})

			t.Run("IsaTTY=false", func(t *testing.T) {
				var got bool
				ios := *ios

				ios.IsaTTY = false
				got = ios.PromptEnabled()
				assert.False(t, got)
			})

			t.Run("IsErrTTY=true", func(t *testing.T) {
				var got bool
				ios := *ios

				ios.IsErrTTY = false
				got = ios.PromptEnabled()
				assert.False(t, got)
			})
		})
	})

	t.Run("ColorEnabled()", func(t *testing.T) {
		t.Run("true", func(t *testing.T) {
			ios := *ios

			ios.IsaTTY = true
			ios.IsErrTTY = true
			checkedNoColor = false
			got := ios.ColorEnabled()
			assert.True(t, got)
		})
		t.Run("false", func(t *testing.T) {
			t.Run("IsaTTY=false", func(t *testing.T) {
				ios := *ios

				ios.IsaTTY = false
				ios.IsErrTTY = true
				checkedNoColor = false
				got := ios.ColorEnabled()
				assert.False(t, got)
			})
			t.Run("IsErrTTY=false", func(t *testing.T) {
				ios := *ios

				ios.IsaTTY = true
				ios.IsErrTTY = false
				checkedNoColor = false
				got := ios.ColorEnabled()
				assert.False(t, got)
			})
		})
	})

	t.Run("SetPrompt()", func(t *testing.T) {
		t.Run("disabled", func(t *testing.T) {
			t.Run("true", func(t *testing.T) {
				ios := *ios
				ios.SetPrompt("true")
				assert.True(t, ios.promptDisabled)
			})
			t.Run("1", func(t *testing.T) {
				ios := *ios
				ios.SetPrompt("1")
				assert.True(t, ios.promptDisabled)
			})
		})
		t.Run("enabled", func(t *testing.T) {
			t.Run("false", func(t *testing.T) {
				ios := *ios
				ios.SetPrompt("false")
				assert.False(t, ios.promptDisabled)
			})
			t.Run("0", func(t *testing.T) {
				ios := *ios
				ios.SetPrompt("0")
				assert.False(t, ios.promptDisabled)
			})
		})
	})

	t.Run("IOTest()", func(t *testing.T) {
		ios, in, out, err := Test()

		assert.Equal(t, ios.In, io.NopCloser(in))
		assert.Equal(t, ios.StdOut, out)
		assert.Equal(t, ios.StdErr, err)

		assert.Equal(t, in, &bytes.Buffer{})
		assert.Equal(t, out, &bytes.Buffer{})
		assert.Equal(t, err, &bytes.Buffer{})
	})
}

func Test_stripControlCharacters(t *testing.T) {
	type args struct {
		badString string
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{
			name: "With moving 2 lines up",
			args: args{
				badString: "echo evil!" + //
					"exit 0" + //
					"[2Aecho Hello World!",
			},
			want: "echo evil!exit 0^[[2Aecho Hello World!",
		},
		{
			name: "With obfuscating characters",
			args: args{
				badString: "echo evil!" + //
					"exit 0" + //
					"[2;0;0;5;3;2;Aecho Hello World!",
			},
			want: "echo evil!exit 0^[[2;0;0;5;3;2;Aecho Hello World!",
		},
		{
			name: "With clearing the screen",
			args: args{
				badString: "echo evil!" + //
					"exit 0" + //
					"[2Lecho Hello World!",
			},
			want: "echo evil!exit 0^[[2Lecho Hello World!",
		},
		{
			name: "control character with empty parameters",
			args: args{
				badString: "[2;;;Aecho Hello World!",
			},
			want: "^[[2;;;Aecho Hello World!",
		},
		{
			name: "With colors",
			args: args{
				badString: "\033[0;30mSome text",
			},
			want: "\x1b[0;30mSome text",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := stripControlCharacters(tt.args.badString)
			assert.Equal(t, got, tt.want)
		})
	}
}
