//go:build !integration

package timestamper

import (
	"bytes"
	"io"
	"strings"
	"testing"
	"time"

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

func setupDummyTime() func() {
	oldNow := now

	pretend, _ := time.Parse(time.RFC3339, "2021-01-01T00:00:00.020010Z")
	now = func() time.Time {
		pretend = pretend.Add(time.Hour)
		return pretend.UTC()
	}

	return func() {
		now = oldNow
	}
}

// nolint:errcheck
func writeLines(w io.Writer) {
	w.Write([]byte("PREFIX This is the beginning of a new line\n"))
	w.Write([]byte("PREFIX This is a split "))
	w.Write([]byte("up "))
	w.Write([]byte("line\n"))
	w.Write([]byte("PREFIX Progress bar: "))

	for i := 0; i < 10; i++ {
		w.Write([]byte(".\r"))
	}

	w.Write([]byte("Done.\r\n"))
	w.Write([]byte("PREFIX Another windows new-line\r\n"))

	w.Write([]byte("PREFIX multiple\nnew\nlines\nin\none\n"))

	w.Write([]byte("\nstart"))
	w.Write([]byte("\nend\n"))

	w.Write([]byte("PREFIX Eat carriages\r\r\r\r\r\r\r\n"))
	w.Write([]byte("PREFIX This is across\ntwo lines\n"))
	w.Write([]byte("PREFIX The end"))
}

func TestWithTimestamps(t *testing.T) {
	// reset local
	local := time.Local
	defer func() {
		time.Local = local
	}()

	for _, tz := range []string{"UTC", "Africa/Cairo", "US/Alaska"} {
		t.Run(tz, func(t *testing.T) {
			// change timezone
			loc, _ := time.LoadLocation(tz)
			time.Local = loc

			buf := new(bytes.Buffer)

			defer setupDummyTime()()

			w := New(buf, StderrType, 255, true)
			writeLines(w)
			w.Close()

			expected := []string{
				"2021-01-01T01:00:00.020010Z ffE PREFIX This is the beginning of a new line\n",
				"2021-01-01T02:00:00.020010Z ffE PREFIX This is a split up line\n",
				"2021-01-01T03:00:00.020010Z ffE PREFIX Progress bar: .\r\n",
				"2021-01-01T04:00:00.020010Z ffE+.\r\n",
				"2021-01-01T05:00:00.020010Z ffE+.\r\n",
				"2021-01-01T06:00:00.020010Z ffE+.\r\n",
				"2021-01-01T07:00:00.020010Z ffE+.\r\n",
				"2021-01-01T08:00:00.020010Z ffE+.\r\n",
				"2021-01-01T09:00:00.020010Z ffE+.\r\n",
				"2021-01-01T10:00:00.020010Z ffE+.\r\n",
				"2021-01-01T11:00:00.020010Z ffE+.\r\n",
				"2021-01-01T12:00:00.020010Z ffE+.\r\n",
				"2021-01-01T13:00:00.020010Z ffE+Done.\r\n",
				"2021-01-01T14:00:00.020010Z ffE PREFIX Another windows new-line\r\n",
				"2021-01-01T15:00:00.020010Z ffE PREFIX multiple\n",
				"2021-01-01T16:00:00.020010Z ffE new\n",
				"2021-01-01T17:00:00.020010Z ffE lines\n",
				"2021-01-01T18:00:00.020010Z ffE in\n",
				"2021-01-01T19:00:00.020010Z ffE one\n",
				"2021-01-01T20:00:00.020010Z ffE \n",
				"2021-01-01T21:00:00.020010Z ffE start\n",
				"2021-01-01T22:00:00.020010Z ffE end\n",
				"2021-01-01T23:00:00.020010Z ffE PREFIX Eat carriages\r\r\r\r\r\r\r\n",
				"2021-01-02T00:00:00.020010Z ffE PREFIX This is across\n",
				"2021-01-02T01:00:00.020010Z ffE two lines\n",
				"2021-01-02T02:00:00.020010Z ffE PREFIX The end\n",
			}

			assert.Equal(t, strings.Join(expected, ""), buf.String())
		})
	}
}

func TestWithoutTimestamp(t *testing.T) {
	buf := new(bytes.Buffer)

	defer setupDummyTime()()

	w := New(buf, StderrType, 255, false)
	writeLines(w)
	w.Close()

	expected := []string{
		"ffE PREFIX This is the beginning of a new line\n",
		"ffE PREFIX This is a split up line\n",
		"ffE PREFIX Progress bar: .\r\n",
		"ffE+.\r\n",
		"ffE+.\r\n",
		"ffE+.\r\n",
		"ffE+.\r\n",
		"ffE+.\r\n",
		"ffE+.\r\n",
		"ffE+.\r\n",
		"ffE+.\r\n",
		"ffE+.\r\n",
		"ffE+Done.\r\n",
		"ffE PREFIX Another windows new-line\r\n",
		"ffE PREFIX multiple\n",
		"ffE new\n",
		"ffE lines\n",
		"ffE in\n",
		"ffE one\n",
		"ffE \n",
		"ffE start\n",
		"ffE end\n",
		"ffE PREFIX Eat carriages\r\r\r\r\r\r\r\n",
		"ffE PREFIX This is across\n",
		"ffE two lines\n",
		"ffE PREFIX The end\n",
	}

	assert.Equal(t, strings.Join(expected, ""), buf.String())
}

// nolint:errcheck
func TestForcedFlush(t *testing.T) {
	buf := new(bytes.Buffer)

	defer setupDummyTime()()

	w := New(buf, StderrType, 255, true)
	w.Write([]byte("PREFIX This is the beginning of a new line\n"))
	w.Write([]byte("We have no new line character in this write"))
	w.Write([]byte("... The line is now flushed.\n"))
	w.Write([]byte("large continuous write incoming"))
	w.Write(bytes.Repeat([]byte{'.'}, bufSize))
	w.Write(bytes.Repeat([]byte{'.'}, bufSize+1))
	w.Write([]byte("ended\n"))
	w.Close()

	expected := []string{
		"2021-01-01T01:00:00.020010Z ffE PREFIX This is the beginning of a new line\n",
		"2021-01-01T02:00:00.020010Z ffE We have no new line character in this write... The line is now flushed.\n",
		"2021-01-01T03:00:00.020010Z ffE large continuous write incoming" + strings.Repeat(".", bufSize) + "\n",
		"2021-01-01T04:00:00.020010Z ffE+" + strings.Repeat(".", bufSize+1) + "\n",
		"2021-01-01T05:00:00.020010Z ffE+ended\n",
	}

	assert.Equal(t, strings.Join(expected, ""), buf.String())
}

func BenchmarkWithTimestamps(b *testing.B) {
	defer setupDummyTime()()

	w := New(io.Discard, StderrType, 255, true)

	headerSize := len(format) + 4

	line := []byte("This is the beginning of a new line\n")
	b.SetBytes(int64((headerSize + len(line)) * 200))
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		for j := 0; j < 200; j++ {
			_, _ = w.Write(line)
		}
	}
}
