package openshift

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

	"github.com/sirupsen/logrus"
	"golang.org/x/oauth2"

	"github.com/dexidp/dex/connector"
	"github.com/dexidp/dex/storage/kubernetes/k8sapi"
)

func TestOpen(t *testing.T) {
	s := newTestServer(map[string]interface{}{})
	defer s.Close()

	hostURL, err := url.Parse(s.URL)
	expectNil(t, err)

	_, err = http.NewRequest("GET", hostURL.String(), nil)
	expectNil(t, err)

	c := Config{
		Issuer:       s.URL,
		ClientID:     "testClientId",
		ClientSecret: "testClientSecret",
		RedirectURI:  "https://localhost/callback",
		InsecureCA:   true,
	}

	logger := logrus.New()

	oconfig, err := c.Open("id", logger)

	oc, ok := oconfig.(*openshiftConnector)

	expectNil(t, err)
	expectEquals(t, ok, true)
	expectEquals(t, oc.apiURL, s.URL)
	expectEquals(t, oc.clientID, "testClientId")
	expectEquals(t, oc.clientSecret, "testClientSecret")
	expectEquals(t, oc.redirectURI, "https://localhost/callback")
	expectEquals(t, oc.oauth2Config.Endpoint.AuthURL, fmt.Sprintf("%s/oauth/authorize", s.URL))
	expectEquals(t, oc.oauth2Config.Endpoint.TokenURL, fmt.Sprintf("%s/oauth/token", s.URL))
}

func TestGetUser(t *testing.T) {
	s := newTestServer(map[string]interface{}{
		"/apis/user.openshift.io/v1/users/~": user{
			ObjectMeta: k8sapi.ObjectMeta{
				Name: "jdoe",
			},
			FullName: "John Doe",
			Groups:   []string{"users"},
		},
	})
	defer s.Close()

	hostURL, err := url.Parse(s.URL)
	expectNil(t, err)

	_, err = http.NewRequest("GET", hostURL.String(), nil)
	expectNil(t, err)

	h, err := newHTTPClient(true, "")

	expectNil(t, err)

	oc := openshiftConnector{apiURL: s.URL, httpClient: h}
	u, err := oc.user(context.Background(), h)

	expectNil(t, err)
	expectEquals(t, u.Name, "jdoe")
	expectEquals(t, u.FullName, "John Doe")
	expectEquals(t, len(u.Groups), 1)
}

func TestVerifySingleGroupFn(t *testing.T) {
	allowedGroups := []string{"users"}
	groupMembership := []string{"users", "org1"}

	validGroupMembership := validateAllowedGroups(groupMembership, allowedGroups)

	expectEquals(t, validGroupMembership, true)
}

func TestVerifySingleGroupFailureFn(t *testing.T) {
	allowedGroups := []string{"admins"}
	groupMembership := []string{"users"}

	validGroupMembership := validateAllowedGroups(groupMembership, allowedGroups)

	expectEquals(t, validGroupMembership, false)
}

func TestVerifyMultipleGroupFn(t *testing.T) {
	allowedGroups := []string{"users", "admins"}
	groupMembership := []string{"users", "org1"}

	validGroupMembership := validateAllowedGroups(groupMembership, allowedGroups)

	expectEquals(t, validGroupMembership, true)
}

func TestVerifyGroup(t *testing.T) {
	s := newTestServer(map[string]interface{}{
		"/apis/user.openshift.io/v1/users/~": user{
			ObjectMeta: k8sapi.ObjectMeta{
				Name: "jdoe",
			},
			FullName: "John Doe",
			Groups:   []string{"users"},
		},
	})
	defer s.Close()

	hostURL, err := url.Parse(s.URL)
	expectNil(t, err)

	_, err = http.NewRequest("GET", hostURL.String(), nil)
	expectNil(t, err)

	h, err := newHTTPClient(true, "")

	expectNil(t, err)

	oc := openshiftConnector{apiURL: s.URL, httpClient: h}
	u, err := oc.user(context.Background(), h)

	expectNil(t, err)
	expectEquals(t, u.Name, "jdoe")
	expectEquals(t, u.FullName, "John Doe")
	expectEquals(t, len(u.Groups), 1)
}

func TestCallbackIdentity(t *testing.T) {
	s := newTestServer(map[string]interface{}{
		"/apis/user.openshift.io/v1/users/~": user{
			ObjectMeta: k8sapi.ObjectMeta{
				Name: "jdoe",
				UID:  "12345",
			},
			FullName: "John Doe",
			Groups:   []string{"users"},
		},
		"/oauth/token": map[string]interface{}{
			"access_token": "oRzxVjCnohYRHEYEhZshkmakKmoyVoTjfUGC",
			"expires_in":   "30",
		},
	})
	defer s.Close()

	hostURL, err := url.Parse(s.URL)
	expectNil(t, err)

	req, err := http.NewRequest("GET", hostURL.String(), nil)
	expectNil(t, err)

	h, err := newHTTPClient(true, "")

	expectNil(t, err)

	oc := openshiftConnector{apiURL: s.URL, httpClient: h, oauth2Config: &oauth2.Config{
		Endpoint: oauth2.Endpoint{
			AuthURL:  fmt.Sprintf("%s/oauth/authorize", s.URL),
			TokenURL: fmt.Sprintf("%s/oauth/token", s.URL),
		},
	}}
	identity, err := oc.HandleCallback(connector.Scopes{Groups: true}, req)

	expectNil(t, err)
	expectEquals(t, identity.UserID, "12345")
	expectEquals(t, identity.Username, "jdoe")
	expectEquals(t, identity.PreferredUsername, "jdoe")
	expectEquals(t, identity.Email, "jdoe")
	expectEquals(t, len(identity.Groups), 1)
	expectEquals(t, identity.Groups[0], "users")
}

func TestRefreshIdentity(t *testing.T) {
	s := newTestServer(map[string]interface{}{
		usersURLPath: user{
			ObjectMeta: k8sapi.ObjectMeta{
				Name: "jdoe",
				UID:  "12345",
			},
			FullName: "John Doe",
			Groups:   []string{"users"},
		},
	})
	defer s.Close()

	h, err := newHTTPClient(true, "")
	expectNil(t, err)

	oc := openshiftConnector{apiURL: s.URL, httpClient: h, oauth2Config: &oauth2.Config{
		Endpoint: oauth2.Endpoint{
			AuthURL:  fmt.Sprintf("%s/oauth/authorize", s.URL),
			TokenURL: fmt.Sprintf("%s/oauth/token", s.URL),
		},
	}}

	data, err := json.Marshal(oauth2.Token{AccessToken: "fFAGRNJru1FTz70BzhT3Zg"})
	expectNil(t, err)

	oldID := connector.Identity{ConnectorData: data}

	identity, err := oc.Refresh(context.Background(), connector.Scopes{Groups: true}, oldID)

	expectNil(t, err)
	expectEquals(t, identity.UserID, "12345")
	expectEquals(t, identity.Username, "jdoe")
	expectEquals(t, identity.PreferredUsername, "jdoe")
	expectEquals(t, identity.Email, "jdoe")
	expectEquals(t, len(identity.Groups), 1)
	expectEquals(t, identity.Groups[0], "users")
}

func TestRefreshIdentityFailure(t *testing.T) {
	s := newTestServer(map[string]interface{}{
		usersURLPath: user{
			ObjectMeta: k8sapi.ObjectMeta{
				Name: "jdoe",
				UID:  "12345",
			},
			FullName: "John Doe",
			Groups:   []string{"users"},
		},
	})
	defer s.Close()

	h, err := newHTTPClient(true, "")
	expectNil(t, err)

	oc := openshiftConnector{apiURL: s.URL, httpClient: h, oauth2Config: &oauth2.Config{
		Endpoint: oauth2.Endpoint{
			AuthURL:  fmt.Sprintf("%s/oauth/authorize", s.URL),
			TokenURL: fmt.Sprintf("%s/oauth/token", s.URL),
		},
	}}

	data, err := json.Marshal(oauth2.Token{AccessToken: "oRzxVjCnohYRHEYEhZshkmakKmoyVoTjfUGC", Expiry: time.Now().Add(-time.Hour)})
	expectNil(t, err)

	oldID := connector.Identity{ConnectorData: data}

	identity, err := oc.Refresh(context.Background(), connector.Scopes{Groups: true}, oldID)
	expectNotNil(t, err)
	expectEquals(t, connector.Identity{}, identity)
}

func newTestServer(responses map[string]interface{}) *httptest.Server {
	var s *httptest.Server
	s = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		responses["/.well-known/oauth-authorization-server"] = map[string]interface{}{
			"issuer":                           s.URL,
			"authorization_endpoint":           fmt.Sprintf("%s/oauth/authorize", s.URL),
			"token_endpoint":                   fmt.Sprintf("%s/oauth/token", s.URL),
			"scopes_supported":                 []string{"user:full", "user:info", "user:check-access", "user:list-scoped-projects", "user:list-projects"},
			"response_types_supported":         []string{"token", "code"},
			"grant_types_supported":            []string{"authorization_code", "implicit"},
			"code_challenge_methods_supported": []string{"plain", "S256"},
		}

		response := responses[r.RequestURI]
		w.Header().Add("Content-Type", "application/json")
		json.NewEncoder(w).Encode(response)
	}))

	return s
}

func expectNil(t *testing.T, a interface{}) {
	if a != nil {
		t.Errorf("Expected %+v to equal nil", a)
	}
}

func expectEquals(t *testing.T, a interface{}, b interface{}) {
	if !reflect.DeepEqual(a, b) {
		t.Errorf("Expected %+v to equal %+v", a, b)
	}
}

func expectNotNil(t *testing.T, a interface{}) {
	if a == nil {
		t.Errorf("Expected %+v to not equal nil", a)
	}
}