package controllers

import (
	"GoMembership/internal/config"
	"GoMembership/internal/constants"
	"GoMembership/internal/database"
	"GoMembership/internal/models"
	"GoMembership/internal/utils"
	"GoMembership/pkg/logger"
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
	"regexp"
	"strings"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
)

type TestContext struct {
	router   *gin.Engine
	response *httptest.ResponseRecorder
	user     *models.User
}

func setupTestContext() (*TestContext, error) {

	user, err := Uc.Service.GetUserByEmail("john.doe@example.com")
	if err != nil {
		return nil, err
	}
	return &TestContext{
		router:   gin.Default(),
		response: httptest.NewRecorder(),
		user:     user,
	}, nil
}
func testCreatePasswordHandler(t *testing.T, loginCookie http.Cookie, adminCookie http.Cookie) {
	invalidCookie := http.Cookie{
		Name:  "jwt",
		Value: "invalid.token.here",
	}
	tc, err := setupTestContext()
	if err != nil {
		t.Fatal(err)
	}
	tc.router.POST("/password", Uc.CreatePasswordHandler)
	requestBody := map[string]interface{}{
		"user": map[string]interface{}{
			"id": tc.user.ID,
		},
	}

	body, _ := json.Marshal(requestBody)
	t.Run("successful password creation request from admin", func(t *testing.T) {
		req, _ := http.NewRequest("POST", "/password", bytes.NewBuffer(body))
		req.AddCookie(&adminCookie)
		tc.router.ServeHTTP(tc.response, req)
		logger.Error.Printf("Test results for %#v", t.Name())
		assert.Equal(t, http.StatusAccepted, tc.response.Code)
		assert.JSONEq(t, `{"message":"password_change_requested"}`, tc.response.Body.String())
		err = checkEmailDelivery(tc.user, true)
		assert.NoError(t, err)
	})

	// test token and password change
	testChangePassword(t, tc)
	logger.Error.Printf("__________END RESULTS---------")
	tc.response = httptest.NewRecorder()
	t.Run("failed password creation request from member", func(t *testing.T) {
		req, _ := http.NewRequest("POST", "/password", bytes.NewBuffer(body))
		req.AddCookie(&loginCookie)
		tc.router.ServeHTTP(tc.response, req)

		logger.Error.Printf("Test results for %#v", t.Name())
		assert.Equal(t, http.StatusForbidden, tc.response.Code)
		assert.JSONEq(t, `{"errors":[{"field":"user.user","key":"server.error.unauthorized"}]}`, tc.response.Body.String())
		err = checkEmailDelivery(tc.user, false)
		assert.NoError(t, err)
	})
	logger.Error.Printf("__________END RESULTS---------")
	tc.response = httptest.NewRecorder()
	t.Run("failed password creation request for invalid cookie", func(t *testing.T) {
		req, _ := http.NewRequest("POST", "/password", bytes.NewBuffer(body))
		req.AddCookie(&invalidCookie)
		tc.router.ServeHTTP(tc.response, req)

		logger.Error.Printf("Test results for %#v", t.Name())
		assert.Equal(t, http.StatusBadRequest, tc.response.Code)
		assert.Contains(t, tc.response.Body.String(), `server.error.no_auth_token`)
		err = checkEmailDelivery(tc.user, false)
		assert.NoError(t, err)
	})
	logger.Error.Printf("__________END RESULTS---------")
	tc.response = httptest.NewRecorder()
}

func testChangePassword(t *testing.T, tc *TestContext) {
	var verification models.Verification
	result := database.DB.Where("user_id = ? AND type = ?", tc.user.ID, constants.VerificationTypes.Password).First(&verification)
	assert.NoError(t, result.Error)
	logger.Error.Printf("token from db: %#v", verification.VerificationToken)
	requestBody := map[string]interface{}{
		"password": "new-pas9247A@!sword",
		"token":    verification.VerificationToken,
	}
	body, _ := json.Marshal(requestBody)
	tc.router.PUT("/users/:id/password", Uc.ChangePassword)

	tc.response = httptest.NewRecorder()
	t.Run("valid password change", func(t *testing.T) {

		req, _ := http.NewRequest("PUT", fmt.Sprintf("/users/%v/password", tc.user.ID), bytes.NewBuffer(body))
		tc.router.ServeHTTP(tc.response, req)

		assert.Equal(t, http.StatusOK, tc.response.Code)
		assert.JSONEq(t, `{"message":"password_changed"}`, tc.response.Body.String())
	})
	tc.response = httptest.NewRecorder()
	//User should now be deactivated. Should lack privileges.
	t.Run("user lacks privileges", func(t *testing.T) {
		req, _ := http.NewRequest("POST", "/password", bytes.NewBuffer(body))
		tc.router.ServeHTTP(tc.response, req)

		logger.Error.Printf("Test results for %#v", t.Name())
		assert.Equal(t, http.StatusBadRequest, tc.response.Code)
		assert.Contains(t, tc.response.Body.String(), `server.error.no_auth_token`)

	})
	logger.Error.Printf("__________END RESULTS---------")
	t.Run("invalid user ID", func(t *testing.T) {
		req, _ := http.NewRequest("PUT", "/users/invalid/password", nil)
		tc.router.ServeHTTP(tc.response, req)

		assert.Equal(t, http.StatusBadRequest, tc.response.Code)
	})
	t.Run("non existant user ID", func(t *testing.T) {
		req, _ := http.NewRequest("PUT", "/users/999/password", nil)
		tc.router.ServeHTTP(tc.response, req)

		assert.Equal(t, http.StatusBadRequest, tc.response.Code)
	})
}

func checkEmailDelivery(user *models.User, wantsSuccess bool) error {

	//check for email delivery
	messages := utils.SMTPGetMessages()
	for _, message := range messages {
		mail, err := utils.DecodeMail(message.MsgRequest())
		if err != nil {
			logger.Error.Printf("Error in validateUser: %#v", err)
			return err
		}
		if strings.Contains(mail.Subject, constants.MailChangePasswordSubject) || strings.Contains(mail.Subject, constants.MailGrantBackendAccessSubject) {

			if err := checkPasswordMail(mail, user); err != nil && wantsSuccess {
				logger.Error.Printf("Error in checkEmailDelivery mail: %#v", err)
				return err
			}

		} else {
			return fmt.Errorf("Subject not expected: %v", mail.Subject)
		}
	}
	return nil
}

func checkPasswordMail(message *utils.Email, user *models.User) error {
	var verification models.Verification
	result := database.DB.Where("user_id = ? AND type = ?", user.ID, constants.VerificationTypes.Password).First(&verification)
	if result.Error != nil {
		return result.Error
	}
	logger.Error.Printf("user id: %v token: %#v", user.ID, verification.VerificationToken)
	re := regexp.MustCompile(`"([^"]*token[^"]*)"`)

	// Find the matching URL in the email content
	match := re.FindStringSubmatch(message.Body)
	if len(match) == 0 {
		return fmt.Errorf("No change Password link found in email body: %#v", message.Body)
	}
	tokenURL, err := url.QueryUnescape(match[1])
	if err != nil {
		return fmt.Errorf("Error decoding URL: %v", err)
	}
	logger.Info.Printf("TokenURL: %#v", tokenURL)

	if !strings.Contains(message.To, user.Email) {
		return fmt.Errorf("Password Information didn't reach user! Recipient was: %v instead of %v", message.To, user.Email)
	}
	if !strings.Contains(message.From, config.SMTP.User) {
		return fmt.Errorf("Password Information was sent from unexpected address! Sender was: %v instead of %v", message.From, config.SMTP.User)
	}
	//Check if all the relevant data has been passed to the mail.
	if !strings.Contains(message.Body, user.FirstName+" "+user.LastName) {
		return fmt.Errorf("User first and last name(%v) has not been rendered in password mail.", user.FirstName+" "+user.LastName)
	}
	if !strings.Contains(message.Body, verification.VerificationToken) {
		return fmt.Errorf("Token(%v) has not been rendered in password mail.", verification.VerificationToken)
	}
	if strings.Trim(tokenURL, " ") != fmt.Sprintf("%v%v/auth/password/change/%v?token=%v", config.Site.BaseURL, config.Site.FrontendPath, user.ID, verification.VerificationToken) {
		return fmt.Errorf("Token has not been rendered correctly in password mail: %v%v/auth/password/change/%v?token=%v", config.Site.BaseURL, config.Site.FrontendPath, user.ID, verification.VerificationToken)
	}

	return nil
}
