package rootmux

import (
	"encoding/json"
	"net/http"
	"net/http/cookiejar"
	"net/http/httptest"
	"net/url"
	"os"
	"testing"
	"time"

	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/auth"
	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/mocks"
	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/models"
	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/tester"
	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/tokens"
)

var (
	oAuth2Server    *httptest.Server
	mailSubject     = models.MailSubject{Year: 2021, Month: 1, Subject: "[Ecolyo] Newsletter"}
	mailSubjectStr  string
	monthlyInfo     = models.MonthlyInfo{Year: 2021, Month: 1, Info: "Informations du mois", Image: "imagebase64"}
	monthlyInfoStr  string
	monthlyNews     = models.MonthlyNews{Year: 2021, Month: 1, Title: "", Content: "Nouvelles fonctionnalités"}
	monthlyNewsStr  string
	newPoll         = models.Poll{Year: 2021, Month: 1, Question: "pollQuestion", Link: "pollLink"}
	newPollStr      string
	partnersInfo    = models.PartnersInfo{ID: 1, GRDFFailure: false, EnedisFailure: false, EGLFailure: true, NotificationActivated: true}
	partnersInfoStr string
	consent         = models.Consent{Firstname: "Foo", Lastname: "Bar", PointID: 123456}
	consentStr      string
	noH             map[string]string
)

func TestMain(m *testing.M) {

	// Create the OAuth2 mock server
	oAuth2Server = httptest.NewServer(mocks.CreateMockOAuth2())
	defer oAuth2Server.Close()

	// Set the constants with environment variables
	os.Setenv("HOSTNAME", "localhost")
	os.Setenv("ADMIN_ROLE", "ADMINS")
	os.Setenv("CLIENT_ID", "foo")
	os.Setenv("CLIENT_SECRET", "bar")
	os.Setenv("TOKEN_URL", oAuth2Server.URL+"/token")
	os.Setenv("USERINFO_URL", oAuth2Server.URL+"/userinfo")
	os.Setenv("LOGOUT_URL", oAuth2Server.URL+"/logout")
	os.Setenv("MEILI_MASTER_KEY", "masterkey")
	os.Setenv("MEILI_HOST", "http://localhost:7700")
	os.Setenv("SGE_API_TOKEN", "sgeApiToken")

	// Setup the token manager to use debug mode
	os.Setenv("DEBUG_MODE", "true")
	tokens.Init("../configs/tokenskey.json", true)

	// Convert example objects to string
	mailSubjectBytes, _ := json.Marshal(mailSubject)
	mailSubjectStr = string(mailSubjectBytes)
	monthlyNewsBytes, _ := json.Marshal(monthlyNews)
	monthlyNewsStr = string(monthlyNewsBytes)
	monthlyInfoBytes, _ := json.Marshal(monthlyInfo)
	monthlyInfoStr = string(monthlyInfoBytes)
	partnersInfoBytes, _ := json.Marshal(partnersInfo)
	partnersInfoStr = string(partnersInfoBytes)
	newPollBytes, _ := json.Marshal(newPoll)
	newPollStr = string(newPollBytes)
	consentBytes, _ := json.Marshal(consent)
	consentStr = string(consentBytes)

	code := m.Run()
	// Remove the database
	os.Remove("backoffice.db")
	os.Exit(code)
}

func TestAll(t *testing.T) {

	// Set up testers
	os.Setenv("AUTH_URL", oAuth2Server.URL+"/auth-wrong-state") // Set the server to access failing OAuth2 endpoints
	oauth2Tests(t)
	os.Setenv("AUTH_URL", oAuth2Server.URL+"/auth") // Set the server to access the correct OAuth2Endpoint
	unloggedTests(t)
	os.Setenv("USERINFO_URL", oAuth2Server.URL+"/admininfo")
	adminTests(t)

	// SGE API tests
	sgeTests(t)
}

/**
SECURITY TESTS (this tests are to check that the security protections works)
**/
func oauth2Tests(t *testing.T) {
	// Create the tester
	ts, do, _ := createTester(t)
	defer ts.Close() // Close the tester
	// Try to login (must fail)
	do("GET", "/OAuth2Login", noH, "", http.StatusInternalServerError, "invalid oauth state")
}

/**
UNLOGGED USER TESTS (this tests are to check that the security protections works)
**/
func unloggedTests(t *testing.T) {
	// Create the tester
	ts, do, _ := createTester(t)
	defer ts.Close() // Close the tester

	// Try to create a monthlyNews (must fail)
	do("PUT", "/api/admin/monthlyNews", noH, monthlyNewsStr, http.StatusUnauthorized, "error extracting token")
	// Try to get a monthlyReport (must fail because no parameters are given)
	do("GET", "/api/common/monthlyReport", noH, "", http.StatusBadRequest, "")
	// Try to get a monthlyReport (must pass empty)
	do("GET", "/api/common/monthlyReport?year=2021&month=12", noH, "", http.StatusOK, `{"year":2021,"month":12,"subject":"[Ecolyo] Votre bilan de décembre 2021","info":"","image":"","newsTitle":"","newsContent":"","question":"","link":""}`)
	// Try to get partnersInfo (must pass)
	do("GET", "/api/common/partnersInfo", noH, "", http.StatusOK, `{"ID":1,"grdf_failure":false,"enedis_failure":false,"egl_failure":false,"notification_activated":false}`)
}

/**
ADMIN TESTS (this tests are to check that an administrator can edit a newsletter's content)
**/
func adminTests(t *testing.T) {
	// Create the tester
	ts, do, _ := createTester(t)
	defer ts.Close() // Close the tester
	tests := func() {
		// Get the XSRF Token
		response := do("GET", "/api/common/WhoAmI", noH, "", http.StatusOK, "")
		token := auth.TokenData{}
		json.Unmarshal([]byte(response), &token)
		xsrfHeader := map[string]string{"XSRF-TOKEN": token.XSRFToken}
		// Try to create a monthlyNews without the XSRF-TOKEN (must fail)
		do("PUT", "/api/admin/monthlyNews", noH, monthlyNewsStr, http.StatusUnauthorized, "XSRF protection triggered")
		// Try to create a monthlyNews without body (must fail)
		do("PUT", "/api/admin/monthlyNews", xsrfHeader, "", http.StatusBadRequest, "request body is empty")
		// Try to get a monthlyNews before it is created (must fail because not found)
		do("GET", "/api/admin/monthlyNews/2021/1", xsrfHeader, "", http.StatusNotFound, "")
		// Try to create a monthlyNews (must pass)
		do("PUT", "/api/admin/monthlyNews", xsrfHeader, monthlyNewsStr, http.StatusCreated, `{"year":2021,"month":1,"title":"Les nouveautés du service","content":"Nouvelles fonctionnalités"`)
		// Try to update a monthlyNews (must pass)
		do("PUT", "/api/admin/monthlyNews", xsrfHeader, monthlyNewsStr, http.StatusOK, `{"year":2021,"month":1,"title":"Les nouveautés du service","content":"Nouvelles fonctionnalités"`)
		// Try to get the monthlyNews created (must pass)
		do("GET", "/api/admin/monthlyNews/2021/1", xsrfHeader, "", http.StatusOK, `{"year":2021,"month":1,"title":"Les nouveautés du service","content":"Nouvelles fonctionnalités"`)
		// Try to get the monthlyReport (must pass)
		do("GET", "/api/common/monthlyReport/2021/1", noH, "", http.StatusOK, `{"year":2021,"month":1,"subject":"[Ecolyo] Votre bilan de janvier 2021","info":"","image":"","newsTitle":"Les nouveautés du service","newsContent":"Nouvelles fonctionnalités","question":"","link":""}`)

		// Try to create a poll without the XSRF-TOKEN (must fail)
		do("PUT", "/api/admin/poll", noH, newPollStr, http.StatusUnauthorized, "XSRF protection triggered")
		// Try to create a poll without body (must fail)
		do("PUT", "/api/admin/poll", xsrfHeader, "", http.StatusBadRequest, "request body is empty")
		// Try to get a poll before it is created (must fail because not found')
		do("GET", "/api/admin/poll/2021/1", xsrfHeader, "", http.StatusNotFound, "")
		// Try to create a poll (must pass)
		do("PUT", "/api/admin/poll", xsrfHeader, newPollStr, http.StatusCreated, newPollStr)
		// Try to update a poll (must pass)
		do("PUT", "/api/admin/poll", xsrfHeader, newPollStr, http.StatusOK, newPollStr)
		// Try to get the poll created (must pass)
		do("GET", "/api/admin/poll/2021/1", xsrfHeader, "", http.StatusOK, newPollStr)
		// Try to get the monthlyReport (must pass)
		do("GET", "/api/common/monthlyReport/2021/1", noH, "", http.StatusOK, `{"year":2021,"month":1,"subject":"[Ecolyo] Votre bilan de janvier 2021","info":"","image":"","newsTitle":"Les nouveautés du service","newsContent":"Nouvelles fonctionnalités","question":"pollQuestion","link":"pollLink"}`)

		// Try to create a monthlyInfo without the XSRF-TOKEN (must fail)
		do("PUT", "/api/admin/monthlyInfo", noH, monthlyInfoStr, http.StatusUnauthorized, "XSRF protection triggered")
		// Try to create a monthlyInfo without body (must fail)
		do("PUT", "/api/admin/monthlyInfo", xsrfHeader, "", http.StatusBadRequest, "request body is empty")
		// Try to get a monthlyInfo before it is created (must fail because not found)
		do("GET", "/api/admin/monthlyInfo/2021/1", xsrfHeader, "", http.StatusNotFound, "")
		// Try to create a monthlyInfo (must pass)
		do("PUT", "/api/admin/monthlyInfo", xsrfHeader, monthlyInfoStr, http.StatusCreated, monthlyInfoStr)
		// Try to update a monthlyInfo (must pass)
		do("PUT", "/api/admin/monthlyInfo", xsrfHeader, monthlyInfoStr, http.StatusOK, monthlyInfoStr)
		// Try to get the monthlyInfo created (must pass)
		do("GET", "/api/admin/monthlyInfo/2021/1", xsrfHeader, "", http.StatusOK, monthlyInfoStr)
		// Try to get the monthlyReport (must pass)
		do("GET", "/api/common/monthlyReport/2021/1", noH, "", http.StatusOK, `{"year":2021,"month":1,"subject":"[Ecolyo] Votre bilan de janvier 2021","info":"Informations du mois","image":"imagebase64","newsTitle":"Les nouveautés du service","newsContent":"Nouvelles fonctionnalités","question":"pollQuestion","link":"pollLink"`)

		// Try to update the partnersInfo (must pass)
		do("PUT", "/api/admin/partnersInfo", xsrfHeader, partnersInfoStr, http.StatusOK, partnersInfoStr)
		// Try to get the monthlyInfo created (must pass)
		do("GET", "/api/common/partnersInfo", xsrfHeader, "", http.StatusOK, partnersInfoStr)

		// Try to update the partnersInfo (must pass)
		do("PUT", "/api/admin/partnersInfo", xsrfHeader, partnersInfoStr, http.StatusOK, partnersInfoStr)
		// Try to get the monthlyInfo created (must pass)
		do("GET", "/api/common/partnersInfo", xsrfHeader, "", http.StatusOK, partnersInfoStr)

		// Try to delete the monthlyNews created (must pass)
		do("DELETE", "/api/admin/monthlyNews/2021/1", xsrfHeader, "", http.StatusOK, "successful delete")
		// Try to get a monthlyNews after it is deleted (must fail because not found)
		do("GET", "/api/admin/monthlyNews/2021/1", xsrfHeader, "", http.StatusNotFound, "")

		// Try to create a mail subject (must pass)
		do("PUT", "/api/admin/mailSubject", xsrfHeader, mailSubjectStr, http.StatusCreated, mailSubjectStr)
		// Try to get the monthlyReport (must pass)
		do("GET", "/api/common/monthlyReport/2021/1", noH, "", http.StatusOK, `{"year":2021,"month":1,"subject":"[Ecolyo] Newsletter","info":"Informations du mois","image":"imagebase64","newsTitle":"","newsContent":"","question":"pollQuestion","link":"pollLink"`)
		// Try to delete the poll created (must pass)
		do("DELETE", "/api/admin/poll/2021/1", xsrfHeader, "", http.StatusOK, "successful delete")
		// Try to get a poll after it is deleted (must fail because not found)
		do("GET", "/api/admin/poll/2021/1", xsrfHeader, "", http.StatusNotFound, "")
		// Try to get the monthlyReport (must pass)
		do("GET", "/api/common/monthlyReport/2021/1", noH, "", http.StatusOK, `{"year":2021,"month":1,"subject":"[Ecolyo] Newsletter","info":"Informations du mois","image":"imagebase64","newsTitle":"","newsContent":"","question":"","link":""`)

		// Try to delete the mail subject created (must pass)
		do("DELETE", "/api/admin/mailSubject/2021/1", xsrfHeader, "", http.StatusOK, "successful delete")
		// Try to get a mail subject after it is deleted (must fail because not found)
		do("GET", "/api/admin/mailSubject/2021/1", xsrfHeader, "", http.StatusNotFound, "")
		// Try to get the monthlyReport (must pass)
		do("GET", "/api/common/monthlyReport/2021/1", noH, "", http.StatusOK, `{"year":2021,"month":1,"subject":"[Ecolyo] Votre bilan de janvier 2021","info":"Informations du mois","image":"imagebase64","newsTitle":"","newsContent":"","question":"","link":""`)

	}
	// Try to login (must pass)
	do("GET", "/OAuth2Login", noH, "", http.StatusOK, "")
	// Run the tests
	tests()
	// Try to logout (must pass)
	do("GET", "/Logout", noH, "", http.StatusOK, "")
	// Try to get a monthlyNews again (must fail)
	do("GET", "/api/admin/monthlyNews", noH, "", http.StatusUnauthorized, "error extracting token")
	// Try to get a poll again (must fail)
	do("GET", "/api/admin/poll", noH, "", http.StatusUnauthorized, "error extracting token")

}

func sgeTests(t *testing.T) {
	// Create the tester
	ts, do, _ := createTester(t)
	defer ts.Close() // Close the tester
	// Try to create a consent  (must fail)
	do("POST", "/api/sge/consent", noH, consentStr, http.StatusUnauthorized, "invalid token")
	// Try to get a consent  (must fail)
	do("GET", "/api/sge/consent/1", noH, "", http.StatusUnauthorized, "invalid token")
	// Try to update a consent  (must fail)
	do("PUT", "/api/sge/consent/1", noH, "", http.StatusUnauthorized, "invalid token")
	// Try to delete a consent  (must fail)
	do("DELETE", "/api/sge/consent/1", noH, "", http.StatusUnauthorized, "invalid token")

	// Create correct authorization header
	sgeApiHeader := map[string]string{"Authorization": "Bearer " + auth.SGEApiToken}
	// Try to create a consent  (must pass)
	do("POST", "/api/sge/consent", sgeApiHeader, consentStr, http.StatusCreated, `{"ID":1`)
	// Wait for new documents to be indexed
	time.Sleep(1 * time.Second)
	// Try to update a consent  (must pass)
	do("PUT", "/api/sge/consent/1", sgeApiHeader, `{"serviceId":123456}`, http.StatusOK, `{"ID":1`)
	// Try to get a consent that doesn't exist (must fail not found)
	do("GET", "/api/sge/consent/123456", sgeApiHeader, "", http.StatusNotFound, `consent not found`)
	// Try to get a consent  (must pass)
	do("GET", "/api/sge/consent/1", sgeApiHeader, "", http.StatusOK, `{"ID":1`)

	// Try to login (must pass)
	do("GET", "/OAuth2Login", noH, "", http.StatusOK, "")

	response := do("GET", "/api/common/WhoAmI", noH, "", http.StatusOK, "")
	token := auth.TokenData{}
	json.Unmarshal([]byte(response), &token)
	xsrfHeader := map[string]string{"XSRF-TOKEN": token.XSRFToken}

	// Try to get first 50 consents  (must pass)
	do("GET", "/api/admin/consent?limit=50&page=0", xsrfHeader, "", http.StatusOK, `{"totalRows":1,"rows":[{"ID":1`)
	// Try to search for a consent  (must pass)
	do("GET", "/api/admin/consent?search=123456", xsrfHeader, "", http.StatusOK, `[{"ID":1`)
	// Try to delete a consent  (must pass)
	do("DELETE", "/api/sge/consent/1", sgeApiHeader, "", http.StatusOK, "")

}

func createTester(t *testing.T) (*httptest.Server, tester.DoFn, tester.DoFn) {
	// Create the server
	mux := CreateRootMux()
	ts := httptest.NewServer(mux.Router)
	url, _ := url.Parse(ts.URL)
	port := url.Port()
	mux.Manager.Config.RedirectURL = "http://" + os.Getenv("HOSTNAME") + ":" + port + "/OAuth2Callback"
	mux.Manager.Hostname = "http://" + os.Getenv("HOSTNAME") + ":" + port
	// Create the cookie jar
	jar, _ := cookiejar.New(nil)
	// wrap the testing function
	return ts, tester.CreateServerTester(t, port, os.Getenv("HOSTNAME"), jar), tester.CreateServerTester(t, port, os.Getenv("HOSTNAME"), nil)
}