package handlers

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"gorm.io/gorm"
	"goyco/internal/config"
	"goyco/internal/database"
	"goyco/internal/repositories"
	"goyco/internal/services"
	"goyco/internal/testutils"
)

func newUserHandler(repo repositories.UserRepository) *UserHandler {
	return newUserHandlerWithSender(repo, &testutils.EmailSenderStub{})
}

func newUserHandlerWithSender(repo repositories.UserRepository, sender services.EmailSender) *UserHandler {
	cfg := &config.Config{
		JWT: config.JWTConfig{Secret: "secret", Expiration: 1},
		App: config.AppConfig{BaseURL: "https://test.example.com"},
	}
	mockRefreshRepo := &mockRefreshTokenRepository{}
	authService, err := services.NewAuthFacadeForTest(cfg, repo, nil, nil, mockRefreshRepo, sender)
	if err != nil {
		panic(fmt.Sprintf("Failed to create auth service: %v", err))
	}
	return NewUserHandler(repo, authService)
}

func TestUserHandlerGetUsers(t *testing.T) {
	var limit, offset int
	repo := testutils.NewUserRepositoryStub()
	repo.GetAllFn = func(l, o int) ([]database.User, error) {
		limit, offset = l, o
		return []database.User{{ID: 1}}, nil
	}

	handler := newUserHandler(repo)

	request := httptest.NewRequest(http.MethodGet, "/api/users?limit=5&offset=2", nil)
	recorder := httptest.NewRecorder()

	handler.GetUsers(recorder, request)

	if limit != 5 || offset != 2 {
		t.Fatalf("expected limit=5 offset=2, got %d %d", limit, offset)
	}

	testutils.AssertHTTPStatus(t, recorder, http.StatusOK)
}

func TestUserHandlerGetUser(t *testing.T) {
	repo := testutils.NewUserRepositoryStub()
	handler := newUserHandler(repo)

	request := httptest.NewRequest(http.MethodGet, "/api/users/1", nil)
	recorder := httptest.NewRecorder()
	handler.GetUser(recorder, request)
	testutils.AssertHTTPStatus(t, recorder, http.StatusBadRequest)

	request = httptest.NewRequest(http.MethodGet, "/api/users/abc", nil)
	request = testutils.WithURLParams(request, map[string]string{"id": "abc"})
	recorder = httptest.NewRecorder()
	handler.GetUser(recorder, request)
	testutils.AssertHTTPStatus(t, recorder, http.StatusBadRequest)

	repo.GetByIDFn = func(uint) (*database.User, error) { return nil, gorm.ErrRecordNotFound }
	request = httptest.NewRequest(http.MethodGet, "/api/users/1", nil)
	request = testutils.WithURLParams(request, map[string]string{"id": "1"})
	recorder = httptest.NewRecorder()
	handler.GetUser(recorder, request)
	testutils.AssertHTTPStatus(t, recorder, http.StatusNotFound)

	repo.GetByIDFn = func(id uint) (*database.User, error) {
		return &database.User{ID: id, Username: "user"}, nil
	}
	request = httptest.NewRequest(http.MethodGet, "/api/users/1", nil)
	request = testutils.WithURLParams(request, map[string]string{"id": "1"})
	recorder = httptest.NewRecorder()
	handler.GetUser(recorder, request)
	testutils.AssertHTTPStatus(t, recorder, http.StatusOK)
}

func TestUserHandlerCreateUser(t *testing.T) {
	repo := testutils.NewUserRepositoryStub()
	repo.CreateFn = func(u *database.User) error {
		u.ID = 10
		return nil
	}
	sent := false
	handler := newUserHandlerWithSender(repo, &testutils.EmailSenderStub{SendFn: func(to, subject, body string) error {
		sent = true
		if to != "user@example.com" {
			t.Fatalf("expected email to user@example.com, got %q", to)
		}
		return nil
	}})

	request := createRegisterRequest(`{"username":"user","email":"user@example.com","password":"Password123!"}`)
	recorder := httptest.NewRecorder()
	handler.CreateUser(recorder, request)
	testutils.AssertHTTPStatus(t, recorder, http.StatusCreated)

	var resp UserResponse
	_ = json.NewDecoder(recorder.Body).Decode(&resp)
	data := resp.Data.(map[string]any)
	if !resp.Success {
		t.Fatalf("expected success response")
	}
	if v, ok := data["verification_sent"].(bool); !ok || !v {
		t.Fatalf("expected verification_sent true, got %+v", data["verification_sent"])
	}
	userData := data["user"].(map[string]any)
	if _, ok := userData["password"]; ok {
		t.Fatalf("expected password field to be omitted, got %+v", userData)
	}
	if !sent {
		t.Fatalf("expected verification email to be sent")
	}

	recorder = httptest.NewRecorder()
	request = createRegisterRequest("invalid")
	handler.CreateUser(recorder, request)
	if recorder.Result().StatusCode != http.StatusBadRequest {
		t.Fatalf("expected 400 for invalid json, got %d", recorder.Result().StatusCode)
	}

	recorder = httptest.NewRecorder()
	request = createRegisterRequest(`{"username":"","email":"","password":""}`)
	handler.CreateUser(recorder, request)
	if recorder.Result().StatusCode != http.StatusBadRequest {
		t.Fatalf("expected 400 for missing fields, got %d", recorder.Result().StatusCode)
	}

	repo.GetByUsernameFn = func(string) (*database.User, error) {
		return &database.User{ID: 1}, nil
	}
	handler = newUserHandler(repo)
	recorder = httptest.NewRecorder()
	request = createRegisterRequest(`{"username":"user","email":"user@example.com","password":"Password123!"}`)
	handler.CreateUser(recorder, request)
	testutils.AssertHTTPStatus(t, recorder, http.StatusConflict)
}

func TestUserHandlerGetUserPosts(t *testing.T) {
	repo := testutils.NewUserRepositoryStub()
	repo.GetPostsFn = func(userID uint, limit, offset int) ([]database.Post, error) {
		return []database.Post{{ID: 1, AuthorID: &userID}}, nil
	}
	handler := newUserHandler(repo)

	request := httptest.NewRequest(http.MethodGet, "/api/users/1/posts?limit=2&offset=1", nil)
	request = testutils.WithURLParams(request, map[string]string{"id": "1"})
	recorder := httptest.NewRecorder()

	handler.GetUserPosts(recorder, request)
	testutils.AssertHTTPStatus(t, recorder, http.StatusOK)

	repo.GetPostsFn = func(uint, int, int) ([]database.Post, error) {
		return nil, gorm.ErrInvalidValue
	}
	recorder = httptest.NewRecorder()
	handler.GetUserPosts(recorder, request)
	testutils.AssertHTTPStatus(t, recorder, http.StatusInternalServerError)
}

func TestUserHandlerDataSanitization(t *testing.T) {
	repo := testutils.NewUserRepositoryStub()
	repo.GetAllFn = func(l, o int) ([]database.User, error) {
		users := []database.User{
			{
				ID:                     1,
				Username:               "user1",
				Email:                  "user1@example.com",
				Password:               "hashedpassword",
				EmailVerified:          true,
				EmailVerifiedAt:        &[]time.Time{time.Now()}[0],
				EmailVerificationToken: "secret-token",
				PasswordResetToken:     "reset-token",
				Locked:                 false,
				CreatedAt:              time.Now(),
				UpdatedAt:              time.Now(),
			},
			{
				ID:                     2,
				Username:               "user2",
				Email:                  "user2@example.com",
				Password:               "another-hashed-password",
				EmailVerified:          false,
				EmailVerificationToken: "another-secret-token",
				Locked:                 true,
				CreatedAt:              time.Now(),
				UpdatedAt:              time.Now(),
			},
		}
		return users, nil
	}

	handler := newUserHandler(repo)

	request := httptest.NewRequest(http.MethodGet, "/api/users", nil)
	recorder := httptest.NewRecorder()

	handler.GetUsers(recorder, request)

	testutils.AssertHTTPStatus(t, recorder, http.StatusOK)

	var response map[string]any
	if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}

	data, ok := response["data"].(map[string]any)
	if !ok {
		t.Fatalf("expected data field in response")
	}

	users, ok := data["users"].([]any)
	if !ok {
		t.Fatalf("expected users field in data")
	}

	if len(users) != 2 {
		t.Fatalf("expected 2 users, got %d", len(users))
	}

	for i, userInterface := range users {
		user, ok := userInterface.(map[string]any)
		if !ok {
			t.Fatalf("expected user %d to be a map", i)
		}

		expectedFields := []string{"id", "username", "created_at", "updated_at"}
		for _, field := range expectedFields {
			if _, exists := user[field]; !exists {
				t.Errorf("expected field %s to be present in user %d", field, i)
			}
		}

		sensitiveFields := []string{"email", "password", "email_verified", "email_verified_at",
			"email_verification_token", "password_reset_token", "locked", "deleted_at"}
		for _, field := range sensitiveFields {
			if _, exists := user[field]; exists {
				t.Errorf("sensitive field %s should not be present in user %d", field, i)
			}
		}
	}
}

func TestUserHandler_PasswordValidation(t *testing.T) {

	tests := []struct {
		name           string
		password       string
		expectedStatus int
		description    string
	}{
		{
			name:           "valid password",
			password:       "Password123!",
			expectedStatus: http.StatusCreated,
			description:    "Valid passwords should be accepted",
		},
		{
			name:           "password without special chars",
			password:       "Password123",
			expectedStatus: http.StatusBadRequest,
			description:    "Passwords without special characters should be rejected",
		},
		{
			name:           "password too short",
			password:       "Pass1!",
			expectedStatus: http.StatusBadRequest,
			description:    "Passwords shorter than 8 characters should be rejected",
		},
		{
			name:           "password without letters",
			password:       "12345678!",
			expectedStatus: http.StatusBadRequest,
			description:    "Passwords without letters should be rejected",
		},
		{
			name:           "password without numbers",
			password:       "Password!",
			expectedStatus: http.StatusBadRequest,
			description:    "Passwords without numbers should be rejected",
		},
		{
			name:           "empty password",
			password:       "",
			expectedStatus: http.StatusBadRequest,
			description:    "Empty passwords should be rejected",
		},
		{
			name:           "password too long",
			password:       string(make([]byte, 129)),
			expectedStatus: http.StatusBadRequest,
			description:    "Passwords longer than 128 characters should be rejected",
		},
		{
			name:           "valid password with unicode",
			password:       "Pássw0rd123!",
			expectedStatus: http.StatusCreated,
			description:    "Valid passwords with unicode should be accepted",
		},
		{
			name:           "valid password with underscore",
			password:       "Password123_",
			expectedStatus: http.StatusCreated,
			description:    "Valid passwords with underscore should be accepted",
		},
		{
			name:           "valid password with hyphen",
			password:       "Password123-",
			expectedStatus: http.StatusCreated,
			description:    "Valid passwords with hyphen should be accepted",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			repo := testutils.NewUserRepositoryStub()
			repo.CreateFn = func(user *database.User) error {
				return nil
			}
			repo.GetByUsernameFn = func(username string) (*database.User, error) {
				return nil, gorm.ErrRecordNotFound
			}
			repo.GetByEmailFn = func(email string) (*database.User, error) {
				return nil, gorm.ErrRecordNotFound
			}

			cfg := &config.Config{
				JWT: config.JWTConfig{Secret: "secret", Expiration: 1},
				App: config.AppConfig{BaseURL: "https://test.example.com"},
			}
			emailSender := &testutils.MockEmailSender{}
			mockRefreshRepo := &mockRefreshTokenRepository{}
			authService, err := services.NewAuthFacadeForTest(cfg, repo, nil, nil, mockRefreshRepo, emailSender)
			if err != nil {
				t.Fatalf("Failed to create auth service: %v", err)
			}
			handler := NewUserHandler(repo, authService)

			requestBody := fmt.Sprintf(`{"username":"testuser","email":"test@example.com","password":"%s"}`, tt.password)
			request := createRegisterRequest(requestBody)
			request.Header.Set("Content-Type", "application/json")
			recorder := httptest.NewRecorder()

			handler.CreateUser(recorder, request)

			testutils.AssertHTTPStatus(t, recorder, tt.expectedStatus)
		})
	}
}
