package version

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"
)

func TestGetLatestVersions(t *testing.T) {
	four := int64(4)
	testCases := []struct {
		name   string
		resp   interface{}
		err    error
		latest Channels
	}{
		{
			"valid response",
			map[string]string{
				"foo":         "foo-1.2.3",
				"fooHotpatch": "foo-1.2.3-4",
				"stable":      "stable-2.1.0",
				"edge":        "edge-2.1.0",
			},
			nil,
			Channels{
				[]channelVersion{
					{"foo", "1.2.3", nil, "foo-1.2.3"},
					{"foo", "1.2.3", &four, "foo-1.2.3-4"},
					{"stable", "2.1.0", nil, "stable-2.1.0"},
					{"edge", "2.1.0", nil, "edge-2.1.0"},
				},
			},
		},
		{
			"channel version mismatch",
			map[string]string{
				"foo":        "foo-1.2.3",
				"stable":     "stable-2.1.0",
				"badchannel": "edge-2.1.0",
			},
			fmt.Errorf("unexpected versioncheck response: channel in edge-2.1.0 does not match badchannel"),
			Channels{},
		},
		{
			"invalid version",
			map[string]string{
				"foo":    "foo-1.2.3",
				"stable": "badchannelversion",
			},
			fmt.Errorf("unexpected versioncheck response: unsupported version format: badchannelversion"),
			Channels{},
		},
		{
			"invalid JSON",
			"bad response",
			fmt.Errorf("json: cannot unmarshal string into Go value of type map[string]string"),
			Channels{},
		},
	}

	for _, tc := range testCases {
		tc := tc // pin
		t.Run(tc.name, func(t *testing.T) {
			j, err := json.Marshal(tc.resp)
			if err != nil {
				t.Fatalf("JSON marshal failed with: %s", err)
			}

			ts := httptest.NewServer(http.HandlerFunc(
				func(w http.ResponseWriter, r *http.Request) {
					w.Write(j)
				}),
			)
			defer ts.Close()

			ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
			defer cancel()
			latest, err := getLatestVersions(ctx, ts.Client(), ts.URL)
			if (err == nil && tc.err != nil) ||
				(err != nil && tc.err == nil) ||
				((err != nil && tc.err != nil) && (err.Error() != tc.err.Error())) {
				t.Fatalf("Expected \"%s\", got \"%s\"", tc.err, err)
			}

			if !channelsEqual(latest, tc.latest) {
				t.Fatalf("Expected latest versions \"%s\", got \"%s\"", tc.latest, latest)
			}
		})
	}
}

func channelsEqual(c1, c2 Channels) bool {
	if len(c1.array) != len(c2.array) {
		return false
	}

	for _, cv1 := range c1.array {
		found := false
		for _, cv2 := range c2.array {
			if cv1.channel == cv2.channel && cv1.version == cv2.version && cv1.hotpatchEqual(cv2) {
				found = true
				break
			}
		}
		if !found {
			return false
		}
	}

	return true
}

func TestChannelsMatch(t *testing.T) {
	four := int64(4)
	channels := Channels{
		[]channelVersion{
			{"stable", "2.1.0", nil, "stable-2.1.0"},
			{"foo", "1.2.3", nil, "foo-1.2.3"},
			{"foo", "1.2.3", &four, "foo-1.2.3-4"},
			{"version", "3.2.1", nil, "version-3.2.1"},
		},
	}

	testCases := []struct {
		actualVersion string
		err           error
	}{
		{"stable-2.1.0", nil},
		{"stable-2.1.0-buildinfo", nil},
		{"foo-1.2.3", nil},
		{"foo-1.2.3-4", nil},
		{"foo-1.2.3-4-buildinfo", nil},
		{"version-3.2.1", nil},
		{
			"foo-1.2.2",
			fmt.Errorf("is running version 1.2.2 but the latest foo version is 1.2.3"),
		},
		{
			"foo-1.2.3-3",
			fmt.Errorf("is running version 1.2.3-3 but the latest foo version is 1.2.3-4"),
		},
		{
			"unsupportedChannel-1.2.3",
			fmt.Errorf("unsupported version channel: unsupportedChannel-1.2.3"),
		},
	}

	for i, tc := range testCases {
		tc := tc // pin
		t.Run(fmt.Sprintf("test %d ChannelsMatch(%s, %s)", i, tc.actualVersion, tc.err), func(t *testing.T) {
			err := channels.Match(tc.actualVersion)
			if (err == nil && tc.err != nil) ||
				(err != nil && tc.err == nil) ||
				((err != nil && tc.err != nil) && (err.Error() != tc.err.Error())) {
				t.Fatalf("Expected \"%s\", got \"%s\"", tc.err, err)
			}
		})
	}
}
