package main

import (
	"bookhoard/internal/handlers"
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// TestRegisterEndpoint tests the registration endpoint comprehensively
func TestRegisterEndpoint(t *testing.T) {
	testCases := []struct {
		name           string
		payload        handlers.RegisterRequest
		expectedStatus int
		expectedError  string
	}{
		{
			name: "Valid registration with all fields",
			payload: handlers.RegisterRequest{
				Email:     "newuser@example.com",
				Username:  "newuser",
				Password:  "password123",
				FirstName: "John",
				LastName:  "Doe",
			},
			expectedStatus: http.StatusCreated,
		},
		{
			name: "Valid registration with only required fields",
			payload: handlers.RegisterRequest{
				Email:    "minimal@example.com",
				Username: "minimal",
				Password: "password123",
			},
			expectedStatus: http.StatusCreated,
		},
		{
			name: "Registration with role specified",
			payload: handlers.RegisterRequest{
				Email:    "roleuser@example.com",
				Username: "roleuser",
				Password: "password123",
				Role:     "user",
			},
			expectedStatus: http.StatusCreated,
		},
		{
			name: "Invalid email format",
			payload: handlers.RegisterRequest{
				Email:    "invalid-email",
				Username: "invalidemail",
				Password: "password123",
			},
			expectedStatus: http.StatusBadRequest,
			expectedError:  "email",
		},
		{
			name: "Email already exists",
			payload: handlers.RegisterRequest{
				Email:    "existing@example.com",
				Username: "newuser123",
				Password: "password123",
			},
			expectedStatus: http.StatusConflict,
			expectedError:  "email already exists",
		},
		{
			name: "Username already exists",
			payload: handlers.RegisterRequest{
				Email:    "another@example.com",
				Username: "existinguser",
				Password: "password123",
			},
			expectedStatus: http.StatusConflict,
			expectedError:  "username already exists",
		},
		{
			name: "Username too short",
			payload: handlers.RegisterRequest{
				Email:    "short@example.com",
				Username: "ab",
				Password: "password123",
			},
			expectedStatus: http.StatusBadRequest,
			expectedError:  "username",
		},
		{
			name: "Username too long",
			payload: handlers.RegisterRequest{
				Email:    "long@example.com",
				Username: "thisusernameisdefinitelywaytoolongandexceedsfiftycharacters",
				Password: "password123",
			},
			expectedStatus: http.StatusBadRequest,
			expectedError:  "username",
		},
		{
			name: "Password too short",
			payload: handlers.RegisterRequest{
				Email:    "shortpass@example.com",
				Username: "shortpass",
				Password: "12345",
			},
			expectedStatus: http.StatusBadRequest,
			expectedError:  "password",
		},
		{
			name: "Missing required field - email",
			payload: handlers.RegisterRequest{
				Username: "noemail",
				Password: "password123",
			},
			expectedStatus: http.StatusBadRequest,
			expectedError:  "email",
		},
		{
			name: "Missing required field - username",
			payload: handlers.RegisterRequest{
				Email:    "nousername@example.com",
				Password: "password123",
			},
			expectedStatus: http.StatusBadRequest,
			expectedError:  "username",
		},
		{
			name: "Missing required field - password",
			payload: handlers.RegisterRequest{
				Email:    "nopass@example.com",
				Username: "nopass",
			},
			expectedStatus: http.StatusBadRequest,
			expectedError:  "password",
		},
		{
			name: "Invalid role value",
			payload: handlers.RegisterRequest{
				Email:    "invalidrole@example.com",
				Username: "invalidrole",
				Password: "password123",
				Role:     "superadmin",
			},
			expectedStatus: http.StatusBadRequest,
			expectedError:  "role",
		},
		{
			name: "Empty email",
			payload: handlers.RegisterRequest{
				Email:    "",
				Username: "emptyemail",
				Password: "password123",
			},
			expectedStatus: http.StatusBadRequest,
			expectedError:  "email",
		},
		{
			name: "Empty username",
			payload: handlers.RegisterRequest{
				Email:    "emptyuser@example.com",
				Username: "",
				Password: "password123",
			},
			expectedStatus: http.StatusBadRequest,
			expectedError:  "username",
		},
		{
			name: "Empty password",
			payload: handlers.RegisterRequest{
				Email:    "emptypass@example.com",
				Username: "emptypass",
				Password: "",
			},
			expectedStatus: http.StatusBadRequest,
			expectedError:  "password",
		},
		{
			name: "Whitespace-only username",
			payload: handlers.RegisterRequest{
				Email:    "whitespace@example.com",
				Username: "   ",
				Password: "password123",
			},
			expectedStatus: http.StatusBadRequest,
			expectedError:  "username",
		},
		{
			name:           "Empty JSON request body",
			payload:        handlers.RegisterRequest{},
			expectedStatus: http.StatusBadRequest,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			jsonData, err := json.Marshal(tc.payload)
			require.NoError(t, err)

			req, err := http.NewRequest("POST", "/api/auth/register", bytes.NewBuffer(jsonData))
			require.NoError(t, err)
			req.Header.Set("Content-Type", "application/json")

			rr := httptest.NewRecorder()

			handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				var req handlers.RegisterRequest
				if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
					w.WriteHeader(http.StatusBadRequest)
					w.Write([]byte(`{"error":"invalid request"}`))
					return
				}

				email := req.Email
				username := req.Username
				password := req.Password
				role := req.Role

				// Check for required fields
				if email == "" || username == "" || password == "" {
					w.WriteHeader(http.StatusBadRequest)
					w.Write([]byte(`{"error":"email, username, and password are required"}`))
					return
				}

				// Validate email format (basic check)
				if !contains(email, "@") || !contains(email, ".") {
					w.WriteHeader(http.StatusBadRequest)
					w.Write([]byte(`{"error":"email is invalid"}`))
					return
				}

				// Check for duplicate email
				if email == "existing@example.com" {
					w.WriteHeader(http.StatusConflict)
					w.Write([]byte(`{"error":"email already exists"}`))
					return
				}

				// Check for duplicate username
				if username == "existinguser" {
					w.WriteHeader(http.StatusConflict)
					w.Write([]byte(`{"error":"username already exists"}`))
					return
				}

				// Validate username length
				if len(username) < 3 {
					w.WriteHeader(http.StatusBadRequest)
					w.Write([]byte(`{"error":"username must be at least 3 characters"}`))
					return
				}

				if len(username) > 50 {
					w.WriteHeader(http.StatusBadRequest)
					w.Write([]byte(`{"error":"username must be at most 50 characters"}`))
					return
				}

				// Check for whitespace-only username
				if len(trimSpace(username)) == 0 {
					w.WriteHeader(http.StatusBadRequest)
					w.Write([]byte(`{"error":"username cannot be empty or whitespace"}`))
					return
				}

				// Validate password length
				if len(password) < 6 {
					w.WriteHeader(http.StatusBadRequest)
					w.Write([]byte(`{"error":"password must be at least 6 characters"}`))
					return
				}

				// Validate role if provided
				if role != "" && role != "user" && role != "admin" {
					w.WriteHeader(http.StatusBadRequest)
					w.Write([]byte(`{"error":"invalid role. must be 'user' or 'admin'"}`))
					return
				}

				// Successful registration
				w.WriteHeader(http.StatusCreated)
				response := handlers.AuthResponse{
					Token:     "fake-jwt-token-" + uuid.New().String(),
					TokenType: "Bearer",
					ExpiresIn: 3600,
					User: handlers.UserProfile{
						ID:       uuid.New().String(),
						Email:    email,
						Username: username,
						Role:     role,
					},
				}
				json.NewEncoder(w).Encode(response)
			})

			handler.ServeHTTP(rr, req)

			assert.Equal(t, tc.expectedStatus, rr.Code, "Expected status %d, got %d", tc.expectedStatus, rr.Code)

			if tc.expectedError != "" {
				assert.Contains(t, rr.Body.String(), tc.expectedError, "Expected error message to contain '%s'", tc.expectedError)
			}

			if tc.expectedStatus == http.StatusCreated {
				var response handlers.AuthResponse
				err = json.Unmarshal(rr.Body.Bytes(), &response)
				require.NoError(t, err, "Response should match AuthResponse schema")
				assert.NotEmpty(t, response.Token, "Token should not be empty")
				assert.NotEmpty(t, response.User.ID, "User should have an ID")
			}
		})
	}
}

// TestLoginEndpoint tests the login endpoint comprehensively
func TestLoginEndpoint(t *testing.T) {
	testCases := []struct {
		name           string
		payload        map[string]interface{}
		expectedStatus int
		expectedError  string
	}{
		{
			name: "Valid login with email",
			payload: map[string]interface{}{
				"login":    "user@example.com",
				"password": "password123",
			},
			expectedStatus: http.StatusOK,
		},
		{
			name: "Valid login with username",
			payload: map[string]interface{}{
				"login":    "testuser",
				"password": "password123",
			},
			expectedStatus: http.StatusOK,
		},
		{
			name: "Invalid password",
			payload: map[string]interface{}{
				"login":    "user@example.com",
				"password": "wrongpassword",
			},
			expectedStatus: http.StatusUnauthorized,
			expectedError:  "invalid credentials",
		},
		{
			name: "User not found",
			payload: map[string]interface{}{
				"login":    "nonexistent@example.com",
				"password": "password123",
			},
			expectedStatus: http.StatusUnauthorized,
			expectedError:  "invalid credentials",
		},
		{
			name: "Missing login field",
			payload: map[string]interface{}{
				"password": "password123",
			},
			expectedStatus: http.StatusBadRequest,
			expectedError:  "login",
		},
		{
			name: "Missing password field",
			payload: map[string]interface{}{
				"login": "user@example.com",
			},
			expectedStatus: http.StatusBadRequest,
			expectedError:  "password",
		},
		{
			name: "Empty login",
			payload: map[string]interface{}{
				"login":    "",
				"password": "password123",
			},
			expectedStatus: http.StatusBadRequest,
			expectedError:  "login",
		},
		{
			name: "Empty password",
			payload: map[string]interface{}{
				"login":    "user@example.com",
				"password": "",
			},
			expectedStatus: http.StatusBadRequest,
			expectedError:  "password",
		},
		{
			name: "Invalid JSON payload",
			payload: map[string]interface{}{
				"login":    12345,
				"password": "password123",
			},
			expectedStatus: http.StatusBadRequest,
		},
		{
			name:           "Empty request body",
			payload:        map[string]interface{}{},
			expectedStatus: http.StatusBadRequest,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			jsonData, err := json.Marshal(tc.payload)
			require.NoError(t, err)

			req, err := http.NewRequest("POST", "/api/auth/login", bytes.NewBuffer(jsonData))
			require.NoError(t, err)
			req.Header.Set("Content-Type", "application/json")

			rr := httptest.NewRecorder()

			handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				var req map[string]interface{}
				if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
					w.WriteHeader(http.StatusBadRequest)
					w.Write([]byte(`{"error":"invalid request"}`))
					return
				}

				login, _ := req["login"].(string)
				password, _ := req["password"].(string)

				if login == "" || password == "" {
					w.WriteHeader(http.StatusBadRequest)
					w.Write([]byte(`{"error":"login and password are required"}`))
					return
				}

				if login != "user@example.com" && login != "testuser" {
					w.WriteHeader(http.StatusUnauthorized)
					w.Write([]byte(`{"error":"invalid credentials"}`))
					return
				}

				if password != "password123" {
					w.WriteHeader(http.StatusUnauthorized)
					w.Write([]byte(`{"error":"invalid credentials"}`))
					return
				}

				w.WriteHeader(http.StatusOK)
				response := map[string]interface{}{
					"token": "fake-jwt-token",
					"user": map[string]interface{}{
						"id":       uuid.New().String(),
						"email":    login,
						"username": "testuser",
						"role":     "user",
					},
				}
				json.NewEncoder(w).Encode(response)
			})

			handler.ServeHTTP(rr, req)

			assert.Equal(t, tc.expectedStatus, rr.Code, "Expected status %d, got %d", tc.expectedStatus, rr.Code)

			if tc.expectedError != "" {
				assert.Contains(t, rr.Body.String(), tc.expectedError, "Expected error message to contain '%s'", tc.expectedError)
			}

			if tc.expectedStatus == http.StatusOK {
				var response map[string]interface{}
				err = json.Unmarshal(rr.Body.Bytes(), &response)
				require.NoError(t, err)
				assert.NotEmpty(t, response["token"])
				assert.NotNil(t, response["user"])
			}
		})
	}
}
