package login

import (
	"errors"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"

	"k8s.io/kubernetes/pkg/auth/user"

	"github.com/openshift/origin/pkg/auth/server/csrf"
)

type testImplicit struct {
	Request *http.Request
	User    user.Info
	Success bool
	Err     error
	Then    string
	Called  bool
}

func (t *testImplicit) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
	t.Request = req
	return t.User, t.Success, t.Err
}

func (t *testImplicit) AuthenticationSucceeded(user user.Info, then string, w http.ResponseWriter, req *http.Request) (bool, error) {
	t.Called = true
	t.User = user
	t.Then = then
	return false, nil
}

func TestImplicit(t *testing.T) {
	testCases := map[string]struct {
		CSRF       csrf.CSRF
		Implicit   *testImplicit
		Path       string
		PostValues url.Values

		ExpectStatusCode int
		ExpectRedirect   string
		ExpectContains   []string
		ExpectThen       string
	}{
		"display confirm form": {
			CSRF:     &csrf.FakeCSRF{Token: "test", Err: nil},
			Implicit: &testImplicit{Success: true, User: &user.DefaultInfo{Name: "user"}},
			Path:     "/login",
			ExpectContains: []string{
				`action="/login"`,
				`You are now logged in as`,
			},
		},
		"successful POST redirects": {
			CSRF:       &csrf.FakeCSRF{Token: "test", Err: nil},
			Implicit:   &testImplicit{Success: true, User: &user.DefaultInfo{Name: "user"}},
			Path:       "/login?then=%2Ffoo",
			PostValues: url.Values{"csrf": []string{"test"}},
			ExpectThen: "/foo",
		},
		"redirect when POST fails CSRF": {
			CSRF:           &csrf.FakeCSRF{Token: "test", Err: nil},
			Implicit:       &testImplicit{Success: true, User: &user.DefaultInfo{Name: "user"}},
			Path:           "/login",
			PostValues:     url.Values{"csrf": []string{"wrong"}},
			ExpectRedirect: "/login?reason=token+expired",
		},
		"redirect when not authenticated": {
			CSRF:           &csrf.FakeCSRF{Token: "test", Err: nil},
			Implicit:       &testImplicit{Success: false},
			Path:           "/login",
			PostValues:     url.Values{"csrf": []string{"test"}},
			ExpectRedirect: "/login?reason=access+denied",
		},
		"redirect on auth failure": {
			CSRF:           &csrf.FakeCSRF{Token: "test", Err: nil},
			Implicit:       &testImplicit{Err: errors.New("failed")},
			Path:           "/login",
			PostValues:     url.Values{"csrf": []string{"test"}},
			ExpectRedirect: "/login?reason=access+denied",
		},
		"expect GET error": {
			CSRF:           &csrf.FakeCSRF{Token: "test", Err: nil},
			Implicit:       &testImplicit{Err: errors.New("failed")},
			ExpectContains: []string{`"message">An unknown error has occurred. Contact your administrator`},
		},
	}

	for k, testCase := range testCases {
		server := httptest.NewServer(NewConfirm(testCase.CSRF, testCase.Implicit, DefaultConfirmFormRenderer))

		var resp *http.Response
		if testCase.PostValues != nil {
			r, err := postForm(server.URL+testCase.Path, testCase.PostValues)
			if err != nil {
				t.Errorf("%s: unexpected error: %v", k, err)
				continue
			}
			resp = r
		} else {
			r, err := getURL(server.URL + testCase.Path)
			if err != nil {
				t.Errorf("%s: unexpected error: %v", k, err)
				continue
			}
			resp = r
		}
		defer resp.Body.Close()

		if testCase.ExpectStatusCode != 0 && testCase.ExpectStatusCode != resp.StatusCode {
			t.Errorf("%s: unexpected response: %#v", k, resp)
			continue
		}

		if testCase.ExpectRedirect != "" {
			uri, err := resp.Location()
			if err != nil {
				t.Errorf("%s: unexpected error: %v", testCase.ExpectRedirect, err)
				continue
			}
			if uri.String() != server.URL+testCase.ExpectRedirect {
				t.Errorf("%s: unexpected redirect: %s", testCase.ExpectRedirect, uri.String())
			}
		}

		if testCase.ExpectThen != "" && (!testCase.Implicit.Called || testCase.Implicit.Then != testCase.ExpectThen) {
			t.Errorf("%s: did not find expected 'then' value: %#v", k, testCase.Implicit)
		}

		if len(testCase.ExpectContains) > 0 {
			data, _ := ioutil.ReadAll(resp.Body)
			body := string(data)
			for i := range testCase.ExpectContains {
				if !strings.Contains(body, testCase.ExpectContains[i]) {
					t.Errorf("%s: did not find expected value %s: %s", k, testCase.ExpectContains[i], body)
					continue
				}
			}
		}
	}
}