package dcos

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

	jwt "github.com/dgrijalva/jwt-go"
	"github.com/stretchr/testify/require"
)

const (
	privateKey = `-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQCwlGyzVp9cqtwiNCgCnaR0kilPZhr4xFBcnXxvQ8/uzOHaWKxj
XWR38cKR3gPh5+4iSmzMdo3HDJM5ks6imXGnp+LPOA5iNewnpLNs7UxA2arwKH/6
4qIaAXAtf5jE46wZIMgc2EW9wGL3dxC0JY8EXPpBFB/3J8gADkorFR8lwwIDAQAB
AoGBAJaFHxfMmjHK77U0UnrQWFSKFy64cftmlL4t/Nl3q7L68PdIKULWZIMeEWZ4
I0UZiFOwr4em83oejQ1ByGSwekEuiWaKUI85IaHfcbt+ogp9hY/XbOEo56OPQUAd
bEZv1JqJOqta9Ug1/E1P9LjEEyZ5F5ubx7813rxAE31qKtKJAkEA1zaMlCWIr+Rj
hGvzv5rlHH3wbOB4kQFXO4nqj3J/ttzR5QiJW24STMDcbNngFlVcDVju56LrNTiD
dPh9qvl7nwJBANILguR4u33OMksEZTYB7nQZSurqXsq6382zH7pTl29ANQTROHaM
PKC8dnDWq8RGTqKuvWblIzzGIKqIMovZo10CQC96T0UXirITFolOL3XjvAuvFO1Q
EAkdXJs77805m0dCK+P1IChVfiAEpBw3bKJArpAbQIlFfdI953JUp5SieU0CQEub
BSSEKMjh/cxu6peEHnb/262vayuCFKkQPu1sxWewLuVrAe36EKCy9dcsDmv5+rgo
Odjdxc9Madm4aKlaT6kCQQCpAgeblDrrxTrNQ+Typzo37PlnQrvI+0EceAUuJ72G
P0a+YZUeHNRqT2pPN9lMTAZGGi3CtcF2XScbLNEBeXge
-----END RSA PRIVATE KEY-----`
)

func TestLogin(t *testing.T) {
	ts := httptest.NewServer(http.NotFoundHandler())
	defer ts.Close()

	var tests = []struct {
		name          string
		responseCode  int
		responseBody  string
		expectedError error
		expectedToken string
	}{
		{
			name:          "Login successful",
			responseCode:  http.StatusOK,
			responseBody:  `{"token": "XXX.YYY.ZZZ"}`,
			expectedError: nil,
			expectedToken: "XXX.YYY.ZZZ",
		},
		{
			name:         "Unauthorized Error",
			responseCode: http.StatusUnauthorized,
			responseBody: `{"title": "x", "description": "y"}`,
			expectedError: &APIError{
				URL:         ts.URL + "/acs/api/v1/auth/login",
				StatusCode:  http.StatusUnauthorized,
				Title:       "x",
				Description: "y",
			},
			expectedToken: "",
		},
	}

	key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(privateKey))
	require.NoError(t, err)

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				w.WriteHeader(tt.responseCode)
				fmt.Fprintln(w, tt.responseBody)
			})

			u, err := url.Parse(ts.URL)
			require.NoError(t, err)

			ctx := context.Background()
			sa := &ServiceAccount{
				AccountID:  "telegraf",
				PrivateKey: key,
			}
			client := NewClusterClient(u, defaultResponseTimeout, 1, nil)
			auth, err := client.Login(ctx, sa)

			require.Equal(t, tt.expectedError, err)

			if tt.expectedToken != "" {
				require.Equal(t, tt.expectedToken, auth.Text)
			} else {
				require.Nil(t, auth)
			}
		})
	}
}

func TestGetSummary(t *testing.T) {
	ts := httptest.NewServer(http.NotFoundHandler())
	defer ts.Close()

	var tests = []struct {
		name          string
		responseCode  int
		responseBody  string
		expectedValue *Summary
		expectedError error
	}{
		{
			name:          "No nodes",
			responseCode:  http.StatusOK,
			responseBody:  `{"cluster": "a", "slaves": []}`,
			expectedValue: &Summary{Cluster: "a", Slaves: []Slave{}},
			expectedError: nil,
		},
		{
			name:          "Unauthorized Error",
			responseCode:  http.StatusUnauthorized,
			responseBody:  `<html></html>`,
			expectedValue: nil,
			expectedError: &APIError{
				URL:        ts.URL + "/mesos/master/state-summary",
				StatusCode: http.StatusUnauthorized,
				Title:      "401 Unauthorized",
			},
		},
		{
			name:         "Has nodes",
			responseCode: http.StatusOK,
			responseBody: `{"cluster": "a", "slaves": [{"id": "a"}, {"id": "b"}]}`,
			expectedValue: &Summary{
				Cluster: "a",
				Slaves: []Slave{
					Slave{ID: "a"},
					Slave{ID: "b"},
				},
			},
			expectedError: nil,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				// check the path
				w.WriteHeader(tt.responseCode)
				fmt.Fprintln(w, tt.responseBody)
			})

			u, err := url.Parse(ts.URL)
			require.NoError(t, err)

			ctx := context.Background()
			client := NewClusterClient(u, defaultResponseTimeout, 1, nil)
			summary, err := client.GetSummary(ctx)

			require.Equal(t, tt.expectedError, err)
			require.Equal(t, tt.expectedValue, summary)
		})
	}

}

func TestGetNodeMetrics(t *testing.T) {
	ts := httptest.NewServer(http.NotFoundHandler())
	defer ts.Close()

	var tests = []struct {
		name          string
		responseCode  int
		responseBody  string
		expectedValue *Metrics
		expectedError error
	}{
		{
			name:          "Empty Body",
			responseCode:  http.StatusOK,
			responseBody:  `{}`,
			expectedValue: &Metrics{},
			expectedError: nil,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				// check the path
				w.WriteHeader(tt.responseCode)
				fmt.Fprintln(w, tt.responseBody)
			})

			u, err := url.Parse(ts.URL)
			require.NoError(t, err)

			ctx := context.Background()
			client := NewClusterClient(u, defaultResponseTimeout, 1, nil)
			m, err := client.GetNodeMetrics(ctx, "foo")

			require.Equal(t, tt.expectedError, err)
			require.Equal(t, tt.expectedValue, m)
		})
	}

}

func TestGetContainerMetrics(t *testing.T) {
	ts := httptest.NewServer(http.NotFoundHandler())
	defer ts.Close()

	var tests = []struct {
		name          string
		responseCode  int
		responseBody  string
		expectedValue *Metrics
		expectedError error
	}{
		{
			name:          "204 No Content",
			responseCode:  http.StatusNoContent,
			responseBody:  ``,
			expectedValue: &Metrics{},
			expectedError: nil,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				// check the path
				w.WriteHeader(tt.responseCode)
				fmt.Fprintln(w, tt.responseBody)
			})

			u, err := url.Parse(ts.URL)
			require.NoError(t, err)

			ctx := context.Background()
			client := NewClusterClient(u, defaultResponseTimeout, 1, nil)
			m, err := client.GetContainerMetrics(ctx, "foo", "bar")

			require.Equal(t, tt.expectedError, err)
			require.Equal(t, tt.expectedValue, m)
		})
	}

}
