package imageprogress

import (
	"encoding/json"
	"io"
	"reflect"
	"strconv"
	"testing"
)

func TestReports(t *testing.T) {
	tests := []struct {
		name        string
		gen         func(*progressGenerator)
		errExpected bool
		expected    report
	}{
		{
			name: "simple report",
			gen: func(p *progressGenerator) {
				p.status("1", "Extracting")
				p.status("2", "Downloading")
				p.status("1", "Downloading")
				p.status("2", "Pull complete")
			},
			expected: report{
				statusDownloading: &layerDetail{Count: 1},
				statusComplete:    &layerDetail{Count: 1},
			},
		},
		{
			name: "ignore invalid layer id",
			gen: func(p *progressGenerator) {
				p.status("1", "Downloading")
				p.status("hello", "testing")
				p.status("1", "Downloading")
			},
			expected: report{
				statusDownloading: &layerDetail{Count: 1},
			},
		},
		{
			name: "ignore retrying status",
			gen: func(p *progressGenerator) {
				p.status("1", "Downloading")
				p.status("2", "Pull complete")
				p.status("1", "Downloading")
				p.status("3", "Retrying")
			},
			expected: report{
				statusDownloading: &layerDetail{Count: 1},
				statusComplete:    &layerDetail{Count: 1},
			},
		},
		{
			name: "detect error",
			gen: func(p *progressGenerator) {
				p.status("1", "Downloading")
				p.err("an error")
			},
			errExpected: true,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			pipeIn, pipeOut := io.Pipe()
			go func() {
				p := newProgressGenerator(pipeOut)
				test.gen(p)
				pipeOut.Close()
			}()
			var lastReport report
			w := newWriter(
				func(r report) {
					lastReport = r
				},
				func(a report, b report) bool {
					return true
				},
			)
			w.(*imageProgressWriter).stableThreshhold = 0
			_, err := io.Copy(w, pipeIn)
			if err == nil {
				err = w.Close()
			}
			if err != nil {
				if !test.errExpected {
					t.Fatalf("%s: unexpected: %v", test.name, err)
				}
				return
			}
			if test.errExpected {
				t.Fatalf("%s: did not get expected error", test.name)
			}
			// w.Close() waits until the goroutine inside of the progress generator finishes
			if !compareReport(lastReport, test.expected) {
				t.Errorf("%s: unexpected report, got: %v, expected: %v", test.name, lastReport, test.expected)
			}
		})
	}
}

func TestErrorOnCopy(t *testing.T) {
	// Producer pipe
	genIn, genOut := io.Pipe()
	p := newProgressGenerator(genOut)

	// generate some data
	go func() {
		for i := 0; i < 100; i++ {
			p.status("1", "Downloading")
			p.status("2", "Downloading")
			p.status("3", "Downloading")
		}
		p.err("data error")
		genOut.Close()
	}()

	w := newWriter(func(r report) {}, func(a, b report) bool { return true })

	// Ensure that the error is propagated to the copy
	_, err := io.Copy(w, genIn)
	if err == nil {
		err = w.Close()
	}
	if err == nil {
		t.Errorf("Did not get an error when copying to writer")
	}
	if err.Error() != "data error" {
		t.Errorf("Did not get expected error: %v", err)
	}
}

func TestStableLayerCount(t *testing.T) {

	tests := []struct {
		name             string
		lastLayerCount   int
		layerStatusCount int
		stableThreshhold int
		callCount        int
		expectStable     bool
	}{
		{
			name:             "increasing layer count",
			lastLayerCount:   3,
			layerStatusCount: 4,
			callCount:        1,
			expectStable:     false,
		},
		{
			name:             "has not met stable threshhold",
			lastLayerCount:   3,
			layerStatusCount: 3,
			callCount:        2,
			stableThreshhold: 3,
			expectStable:     false,
		},
		{
			name:             "met stable threshhold",
			lastLayerCount:   3,
			layerStatusCount: 3,
			callCount:        4,
			stableThreshhold: 3,
			expectStable:     true,
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			w := newWriter(func(report) {}, func(a, b report) bool { return true }).(*imageProgressWriter)
			w.lastLayerCount = test.lastLayerCount
			w.layerStatus = map[string]*progressLine{}
			w.stableThreshhold = test.stableThreshhold
			for i := 0; i < test.layerStatusCount; i++ {
				w.layerStatus[strconv.Itoa(i)] = &progressLine{}
			}
			var result bool
			for i := 0; i < test.callCount; i++ {
				result = w.isStableLayerCount()
			}
			if result != test.expectStable {
				t.Errorf("%s: expected %v, got %v", test.name, test.expectStable, result)
			}
		})
	}
}

func compareReport(a, b report) bool {
	if len(a) != len(b) {
		return false
	}
	for k := range a {
		if _, ok := b[k]; !ok {
			return false
		}
		if !reflect.DeepEqual(*a[k], *b[k]) {
			return false
		}
	}
	return true
}

type progressGenerator json.Encoder

func newProgressGenerator(w io.Writer) *progressGenerator {
	return (*progressGenerator)(json.NewEncoder(w))
}

func (p *progressGenerator) status(id, status string) {
	(*json.Encoder)(p).Encode(&progressLine{ID: id, Status: status})
}

func (p *progressGenerator) err(msg string) {
	(*json.Encoder)(p).Encode(&progressLine{Error: msg})
}
