package image

import (
	"bytes"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/docker/cli/cli/command/formatter"
	"github.com/docker/cli/internal/test"
	"github.com/docker/docker/api/types/image"
	"github.com/docker/docker/pkg/stringid"
	"gotest.tools/v3/assert"
)

type historyCase struct {
	historyCtx historyContext
	expValue   string
	call       func() string
}

func TestHistoryContext_ID(t *testing.T) {
	id := stringid.GenerateRandomID()

	var ctx historyContext
	cases := []historyCase{
		{
			historyContext{
				h:     image.HistoryResponseItem{ID: id},
				trunc: false,
			}, id, ctx.ID,
		},
		{
			historyContext{
				h:     image.HistoryResponseItem{ID: id},
				trunc: true,
			}, stringid.TruncateID(id), ctx.ID,
		},
	}

	for _, c := range cases {
		ctx = c.historyCtx
		v := c.call()
		if strings.Contains(v, ",") {
			test.CompareMultipleValues(t, v, c.expValue)
		} else if v != c.expValue {
			t.Fatalf("Expected %s, was %s\n", c.expValue, v)
		}
	}
}

func TestHistoryContext_CreatedSince(t *testing.T) {
	longerAgo := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
	dateStr := longerAgo.Local().Format(time.RFC3339)
	var ctx historyContext
	cases := []historyCase{
		{
			historyContext{
				h:     image.HistoryResponseItem{Created: time.Now().AddDate(0, 0, -7).Unix()},
				trunc: false,
				human: true,
			}, "7 days ago", ctx.CreatedSince,
		},
		{
			historyContext{
				h:     image.HistoryResponseItem{Created: longerAgo.Unix()},
				trunc: false,
				human: false,
			}, dateStr, ctx.CreatedSince,
		},
		{
			// The zero time is not displayed.
			historyContext{
				h:     image.HistoryResponseItem{Created: 0},
				trunc: false,
				human: true,
			}, "N/A", ctx.CreatedSince,
		},
		{
			// A time before the year 2000 is not displayed.
			historyContext{
				h:     image.HistoryResponseItem{Created: time.Date(1980, time.November, 10, 10, 23, 0, 0, time.UTC).Unix()},
				trunc: false,
				human: true,
			}, "N/A", ctx.CreatedSince,
		},
		{
			// A time after 2000 is displayed.
			historyContext{
				h:     image.HistoryResponseItem{Created: time.Now().AddDate(-11, 0, 0).Unix()},
				trunc: false,
				human: true,
			}, "11 years ago", ctx.CreatedSince,
		},
	}

	for _, c := range cases {
		ctx = c.historyCtx
		v := c.call()
		if strings.Contains(v, ",") {
			test.CompareMultipleValues(t, v, c.expValue)
		} else if v != c.expValue {
			t.Fatalf("Expected %q, was %q\n", c.expValue, v)
		}
	}
}

func TestHistoryContext_CreatedBy(t *testing.T) {
	withTabs := `/bin/sh -c apt-key adv --keyserver hkp://pgp.mit.edu:80	--recv-keys 573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62	&& echo "deb http://nginx.org/packages/mainline/debian/ jessie nginx" >> /etc/apt/sources.list  && apt-get update  && apt-get install --no-install-recommends --no-install-suggests -y       ca-certificates       nginx=${NGINX_VERSION}       nginx-module-xslt       nginx-module-geoip       nginx-module-image-filter       nginx-module-perl       nginx-module-njs       gettext-base  && rm -rf /var/lib/apt/lists/*` //nolint:lll
	expected := `/bin/sh -c apt-key adv --keyserver hkp://pgp.mit.edu:80 --recv-keys 573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62 && echo "deb http://nginx.org/packages/mainline/debian/ jessie nginx" >> /etc/apt/sources.list  && apt-get update  && apt-get install --no-install-recommends --no-install-suggests -y       ca-certificates       nginx=${NGINX_VERSION}       nginx-module-xslt       nginx-module-geoip       nginx-module-image-filter       nginx-module-perl       nginx-module-njs       gettext-base  && rm -rf /var/lib/apt/lists/*` //nolint:lll

	var ctx historyContext
	cases := []historyCase{
		{
			historyContext{
				h:     image.HistoryResponseItem{CreatedBy: withTabs},
				trunc: false,
			}, expected, ctx.CreatedBy,
		},
		{
			historyContext{
				h:     image.HistoryResponseItem{CreatedBy: withTabs},
				trunc: true,
			}, formatter.Ellipsis(expected, 45), ctx.CreatedBy,
		},
	}

	for _, c := range cases {
		ctx = c.historyCtx
		v := c.call()
		if strings.Contains(v, ",") {
			test.CompareMultipleValues(t, v, c.expValue)
		} else if v != c.expValue {
			t.Fatalf("Expected %s, was %s\n", c.expValue, v)
		}
	}
}

func TestHistoryContext_Size(t *testing.T) {
	size := int64(182964289)
	expected := "183MB"

	var ctx historyContext
	cases := []historyCase{
		{
			historyContext{
				h:     image.HistoryResponseItem{Size: size},
				trunc: false,
				human: true,
			}, expected, ctx.Size,
		}, {
			historyContext{
				h:     image.HistoryResponseItem{Size: size},
				trunc: false,
				human: false,
			}, strconv.Itoa(182964289), ctx.Size,
		},
	}

	for _, c := range cases {
		ctx = c.historyCtx
		v := c.call()
		if strings.Contains(v, ",") {
			test.CompareMultipleValues(t, v, c.expValue)
		} else if v != c.expValue {
			t.Fatalf("Expected %s, was %s\n", c.expValue, v)
		}
	}
}

func TestHistoryContext_Comment(t *testing.T) {
	comment := "Some comment"

	var ctx historyContext
	cases := []historyCase{
		{
			historyContext{
				h:     image.HistoryResponseItem{Comment: comment},
				trunc: false,
			}, comment, ctx.Comment,
		},
	}

	for _, c := range cases {
		ctx = c.historyCtx
		v := c.call()
		if strings.Contains(v, ",") {
			test.CompareMultipleValues(t, v, c.expValue)
		} else if v != c.expValue {
			t.Fatalf("Expected %s, was %s\n", c.expValue, v)
		}
	}
}

func TestHistoryContext_Table(t *testing.T) {
	out := bytes.NewBufferString("")
	unixTime := time.Now().AddDate(0, 0, -1).Unix()
	oldDate := time.Now().AddDate(-17, 0, 0).Unix()
	histories := []image.HistoryResponseItem{
		{
			ID:        "imageID1",
			Created:   unixTime,
			CreatedBy: "/bin/bash ls && npm i && npm run test && karma -c karma.conf.js start && npm start && more commands here && the list goes on",
			Size:      int64(182964289),
			Comment:   "Hi",
			Tags:      []string{"image:tag2"},
		},
		{ID: "imageID2", Created: unixTime, CreatedBy: "/bin/bash echo", Size: int64(182964289), Comment: "Hi", Tags: []string{"image:tag2"}},
		{ID: "imageID3", Created: unixTime, CreatedBy: "/bin/bash ls", Size: int64(182964289), Comment: "Hi", Tags: []string{"image:tag2"}},
		{ID: "imageID4", Created: unixTime, CreatedBy: "/bin/bash grep", Size: int64(182964289), Comment: "Hi", Tags: []string{"image:tag2"}},
		{ID: "imageID5", Created: 0, CreatedBy: "/bin/bash echo", Size: int64(182964289), Comment: "Hi", Tags: []string{"image:tag2"}},
		{ID: "imageID6", Created: oldDate, CreatedBy: "/bin/bash echo", Size: int64(182964289), Comment: "Hi", Tags: []string{"image:tag2"}},
	}

	//nolint:dupword // ignore "Duplicate words (CREATED) found"
	const expectedNoTrunc = `IMAGE      CREATED        CREATED BY                                                                                                                     SIZE      COMMENT
imageID1   24 hours ago   /bin/bash ls && npm i && npm run test && karma -c karma.conf.js start && npm start && more commands here && the list goes on   183MB     Hi
imageID2   24 hours ago   /bin/bash echo                                                                                                                 183MB     Hi
imageID3   24 hours ago   /bin/bash ls                                                                                                                   183MB     Hi
imageID4   24 hours ago   /bin/bash grep                                                                                                                 183MB     Hi
imageID5   N/A            /bin/bash echo                                                                                                                 183MB     Hi
imageID6   17 years ago   /bin/bash echo                                                                                                                 183MB     Hi
`
	//nolint:dupword // ignore "Duplicate words (CREATED) found"
	const expectedTrunc = `IMAGE      CREATED        CREATED BY                                      SIZE      COMMENT
imageID1   24 hours ago   /bin/bash ls && npm i && npm run test && kar…   183MB     Hi
imageID2   24 hours ago   /bin/bash echo                                  183MB     Hi
imageID3   24 hours ago   /bin/bash ls                                    183MB     Hi
imageID4   24 hours ago   /bin/bash grep                                  183MB     Hi
imageID5   N/A            /bin/bash echo                                  183MB     Hi
imageID6   17 years ago   /bin/bash echo                                  183MB     Hi
`

	cases := []struct {
		context  formatter.Context
		expected string
	}{
		{
			formatter.Context{
				Format: NewHistoryFormat("table", false, true),
				Trunc:  true,
				Output: out,
			},
			expectedTrunc,
		},
		{
			formatter.Context{
				Format: NewHistoryFormat("table", false, true),
				Trunc:  false,
				Output: out,
			},
			expectedNoTrunc,
		},
	}

	for _, tc := range cases {
		tc := tc
		t.Run(string(tc.context.Format), func(t *testing.T) {
			err := HistoryWrite(tc.context, true, histories)
			assert.NilError(t, err)
			assert.Equal(t, out.String(), tc.expected)
			// Clean buffer
			out.Reset()
		})
	}
}
