package cmdlog_test

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"testing"
	"time"

	"github.com/creack/pty"

	"oss.terrastruct.com/util-go/assert"
	"oss.terrastruct.com/util-go/cmdlog"
	"oss.terrastruct.com/util-go/xos"
)

func TestLogger(t *testing.T) {
	t.Parallel()

	var tca = []struct {
		name string
		run  func(t *testing.T, ctx context.Context, env *xos.Env)
	}{
		{
			name: "COLOR=1",
			run: func(t *testing.T, ctx context.Context, env *xos.Env) {
				b := &bytes.Buffer{}
				env.Setenv("COLOR", "1")
				l := cmdlog.New(env, b)

				testLogger(l)

				t.Log(b.String())
				assert.TestdataJSON(t, b.String())
			},
		},
		{
			name: "COLOR=",
			run: func(t *testing.T, ctx context.Context, env *xos.Env) {
				b := &bytes.Buffer{}
				l := cmdlog.New(env, b)

				testLogger(l)

				t.Log(b.String())
				assert.TestdataJSON(t, b.String())
			},
		},
		{
			name: "tty",
			run: func(t *testing.T, ctx context.Context, env *xos.Env) {
				ptmx, tty, err := pty.Open()
				if err != nil {
					t.Fatalf("failed to open pty: %v", err)
				}
				defer assert.Close(t, ptmx)
				defer assert.Close(t, tty)

				l := cmdlog.New(env, tty)
				testLogger(l)

				timer := time.AfterFunc(time.Second*5, func() {
					// For some reason ptmx.SetDeadline() does not work.
					// tty has to be closed for a read on ptmx to unblock.
					tty.Close()
					t.Error("read took too long, update expLen")
				})
				defer timer.Stop()
				// If the expected output changes, increase this to 9999, rerun and then update
				// for new length.
				const expLen = 415
				out, err := io.ReadAll(io.LimitReader(ptmx, expLen))
				if err != nil {
					t.Fatalf("failed to read log output: %v", err)
				}
				t.Log(len(out), string(out))
				assert.TestdataJSON(t, string(out))
			},
		},
		{
			name: "testing.TB",
			run: func(t *testing.T, ctx context.Context, env *xos.Env) {
				ft := &fakeTB{
					TB: t,
					logf: func(f string, v ...interface{}) {
						t.Helper()
						assert.String(t, "info: what's up\n", fmt.Sprintf(f, v...))
					},
				}

				env.Setenv("COLOR", "0")
				l := cmdlog.NewTB(env, ft)
				l.Info.Printf("what's up")
			},
		},
		{
			name: "WithPrefix",
			run: func(t *testing.T, ctx context.Context, env *xos.Env) {
				b := &bytes.Buffer{}
				env.Setenv("COLOR", "1")
				l := cmdlog.New(env, b)

				l2 := l.WithCCPrefix("lochness")
				if l2 == l {
					t.Fatalf("expected l and l2 to be different loggers")
				}
				l2 = l2.WithCCPrefix("imgbundler")
				l2 = l2.WithCCPrefix("cache")

				testLogger(l)
				testLogger(l2)

				t.Log(b.String())
				assert.TestdataJSON(t, b.String())
			},
		},
		{
			name: "multiline",
			run: func(t *testing.T, ctx context.Context, env *xos.Env) {
				b := &bytes.Buffer{}
				env.Setenv("COLOR", "1")
				l := cmdlog.New(env, b)

				l.NoLevel.Print("")
				l.SetTS(true)
				l.NoLevel.Print("")
				l.SetTS(false)

				l2 := l.WithCCPrefix("lochness")
				l2 = l2.WithCCPrefix("imgbundler")
				l2 = l2.WithCCPrefix("cache")

				l2.Warn.Print(``)
				l2.Warn.Print("\n\n\n")
				l2.SetTS(true)
				l2.Warn.Printf(`yes %d
yes %d`, 3, 4)

				t.Log(b.String())
				assert.TestdataJSON(t, b.String())
			},
		},
	}

	ctx := context.Background()
	for _, tc := range tca {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			ctx, cancel := context.WithCancel(ctx)
			defer cancel()

			env := xos.NewEnv(nil)
			tc.run(t, ctx, env)
		})
	}
}

func testLogger(l *cmdlog.Logger) {
	l.NoLevel.Println("Somehow, the world always affects you more than you affect it.")

	l.SetDebug(true)
	l.Debug.Println("Man is a rational animal who always loses his temper when he is called upon.")

	l.SetDebug(false)
	l.Debug.Println("You can never trust a woman; she may be true to you.")

	l.SetTS(true)
	l.Success.Println("An alcoholic is someone you don't like who drinks as much as you do.")
	l.Info.Println("There once was this swami who lived above a delicatessan.")

	l.SetTSFormat(time.UnixDate)
	l.Warn.Println("Telephone books are like dictionaries -- if you know the answer before.")

	l.SetTS(false)
	l.Error.Println("Nothing can be done in one trip.")
	l.Error.Println(`Good day to let down old friends who need help.
I believe in getting into hot water; it keeps you clean.`)
}

type fakeTB struct {
	testing.TB
	logf func(string, ...interface{})
}

func (ftb *fakeTB) Logf(f string, v ...interface{}) {
	ftb.TB.Helper()
	ftb.logf(f, v...)
}
