package controllers

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"testing"
	"time"

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

	"GoMembership/internal/config"
	"GoMembership/internal/constants"
	"GoMembership/internal/middlewares"
	"GoMembership/internal/models"
	"GoMembership/internal/repositories"
	"GoMembership/internal/utils"
	"GoMembership/pkg/logger"

	"github.com/golang-jwt/jwt/v5"
)

type RegisterUserTest struct {
	WantDBData   map[string]interface{}
	Name         string
	Input        string
	WantResponse int
	Assert       bool
}

var jwtSigningMethod = jwt.SigningMethodHS256

func (rt *RegisterUserTest) SetupContext() (*gin.Context, *httptest.ResponseRecorder, *gin.Engine) {
	return GetMockedJSONContext([]byte(rt.Input), "register")
}

func (rt *RegisterUserTest) RunHandler(c *gin.Context, router *gin.Engine) {
	Uc.RegisterUser(c)
}

func (rt *RegisterUserTest) ValidateResponse(w *httptest.ResponseRecorder) error {
	if w.Code != rt.WantResponse {
		responseBody, _ := io.ReadAll(w.Body)
		return fmt.Errorf("Register User: Didn't get the expected response code: got: %v; expected: %v. Context: %#v", w.Code, rt.WantResponse, string(responseBody))
	}
	return nil
}

func (rt *RegisterUserTest) ValidateResult() error {
	return validateUser(rt.Assert, rt.WantDBData)
}

func testUserController(t *testing.T) {

	tests := getTestUsers()
	for _, tt := range tests {
		logger.Error.Print("==============================================================")
		logger.Error.Printf("Register User Testing : %v", tt.Name)
		logger.Error.Print("==============================================================")
		t.Run(tt.Name, func(t *testing.T) {
			if err := runSingleTest(&tt); err != nil {
				t.Fatalf("Test failed: %v", err.Error())
			}
		})
	}

	loginEmail, loginCookie := testLoginHandler(t)
	logoutCookie := testCurrentUserHandler(t, loginEmail, loginCookie)
	testUpdateUser(t, loginCookie)
	testLogoutHandler(t, logoutCookie)
}

func testLogoutHandler(t *testing.T, loginCookie http.Cookie) {

	tests := []struct {
		name           string
		setupCookie    func(*http.Request)
		expectedStatus int
	}{
		{
			name: "Logout with valid cookie",
			setupCookie: func(req *http.Request) {
				req.AddCookie(&loginCookie)
			},
			expectedStatus: http.StatusOK,
		},
		{
			name:           "Logout without cookie",
			setupCookie:    func(req *http.Request) {},
			expectedStatus: http.StatusOK, // Logout should still succeed even without a cookie
		},
	}

	for _, tt := range tests {
		logger.Error.Print("==============================================================")
		logger.Error.Printf("Logout User Testing : %v", tt.name)
		logger.Error.Print("==============================================================")
		t.Run(tt.name, func(t *testing.T) {
			gin.SetMode(gin.TestMode)
			router := gin.New()
			router.POST("/logout", Uc.LogoutHandler)

			w := httptest.NewRecorder()
			req, _ := http.NewRequest("POST", "/logout", nil)
			tt.setupCookie(req)

			router.ServeHTTP(w, req)

			assert.Equal(t, tt.expectedStatus, w.Code)

			var response map[string]string
			err := json.Unmarshal(w.Body.Bytes(), &response)
			assert.NoError(t, err)
			assert.Equal(t, "Logged out successfully", response["message"])

			// Check if the cookie has been cleared
			var logoutCookie *http.Cookie
			for _, cookie := range w.Result().Cookies() {
				if cookie.Name == "jwt" {
					logoutCookie = cookie
					break
				}
			}
			assert.NotNil(t, logoutCookie, "Logout should set a clearing cookie")
			assert.Equal(t, "", logoutCookie.Value, "Logout cookie should have empty value")
			assert.True(t, logoutCookie.Expires.Before(time.Now()), "Logout cookie should be expired")

			// Verify that the user can no longer access protected routes
			w = httptest.NewRecorder()
			req, _ = http.NewRequest("GET", "/current", nil)
			if logoutCookie != nil {
				req.AddCookie(logoutCookie)
			}
			router.GET("/current", middlewares.AuthMiddleware(), Uc.CurrentUserHandler)
			router.ServeHTTP(w, req)
			assert.Equal(t, http.StatusUnauthorized, w.Code, "User should not be able to access protected routes after logout")
		})
	}
}

func testLoginHandler(t *testing.T) (string, http.Cookie) {
	// This test should run after the user registration test
	var loginCookie http.Cookie
	var loginInput loginInput
	t.Run("LoginHandler", func(t *testing.T) {
		// Test cases
		tests := []struct {
			name           string
			input          string
			wantStatusCode int
			wantToken      bool
		}{
			{
				name: "Valid login",
				input: `{
                    "email": "john.doe@example.com",
                    "password": "password123"
                }`,
				wantStatusCode: http.StatusOK,
				wantToken:      true,
			},
			{
				name: "Invalid email",
				input: `{
                    "email": "nonexistent@example.com",
                    "password": "password123"
                }`,
				wantStatusCode: http.StatusNotFound,
				wantToken:      false,
			},
			{
				name: "Invalid password",
				input: `{
                    "email": "john.doe@example.com",
                    "password": "wrongpassword"
                }`,
				wantStatusCode: http.StatusNotAcceptable,
				wantToken:      false,
			},
		}

		for _, tt := range tests {
			logger.Error.Print("==============================================================")
			logger.Error.Printf("Testing : %v", tt.name)
			logger.Error.Print("==============================================================")
			t.Run(tt.name, func(t *testing.T) {
				// Setup
				c, w, _ := GetMockedJSONContext([]byte(tt.input), "/login")

				// Execute
				Uc.LoginHandler(c)

				// Assert
				assert.Equal(t, tt.wantStatusCode, w.Code)

				var response map[string]interface{}
				err := json.Unmarshal(w.Body.Bytes(), &response)
				assert.NoError(t, err)

				if tt.wantToken {
					assert.Contains(t, response, "message")
					assert.Equal(t, "Login successful", response["message"])
					for _, cookie := range w.Result().Cookies() {
						if cookie.Name == "jwt" {
							loginCookie = *cookie

							err = json.Unmarshal([]byte(tt.input), &loginInput)
							assert.NoError(t, err, "Failed to unmarshal input JSON")

							break
						}
					}
					assert.NotEmpty(t, loginCookie)
				} else {
					assert.Contains(t, response, "errors")
					assert.NotEmpty(t, response["errors"])
				}
			})

		}
	})
	return loginInput.Email, loginCookie
}

func testCurrentUserHandler(t *testing.T, loginEmail string, loginCookie http.Cookie) http.Cookie {
	// This test should run after the user login test
	invalidCookie := http.Cookie{
		Name:  "jwt",
		Value: "invalid.token.here",
	}
	tests := []struct {
		name             string
		setupCookie      func(*http.Request)
		expectedUserMail string
		expectedStatus   int
		expectNewCookie  bool
		expectedErrors   []map[string]string
	}{
		{
			name: "With valid cookie",
			setupCookie: func(req *http.Request) {
				req.AddCookie(&loginCookie)
			},
			expectedUserMail: loginEmail,
			expectedStatus:   http.StatusOK,
		},
		{
			name: "With valid expired cookie",
			setupCookie: func(req *http.Request) {
				sessionID := "test-session"
				token := jwt.NewWithClaims(jwtSigningMethod, jwt.MapClaims{
					"user_id":    1,
					"role_id":    0,
					"session_id": sessionID,
					"exp":        time.Now().Add(-time.Hour).Unix(), // Expired 1 hour ago
				})
				tokenString, _ := token.SignedString([]byte(config.Auth.JWTSecret))
				req.AddCookie(&http.Cookie{Name: "jwt", Value: tokenString})
				middlewares.UpdateSession(sessionID, 1) // Add a valid session
			},
			expectedUserMail: config.Recipients.AdminEmail,
			expectedStatus:   http.StatusOK,
			expectNewCookie:  true,
		},
		{
			name:           "Without cookie",
			setupCookie:    func(req *http.Request) {},
			expectedStatus: http.StatusUnauthorized,
			expectedErrors: []map[string]string{
				{"field": "general", "key": "server.error.no_auth_token"},
			},
		},
		{
			name: "With invalid cookie",
			setupCookie: func(req *http.Request) {
				req.AddCookie(&invalidCookie)
			},
			expectedStatus: http.StatusUnauthorized,
			expectedErrors: []map[string]string{
				{"field": "general", "key": "server.error.no_auth_token"},
			},
		},
	}

	for _, tt := range tests {
		logger.Error.Print("==============================================================")
		logger.Error.Printf("CurrentUser Testing : %v", tt.name)
		logger.Error.Print("==============================================================")
		t.Run(tt.name, func(t *testing.T) {

			gin.SetMode(gin.TestMode)
			router := gin.New()
			router.Use(middlewares.AuthMiddleware())
			router.GET("/current", Uc.CurrentUserHandler)

			w := httptest.NewRecorder()
			req, _ := http.NewRequest("GET", "/current", nil)
			tt.setupCookie(req)

			router.ServeHTTP(w, req)

			assert.Equal(t, tt.expectedStatus, w.Code)

			if tt.expectedStatus == http.StatusOK {
				var response struct {
					User          models.User                `json:"user"`
					Subscriptions []models.SubscriptionModel `json:"subscriptions"`
				}
				err := json.Unmarshal(w.Body.Bytes(), &response)
				assert.NoError(t, err)
				// logger.Error.Printf("response: %#v", response)
				assert.Equal(t, tt.expectedUserMail, response.User.Email)
				var newCookie *http.Cookie
				for _, cookie := range w.Result().Cookies() {
					if cookie.Name == "jwt" {
						newCookie = cookie
						break
					}
				}
				if tt.expectNewCookie {
					assert.NotNil(t, newCookie, "New cookie should be set for expired token")
					assert.NotEqual(t, loginCookie.Value, newCookie.Value, "Cookie value should be different")
					assert.True(t, newCookie.MaxAge > 0, "New cookie should not be expired")
				} else {
					assert.Nil(t, newCookie, "No new cookie should be set for non-expired token")
				}
			} else {
				// For unauthorized requests, check for the new error structure
				var errorResponse map[string]interface{}
				err := json.Unmarshal(w.Body.Bytes(), &errorResponse)
				assert.NoError(t, err)

				errors, ok := errorResponse["errors"].([]interface{})
				assert.True(t, ok, "Expected 'errors' field in response")
				assert.Len(t, errors, len(tt.expectedErrors), "Unexpected number of errors")

				for i, expectedError := range tt.expectedErrors {
					if i < len(errors) {
						actualError := errors[i].(map[string]interface{})
						assert.Equal(t, expectedError["field"], actualError["field"], "Mismatched error field")
						assert.Equal(t, expectedError["key"], actualError["key"], "Mismatched error key")
					}
				}
			}

		})
	}
	return loginCookie
}

func validateUser(assert bool, wantDBData map[string]interface{}) error {
	users, err := Uc.Service.GetUsers(wantDBData)
	if err != nil {
		return fmt.Errorf("Error in database ops: %#v", err)
	}

	if assert != (len(*users) != 0) {
		return fmt.Errorf("User entry query didn't met expectation: %v != %#v", assert, *users)
	}
	if assert {
		user := (*users)[0]
		// Check for mandate reference
		if user.BankAccount.MandateReference == "" {
			return fmt.Errorf("Mandate reference not generated for user: %s", user.Email)
		}

		// Validate mandate reference format
		expected := user.GenerateMandateReference()
		if !strings.HasPrefix(user.BankAccount.MandateReference, expected) {
			return fmt.Errorf("Mandate reference is invalid. Expected: %s, Got: %s", expected, user.BankAccount.MandateReference)
		}

		//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.MailRegistrationSubject) {
				if err := checkRegistrationMail(mail, &user); err != nil {
					logger.Error.Printf("Error in checkRegistrationMail: %#v", err)
					return err
				}
			} else if strings.Contains(mail.Subject, constants.MailVerificationSubject) {
				if err := checkVerificationMail(mail, &user); err != nil {
					logger.Error.Printf("Error in checkVerificationMail: %#v", err)
					return err
				}
				verifiedUsers, err := Uc.Service.GetUsers(wantDBData)
				if err != nil {
					logger.Error.Printf("Error in GetUsers: %#v", err)
					return err
				}
				if (*verifiedUsers)[0].Status != constants.VerifiedStatus {
					return fmt.Errorf("Users(%v) status isn't verified after email verification. Status is: %v", (*verifiedUsers)[0].Email, (*verifiedUsers)[0].Status)
				}
			} else {
				return fmt.Errorf("Subject not expected: %v", mail.Subject)
			}
		}
	}
	return nil
}

func testUpdateUser(t *testing.T, loginCookie http.Cookie) {

	invalidCookie := http.Cookie{
		Name:  "jwt",
		Value: "invalid.token.here",
	}
	// Get the user we just created
	users, err := Uc.Service.GetUsers(map[string]interface{}{"email": "john.doe@example.com"})
	if err != nil || len(*users) == 0 {
		t.Fatalf("Failed to get test user: %v", err)
	}
	user := (*users)[0]
	if user.Licence == nil {
		user.Licence = &models.Licence{
			Number:         "Z021AB37X13",
			ExpirationDate: time.Now().UTC().AddDate(1, 0, 0),
			IssuedDate:     time.Now().UTC().AddDate(-1, 0, 0),
			IssuingCountry: "Deutschland",
		}
	}
	tests := []struct {
		name           string
		setupCookie    func(*http.Request)
		updateFunc     func(*models.User)
		expectedStatus int
		expectedErrors []map[string]string
	}{
		{
			name: "Valid Update",
			setupCookie: func(req *http.Request) {
				req.AddCookie(&loginCookie)
			},
			updateFunc: func(u *models.User) {
				u.Password = ""
				u.FirstName = "John Updated"
				u.LastName = "Doe Updated"
				u.Phone = "01738484994"
			},
			expectedStatus: http.StatusAccepted,
		},
		{
			name: "Valid Update, invalid cookie",
			setupCookie: func(req *http.Request) {
				req.AddCookie(&invalidCookie)
			},
			updateFunc: func(u *models.User) {
				u.Password = ""
				u.FirstName = "John Updated"
				u.LastName = "Doe Updated"
				u.Phone = "01738484994"
			},
			expectedStatus: http.StatusUnauthorized,
			expectedErrors: []map[string]string{
				{"field": "general", "key": "server.error.no_auth_token"},
			},
		},
		{
			name: "Invalid Email Update",
			setupCookie: func(req *http.Request) {
				req.AddCookie(&loginCookie)
			},
			updateFunc: func(u *models.User) {
				u.Password = ""
				u.FirstName = "John Updated"
				u.LastName = "Doe Updated"
				u.Phone = "01738484994"
				u.Email = "invalid-email"
			},
			expectedStatus: http.StatusBadRequest,
			expectedErrors: []map[string]string{
				{"field": "Email", "key": "server.validation.email"},
			},
		},
		{
			name: "Change Number",
			setupCookie: func(req *http.Request) {
				req.AddCookie(&loginCookie)
			},
			updateFunc: func(u *models.User) {
				u.Password = ""
				u.FirstName = "John Updated"
				u.LastName = "Doe Updated"
				u.Phone = "01738484994"
				u.Licence.Number = "B072RRE2I50"
			},
			expectedStatus: http.StatusAccepted,
		},
		{
			name: "Add category",
			setupCookie: func(req *http.Request) {
				req.AddCookie(&loginCookie)
			},
			updateFunc: func(u *models.User) {
				u.Password = ""
				u.FirstName = "John Updated"
				u.LastName = "Doe Updated"
				u.Phone = "01738484994"
				u.Licence.Number = "B072RRE2I50"
				var licenceRepo repositories.LicenceInterface = &repositories.LicenceRepository{}
				category, err := licenceRepo.FindCategoryByName("B")
				assert.NoError(t, err)
				u.Licence.Categories = []models.Category{category}
			},
			expectedStatus: http.StatusAccepted,
		},
		{
			name: "Delete 1 and add 1 category",
			setupCookie: func(req *http.Request) {
				req.AddCookie(&loginCookie)
			},
			updateFunc: func(u *models.User) {
				u.Password = ""
				u.FirstName = "John Updated"
				u.LastName = "Doe Updated"
				u.Phone = "01738484994"
				u.Licence.Number = "B072RRE2I50"
				var licenceRepo repositories.LicenceInterface = &repositories.LicenceRepository{}
				category, err := licenceRepo.FindCategoryByName("A")
				category2, err := licenceRepo.FindCategoryByName("BE")
				assert.NoError(t, err)
				u.Licence.Categories = []models.Category{category, category2}
			},
			expectedStatus: http.StatusAccepted,
		},
		{
			name: "Delete 1 category",
			setupCookie: func(req *http.Request) {
				req.AddCookie(&loginCookie)
			},
			updateFunc: func(u *models.User) {
				u.Password = ""
				u.FirstName = "John Updated"
				u.LastName = "Doe Updated"
				u.Phone = "01738484994"
				u.Licence.Number = "B072RRE2I50"
				var licenceRepo repositories.LicenceInterface = &repositories.LicenceRepository{}
				category, err := licenceRepo.FindCategoryByName("A")
				assert.NoError(t, err)
				u.Licence.Categories = []models.Category{category}
			},
			expectedStatus: http.StatusAccepted,
		},
		{
			name: "Delete all categories",
			setupCookie: func(req *http.Request) {
				req.AddCookie(&loginCookie)
			},
			updateFunc: func(u *models.User) {
				u.Password = ""
				u.FirstName = "John Updated"
				u.LastName = "Doe Updated"
				u.Phone = "01738484994"
				u.Licence.Number = "B072RRE2I50"
				u.Licence.Categories = []models.Category{}
			},
			expectedStatus: http.StatusAccepted,
		},
		{
			name: "User ID mismatch while not admin",
			setupCookie: func(req *http.Request) {
				req.AddCookie(&loginCookie)
			},
			updateFunc: func(u *models.User) {
				u.Password = ""
				u.ID = 1
				u.LastName = "Doe Updated"
				u.Phone = "01738484994"
				u.Licence.Number = "B072RRE2I50"
				u.FirstName = "John Missing ID"
			},
			expectedStatus: http.StatusForbidden,
			expectedErrors: []map[string]string{
				{"field": "user.user", "key": "server.error.unauthorized"},
			},
		},
		{
			name: "Password Update",
			setupCookie: func(req *http.Request) {
				req.AddCookie(&loginCookie)
			},
			updateFunc: func(u *models.User) {
				u.Password = ""
				u.LastName = "Doe Updated"
				u.Phone = "01738484994"
				u.Licence.Number = "B072RRE2I50"
				u.Password = "NewPassword"
			},
			expectedStatus: http.StatusAccepted,
		},
		// {
		// 	name: "Non-existent User",
		// 	setupCookie: func(req *http.Request) {
		// 		req.AddCookie(&loginCookie)
		// 	},
		// 	updateFunc: func(u *models.User) {
		// 		u.Password = ""
		// 		u.ID = 99999
		// 		u.FirstName = "Non-existent"
		// 	},
		// 	expectedStatus: http.StatusNotFound,
		// 	expectedError:  "User not found",
		// },
	}
	for _, tt := range tests {
		logger.Error.Print("==============================================================")
		logger.Error.Printf("Update Testing : %v", tt.name)
		logger.Error.Print("==============================================================")
		t.Run(tt.name, func(t *testing.T) {
			// Create a copy of the user and apply the updates
			updatedUser := user
			logger.Error.Printf("user to be updated: %+v", user.Licence)
			tt.updateFunc(&updatedUser)
			// Convert user to JSON

			updateData := &RegistrationData{User: updatedUser}
			jsonData, err := json.Marshal(updateData)
			if err != nil {
				t.Fatalf("Failed to marshal user data: %v", err)
			}

			// logger.Error.Printf("Updated User: %#v", updatedUser)
			// Create request
			req, _ := http.NewRequest("PUT", "/users/"+strconv.FormatUint(uint64(user.ID), 10), bytes.NewBuffer(jsonData))
			req.Header.Set("Content-Type", "application/json")

			tt.setupCookie(req)
			// Create response recorder
			w := httptest.NewRecorder()

			// Set up router and add middleware
			router := gin.New()
			router.Use(middlewares.AuthMiddleware())
			router.PUT("/users/:id", Uc.UpdateHandler)

			// Perform request
			router.ServeHTTP(w, req)

			bodyBytes, _ := io.ReadAll(w.Body)
			t.Logf("Response Body: %s", string(bodyBytes))
			// Check status code
			assert.Equal(t, tt.expectedStatus, w.Code)

			// Parse response
			var response map[string]interface{}
			err = json.Unmarshal(bodyBytes, &response)
			if err != nil {
				t.Fatalf("Failed to unmarshal response body: %v", err)
			}

			if tt.expectedErrors != nil {
				errors, ok := response["errors"].([]interface{})
				if !ok {
					t.Fatalf("Expected 'errors' field in response, got: %v", response)
				}
				assert.Len(t, errors, len(tt.expectedErrors), "Unexpected number of errors")
				for i, expectedError := range tt.expectedErrors {
					if i < len(errors) {
						actualError := errors[i].(map[string]interface{})
						assert.Equal(t, expectedError["field"], actualError["field"], "Mismatched error field")
						assert.Equal(t, expectedError["key"], actualError["key"], "Mismatched error key")
					}
				}
			} else {
				// Check for success message
				message, ok := response["message"].(string)
				if !ok {
					t.Fatalf("Expected 'message' field in response, got: %v", response)
				}
				assert.Equal(t, "User updated successfully", message)

				// Verify the update in the database
				updatedUserFromDB, err := Uc.Service.GetUserByID(user.ID)
				assert.NoError(t, err)

				if updatedUser.Password == "" {
					assert.Equal(t, user.Password, (*updatedUserFromDB).Password)
				} else {
					assert.NotEqual(t, user.Password, (*updatedUserFromDB).Password)
				}

				updatedUserFromDB.Password = ""
				updatedUser.Password = ""
				assert.Equal(t, updatedUser.FirstName, updatedUserFromDB.FirstName, "FirstName mismatch")
				assert.Equal(t, updatedUser.LastName, updatedUserFromDB.LastName, "LastName mismatch")
				assert.Equal(t, updatedUser.Email, updatedUserFromDB.Email, "Email mismatch")
				assert.Equal(t, updatedUser.DateOfBirth, updatedUserFromDB.DateOfBirth, "DateOfBirth mismatch")
				assert.Equal(t, updatedUser.Company, updatedUserFromDB.Company, "Company mismatch")
				assert.Equal(t, updatedUser.Phone, updatedUserFromDB.Phone, "Phone mismatch")
				assert.Equal(t, updatedUser.Notes, updatedUserFromDB.Notes, "Notes mismatch")
				assert.Equal(t, updatedUser.ProfilePicture, updatedUserFromDB.ProfilePicture, "ProfilePicture mismatch")
				assert.Equal(t, updatedUser.Address, updatedUserFromDB.Address, "Address mismatch")
				assert.Equal(t, updatedUser.ZipCode, updatedUserFromDB.ZipCode, "ZipCode mismatch")
				assert.Equal(t, updatedUser.City, updatedUserFromDB.City, "City mismatch")
				assert.Equal(t, updatedUser.PaymentStatus, updatedUserFromDB.PaymentStatus, "PaymentStatus mismatch")
				assert.Equal(t, updatedUser.Status, updatedUserFromDB.Status, "Status mismatch")
				assert.Equal(t, updatedUser.RoleID, updatedUserFromDB.RoleID, "RoleID mismatch")

				// For nested structs, you might want to compare individual fields
				assert.Equal(t, updatedUser.BankAccount.Bank, updatedUserFromDB.BankAccount.Bank, "BankAccount.Bank mismatch")
				assert.Equal(t, updatedUser.BankAccount.AccountHolderName, updatedUserFromDB.BankAccount.AccountHolderName, "BankAccount.AccountHolderName mismatch")
				assert.Equal(t, updatedUser.BankAccount.IBAN, updatedUserFromDB.BankAccount.IBAN, "BankAccount.IBAN mismatch")
				assert.Equal(t, updatedUser.BankAccount.BIC, updatedUserFromDB.BankAccount.BIC, "BankAccount.BIC mismatch")
				assert.Equal(t, updatedUser.BankAccount.MandateReference, updatedUserFromDB.BankAccount.MandateReference, "BankAccount.MandateReference mismatch")

				assert.Equal(t, updatedUser.Membership.StartDate, updatedUserFromDB.Membership.StartDate, "Membership.StartDate mismatch")
				assert.Equal(t, updatedUser.Membership.EndDate, updatedUserFromDB.Membership.EndDate, "Membership.EndDate mismatch")
				assert.Equal(t, updatedUser.Membership.Status, updatedUserFromDB.Membership.Status, "Membership.Status mismatch")
				assert.Equal(t, updatedUser.Membership.SubscriptionModelID, updatedUserFromDB.Membership.SubscriptionModelID, "Membership.SubscriptionModelID mismatch")
				assert.Equal(t, updatedUser.Membership.ParentMembershipID, updatedUserFromDB.Membership.ParentMembershipID, "Membership.ParentMembershipID mismatch")

				assert.Equal(t, updatedUser.Licence.Status, updatedUserFromDB.Licence.Status, "Licence.Status mismatch")
				assert.Equal(t, updatedUser.Licence.Number, updatedUserFromDB.Licence.Number, "Licence.Number mismatch")
				assert.Equal(t, updatedUser.Licence.IssuedDate, updatedUserFromDB.Licence.IssuedDate, "Licence.IssuedDate mismatch")
				assert.Equal(t, updatedUser.Licence.ExpirationDate, updatedUserFromDB.Licence.ExpirationDate, "Licence.ExpirationDate mismatch")
				assert.Equal(t, updatedUser.Licence.IssuingCountry, updatedUserFromDB.Licence.IssuingCountry, "Licence.IssuingCountry mismatch")

				// For slices or more complex nested structures, you might want to use deep equality checks
				assert.ElementsMatch(t, updatedUser.Consents, updatedUserFromDB.Consents, "Consents mismatch")
				if len(updatedUser.Licence.Categories) > 0 {
					for i := range updatedUser.Licence.Categories {
						assert.Equal(t, updatedUser.Licence.Categories[i].Name, updatedUserFromDB.Licence.Categories[i].Name, "Category Category mismatch at index %d", i)
					}
				} else {
					assert.Emptyf(t, updatedUserFromDB.Licence.Categories, "Categories aren't empty when they should")
				}
			}
		})
	}
}

func checkWelcomeMail(message *utils.Email, user *models.User) error {

	if !strings.Contains(message.To, user.Email) {
		return fmt.Errorf("Registration Information didn't reach the user! Recipient was: %v instead of %v", message.To, user.Email)
	}
	if !strings.Contains(message.From, config.SMTP.User) {
		return fmt.Errorf("Registration 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) {
		return fmt.Errorf("User first name(%v) has not been rendered in registration mail.", user.FirstName)
	}
	if !strings.Contains(message.Body, fmt.Sprintf("Preis/Monat</strong>: %v", user.Membership.SubscriptionModel.MonthlyFee)) {
		return fmt.Errorf("Users monthly subscription fee(%v) has not been rendered in registration mail.", user.Membership.SubscriptionModel.MonthlyFee)
	}
	if !strings.Contains(message.Body, fmt.Sprintf("Preis/h</strong>: %v", user.Membership.SubscriptionModel.HourlyRate)) {
		return fmt.Errorf("Users hourly subscription fee(%v) has not been rendered in registration mail.", user.Membership.SubscriptionModel.HourlyRate)
	}
	if user.Company != "" && !strings.Contains(message.Body, user.Company) {
		return fmt.Errorf("Users Company(%v) has not been rendered in registration mail.", user.Company)
	}
	if !strings.Contains(message.Body, fmt.Sprintf("Mitgliedsnummer</strong>: %v", user.Membership.ID)) {
		return fmt.Errorf("Users membership Id(%v) has not been rendered in registration mail.", user.Membership.ID)
	}
	if !strings.Contains(message.Body, config.Site.BaseURL) {
		return fmt.Errorf("Base Url (%v) has not been rendered in registration mail.", config.Site.BaseURL)
	}
	if !strings.Contains(message.Body, config.Site.BaseURL+config.Templates.LogoURI) {
		return fmt.Errorf("Logo Url (%v) has not been rendered in registration mail.", config.Site.BaseURL+config.Site.WebsiteTitle)
	}
	if !strings.Contains(message.Body, config.Site.WebsiteTitle) {
		return fmt.Errorf("Website title (%v) has not been rendered in registration mail.", config.Site.WebsiteTitle)
	}
	return nil
}

func checkRegistrationMail(message *utils.Email, user *models.User) error {

	if !strings.Contains(message.To, config.Recipients.UserRegistration) {
		return fmt.Errorf("Registration Information didn't reach admin! Recipient was: %v instead of %v", message.To, config.Recipients.UserRegistration)
	}
	if !strings.Contains(message.From, config.SMTP.User) {
		return fmt.Errorf("Registration 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 registration mail.", user.FirstName+" "+user.LastName)
	}
	if !strings.Contains(message.Body, fmt.Sprintf("Preis/Monat</strong>: %v", user.Membership.SubscriptionModel.MonthlyFee)) {
		return fmt.Errorf("Users monthly subscription fee(%v) has not been rendered in registration mail.", user.Membership.SubscriptionModel.MonthlyFee)
	}
	if !strings.Contains(message.Body, fmt.Sprintf("Preis/h</strong>: %v", user.Membership.SubscriptionModel.HourlyRate)) {
		return fmt.Errorf("Users hourly subscription fee(%v) has not been rendered in registration mail.", user.Membership.SubscriptionModel.HourlyRate)
	}
	if user.Company != "" && !strings.Contains(message.Body, user.Company) {
		return fmt.Errorf("Users Company(%v) has not been rendered in registration mail.", user.Company)
	}
	if !strings.Contains(message.Body, fmt.Sprintf("Mitgliedsnr:</strong> %v", user.Membership.ID)) {
		return fmt.Errorf("Users membership Id(%v) has not been rendered in registration mail.", user.Membership.ID)
	}
	if !strings.Contains(message.Body, user.Address+","+user.ZipCode) {
		return fmt.Errorf("Users address(%v) has not been rendered in registration mail.", user.Address+" sv,"+user.ZipCode)
	}
	if !strings.Contains(message.Body, user.City) {
		return fmt.Errorf("Users city(%v) has not been rendered in registration mail.", user.City)
	}
	if !strings.Contains(message.Body, user.DateOfBirth.Format("20060102")) {
		return fmt.Errorf("Users birthday(%v) has not been rendered in registration mail.", user.DateOfBirth.Format("20060102"))
	}
	if !strings.Contains(message.Body, "Email:</strong> "+user.Email) {
		return fmt.Errorf("Users email(%v) has not been rendered in registration mail.", user.Email)
	}
	if !strings.Contains(message.Body, user.Phone) {
		return fmt.Errorf("Users phone(%v) has not been rendered in registration mail.", user.Phone)
	}
	if !strings.Contains(message.Body, user.BankAccount.IBAN) {
		return fmt.Errorf("Users IBAN(%v) has not been rendered in registration mail.", user.BankAccount.IBAN)
	}
	if !strings.Contains(message.Body, config.Site.BaseURL) {
		return fmt.Errorf("Base Url (%v) has not been rendered in registration mail.", config.Site.BaseURL)
	}
	return nil
}

func checkVerificationMail(message *utils.Email, user *models.User) error {
	if !strings.Contains(message.To, user.Email) {
		return fmt.Errorf("Registration Information didn't reach client! Recipient was: %v instead of %v", message.To, user.Email)
	}
	verificationURL, err := getVerificationURL(message.Body)
	if err != nil {
		return fmt.Errorf("Error parsing verification URL: %#v", err.Error())
	}
	if !strings.Contains(verificationURL, user.Verification.VerificationToken) {
		return fmt.Errorf("Users Verification link token(%v) has not been rendered in email verification mail. %v", user.Verification.VerificationToken, verificationURL)
	}

	if !strings.Contains(message.Body, config.Site.BaseURL) {
		return fmt.Errorf("Base Url (%v) has not been rendered in email verification mail.", config.Site.BaseURL)
	}
	// open the provided link:
	if err := verifyMail(verificationURL); err != nil {
		return err
	}
	messages := utils.SMTPGetMessages()
	for _, message := range messages {
		mail, err := utils.DecodeMail(message.MsgRequest())
		if err != nil {
			return err
		}
		if err := checkWelcomeMail(mail, user); err != nil {
			return err
		}
	}
	return nil

}

func verifyMail(verificationURL string) error {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.LoadHTMLGlob(filepath.Join(config.Templates.HTMLPath, "*"))

	router.GET("/users/verify", Uc.VerifyMailHandler)
	wv := httptest.NewRecorder()
	cv, _ := gin.CreateTestContext(wv)
	var err error
	cv.Request, err = http.NewRequest("GET", verificationURL, nil)
	if err != nil {
		return fmt.Errorf("Failed to create new GET Request: %v", err.Error())
	}
	router.ServeHTTP(wv, cv.Request)
	if wv.Code != 200 {

		responseBody, _ := io.ReadAll(wv.Body)

		return fmt.Errorf("VerifyMail: Didn't get the expected response code: got: %v; expected: %v Context: %#v", wv.Code, 200, string(responseBody))
	}
	return nil
}

func getVerificationURL(mailBody string) (string, error) {
	re := regexp.MustCompile(`"([^"]*verify[^"]*)"`)

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

// TEST DATA:

func customizeInput(customize func(models.User) models.User) *RegistrationData {
	user := getBaseUser()
	user = customize(user) // Apply the customization
	return &RegistrationData{User: user}
}

func getTestUsers() []RegisterUserTest {
	return []RegisterUserTest{
		{
			Name:         "birthday < 18 should fail",
			WantResponse: http.StatusBadRequest,
			WantDBData:   map[string]interface{}{"email": "john.doe@example.com"},
			Assert:       false,
			Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
				user.DateOfBirth = time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)
				return user
			})),
		},
		{
			Name:         "FirstName empty, should fail",
			WantResponse: http.StatusBadRequest,
			WantDBData:   map[string]interface{}{"email": "john.doe@example.com"},
			Assert:       false,
			Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
				user.FirstName = ""
				return user
			})),
		},
		{
			Name:         "LastName Empty should fail",
			WantResponse: http.StatusBadRequest,
			WantDBData:   map[string]interface{}{"email": "john.doe@example.com"},
			Assert:       false,
			Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
				user.LastName = ""
				return user
			})),
		},
		{
			Name:         "EMail wrong format should fail",
			WantResponse: http.StatusBadRequest,
			WantDBData:   map[string]interface{}{"email": "johnexample.com"},
			Assert:       false,
			Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
				user.Email = "johnexample.com"
				return user
			})),
		},
		{
			Name:         "Missing Zip Code should fail",
			WantResponse: http.StatusBadRequest,
			WantDBData:   map[string]interface{}{"email": "john.doe@example.com"},
			Assert:       false,
			Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
				user.ZipCode = ""
				return user
			})),
		},
		{
			Name:         "Missing Address should fail",
			WantResponse: http.StatusBadRequest,
			WantDBData:   map[string]interface{}{"email": "john.doe@example.com"},
			Assert:       false,
			Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
				user.Address = ""
				return user
			})),
		},
		{
			Name:         "Missing City should fail",
			WantResponse: http.StatusBadRequest,
			WantDBData:   map[string]interface{}{"email": "john.doe@example.com"},
			Assert:       false,
			Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
				user.City = ""
				return user
			})),
		},
		{
			Name:         "Missing IBAN should fail",
			WantResponse: http.StatusBadRequest,
			WantDBData:   map[string]interface{}{"email": "john.doe@example.com"},
			Assert:       false,
			Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
				user.BankAccount.IBAN = ""
				return user
			})),
		},
		{
			Name:         "Invalid IBAN should fail",
			WantResponse: http.StatusBadRequest,
			WantDBData:   map[string]interface{}{"email": "john.doe@example.com"},
			Assert:       false,
			Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
				user.BankAccount.IBAN = "DE1234234123134"
				return user
			})),
		},
		{
			Name:         "Missing subscription plan should fail",
			WantResponse: http.StatusBadRequest,
			WantDBData:   map[string]interface{}{"email": "john.doe@example.com"},
			Assert:       false,
			Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
				user.Membership.SubscriptionModel.Name = ""
				return user
			})),
		},
		{
			Name:         "Invalid subscription plan should fail",
			WantResponse: http.StatusBadRequest,
			WantDBData:   map[string]interface{}{"email": "john.doe@example.com"},
			Assert:       false,
			Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
				user.Membership.SubscriptionModel.Name = "NOTEXISTENTPLAN"
				return user
			})),
		},
		{
			Name:         "Correct Entry should pass",
			WantResponse: http.StatusCreated,
			WantDBData:   map[string]interface{}{"email": "john.doe@example.com"},
			Assert:       true,
			Input:        GenerateInputJSON(customizeInput(func(user models.User) models.User { return user })),
		},
		{
			Name:         "Email duplicate should fail",
			WantResponse: http.StatusConflict,
			WantDBData:   map[string]interface{}{"first_name": "Jane"},
			Assert:       false,
			Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
				user.FirstName = "Jane"
				return user
			})),
		},
		{
			Name:         "Company present should pass",
			WantResponse: http.StatusCreated,
			WantDBData:   map[string]interface{}{"email": "john.doe2@example.com"},
			Assert:       true,
			Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
				user.Email = "john.doe2@example.com"
				user.Company = "ACME"
				return user
			})),
		},
		{
			Name:         "Subscription constraints not entered; should fail",
			WantResponse: http.StatusBadRequest,
			WantDBData:   map[string]interface{}{"email": "john.junior.doe@example.com"},
			Assert:       false,
			Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
				user.Email = "john.junior.doe@example.com"
				user.Membership.SubscriptionModel.Name = "additional"
				return user
			})),
		},
		{
			Name:         "Subscription constraints wrong; should fail",
			WantResponse: http.StatusBadRequest,
			WantDBData:   map[string]interface{}{"email": "john.junior.doe@example.com"},
			Assert:       false,
			Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
				user.Email = "john.junior.doe@example.com"
				user.Membership.ParentMembershipID = 200
				user.Membership.SubscriptionModel.Name = "additional"
				return user
			})),
		},
		{
			Name:         "Subscription constraints correct, should pass",
			WantResponse: http.StatusCreated,
			WantDBData:   map[string]interface{}{"email": "john.junior.doe@example.com"},
			Assert:       true,
			Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
				user.Email = "john.junior.doe@example.com"
				user.Membership.ParentMembershipID = 1
				user.Membership.SubscriptionModel.Name = "additional"
				return user
			})),
		},
		{
			Name:         "Correct Entry with Mandate Reference",
			WantResponse: http.StatusCreated,
			WantDBData:   map[string]interface{}{"email": "john.mandate@example.com"},
			Assert:       true,
			Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
				user.Email = "john.mandate@example.com"
				user.BankAccount.IBAN = "DE89370400440532013000"
				return user
			})),
		},
		// Currently unsupported. My number wouldn't match, though it should.
		// {
		// 	Name:         "wrong driverslicence number, should fail",
		// 	WantResponse: http.StatusBadRequest,
		// 	WantDBData:   map[string]interface{}{"email": "john.wronglicence.doe@example.com"},
		// 	Assert:       false,
		// 	Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
		// 		user.Email = "john.wronglicence.doe@example.com"
		// 		user.Licence = &models.Licence{
		// 			Number:         "AAAA12345AA",
		// 			ExpirationDate: time.Now().AddDate(1, 0, 0),
		// 			IssuedDate:     time.Now().AddDate(-1, 0, 0),
		// 		}
		// 		return user
		// 	})),
		// },
		{
			Name:         "empty driverslicence number, should fail",
			WantResponse: http.StatusBadRequest,
			WantDBData:   map[string]interface{}{"email": "john.wronglicence.doe@example.com"},
			Assert:       false,
			Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
				user.Email = "john.wronglicence.doe@example.com"
				user.Licence = &models.Licence{
					Number:         "",
					ExpirationDate: time.Now().AddDate(1, 0, 0),
					IssuedDate:     time.Now().AddDate(-1, 0, 0),
				}
				return user
			})),
		},
		{
			Name:         "Correct Licence number, should pass",
			WantResponse: http.StatusCreated,
			WantDBData:   map[string]interface{}{"email": "john.correctLicenceNumber@example.com"},
			Assert:       true,
			Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
				user.Email = "john.correctLicenceNumber@example.com"
				user.Licence = &models.Licence{
					Number:         "B072RRE2I55",
					ExpirationDate: time.Now().AddDate(1, 0, 0),
					IssuedDate:     time.Now().AddDate(-1, 0, 0),
				}
				return user
			})),
		},
	}
}
