package handlers

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

	"veza-backend-api/internal/models"
	"veza-backend-api/internal/repository"
	"veza-backend-api/internal/services"

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

func TestProfileHandler_GetProfile_Success(t *testing.T) {
	gin.SetMode(gin.TestMode)

	// Setup: Create real UserService with in-memory repository
	userRepo := repository.NewUserRepository()
	userService := services.NewUserService(userRepo)
	handler := NewProfileHandler(userService)

	// Create a test user
	userID := uuid.New()
	createdAt := time.Now()
	user := &models.User{
		ID:         userID,
		Username:   "testuser",
		Email:      "test@example.com",
		Avatar:     "https://example.com/avatar.jpg",
		Bio:        "Test bio",
		FirstName:  "Test",
		LastName:   "User",
		CreatedAt:  createdAt,
		IsActive:   true,
		IsVerified: true,
		IsPublic:   true,
	}

	// Add user to repository
	err := userRepo.Create(user)
	assert.NoError(t, err)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/users/"+userID.String()+"/profile", nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Params = gin.Params{{Key: "id", Value: userID.String()}}

	handler.GetProfile(c)

	assert.Equal(t, http.StatusOK, w.Code)

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

	profile := response["profile"].(map[string]interface{})
	assert.Equal(t, "testuser", profile["username"])
	assert.Equal(t, "https://example.com/avatar.jpg", profile["avatar_url"])
	assert.Equal(t, "Test bio", profile["bio"])
}

func TestProfileHandler_GetProfile_InvalidID(t *testing.T) {
	gin.SetMode(gin.TestMode)

	userRepo := repository.NewUserRepository()
	userService := services.NewUserService(userRepo)
	handler := NewProfileHandler(userService)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/users/invalid/profile", nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Params = gin.Params{{Key: "id", Value: "invalid"}}

	handler.GetProfile(c)

	assert.Equal(t, http.StatusBadRequest, w.Code)

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)
	assert.Contains(t, response, "error")
	assert.Equal(t, "invalid user id", response["error"])
}

func TestProfileHandler_GetProfile_UserNotFound(t *testing.T) {
	gin.SetMode(gin.TestMode)

	userRepo := repository.NewUserRepository()
	userService := services.NewUserService(userRepo)
	handler := NewProfileHandler(userService)

	randomID := uuid.New().String()
	req := httptest.NewRequest(http.MethodGet, "/api/v1/users/"+randomID+"/profile", nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Params = gin.Params{{Key: "id", Value: randomID}}

	handler.GetProfile(c)

	assert.Equal(t, http.StatusNotFound, w.Code)

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)
	assert.Contains(t, response, "error")
	assert.Equal(t, "user not found", response["error"])
}

func TestProfileHandler_GetProfile_OwnProfile(t *testing.T) {
	gin.SetMode(gin.TestMode)

	userRepo := repository.NewUserRepository()
	userService := services.NewUserService(userRepo)
	handler := NewProfileHandler(userService)

	userID := uuid.New()
	createdAt := time.Now()
	user := &models.User{
		ID:         userID,
		Username:   "testuser",
		Email:      "test@example.com",
		Avatar:     "https://example.com/avatar.jpg",
		Bio:        "Test bio",
		FirstName:  "Test",
		LastName:   "User",
		CreatedAt:  createdAt,
		IsActive:   true,
		IsVerified: true,
		IsPublic:   true,
	}

	err := userRepo.Create(user)
	assert.NoError(t, err)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/users/"+userID.String()+"/profile", nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Params = gin.Params{{Key: "id", Value: userID.String()}}
	c.Set("user_id", userID)

	handler.GetProfile(c)

	assert.Equal(t, http.StatusOK, w.Code)

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

	profile := response["profile"].(map[string]interface{})
	assert.Equal(t, "testuser", profile["username"])
	// When viewing own profile, should include email
	// assert.Equal(t, "test@example.com", profile["email"]) // Profile struct does not have email
	assert.Equal(t, "Test", profile["first_name"])
	assert.Equal(t, "User", profile["last_name"])
}

func TestProfileHandler_UpdateProfile_Success(t *testing.T) {
	gin.SetMode(gin.TestMode)

	userRepo := repository.NewUserRepository()
	userService := services.NewUserService(userRepo)
	handler := NewProfileHandler(userService)

	userID := uuid.New()
	createdAt := time.Now()
	user := &models.User{
		ID:         userID,
		Username:   "testuser",
		Email:      "test@example.com",
		FirstName:  "Test",
		LastName:   "User",
		Bio:        "Old bio",
		CreatedAt:  createdAt,
		IsActive:   true,
		IsVerified: true,
		IsPublic:   true,
	}

	err := userRepo.Create(user)
	assert.NoError(t, err)

	reqBody := map[string]interface{}{
		"first_name": "Updated",
		"last_name":  "Name",
		"bio":        "New bio",
		"location":   "Paris",
	}

	body, _ := json.Marshal(reqBody)
	req := httptest.NewRequest(http.MethodPut, "/api/v1/users/"+userID.String()+"/profile", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Params = gin.Params{{Key: "id", Value: userID.String()}}
	c.Set("user_id", userID)

	handler.UpdateProfile(c)

	assert.Equal(t, http.StatusOK, w.Code)

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

func TestProfileHandler_UpdateProfile_Unauthorized(t *testing.T) {
	gin.SetMode(gin.TestMode)

	userRepo := repository.NewUserRepository()
	userService := services.NewUserService(userRepo)
	handler := NewProfileHandler(userService)

	userID := uuid.New() // We need a valid ID for the path even if not auth
	reqBody := map[string]interface{}{
		"first_name": "Updated",
	}

	body, _ := json.Marshal(reqBody)
	req := httptest.NewRequest(http.MethodPut, "/api/v1/users/"+userID.String()+"/profile", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Params = gin.Params{{Key: "id", Value: userID.String()}}
	// No user_id set - unauthorized

	handler.UpdateProfile(c)

	assert.Equal(t, http.StatusUnauthorized, w.Code)
}

func TestProfileHandler_UpdateProfile_Forbidden(t *testing.T) {
	gin.SetMode(gin.TestMode)

	userRepo := repository.NewUserRepository()
	userService := services.NewUserService(userRepo)
	handler := NewProfileHandler(userService)

	userID := uuid.New()
	reqBody := map[string]interface{}{
		"first_name": "Updated",
	}

	body, _ := json.Marshal(reqBody)
	req := httptest.NewRequest(http.MethodPut, "/api/v1/users/"+userID.String()+"/profile", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Params = gin.Params{{Key: "id", Value: userID.String()}}
	c.Set("user_id", uuid.New()) // Different user ID

	handler.UpdateProfile(c)

	assert.Equal(t, http.StatusForbidden, w.Code)
}

func TestProfileHandler_UpdateProfile_InvalidUsername(t *testing.T) {
	gin.SetMode(gin.TestMode)

	userRepo := repository.NewUserRepository()
	userService := services.NewUserService(userRepo)
	handler := NewProfileHandler(userService)

	userID := uuid.New()
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
		IsActive: true,
	}

	err := userRepo.Create(user)
	assert.NoError(t, err)

	reqBody := map[string]interface{}{
		"username": "ab", // Too short
	}

	body, _ := json.Marshal(reqBody)
	req := httptest.NewRequest(http.MethodPut, "/api/v1/users/"+userID.String()+"/profile", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Params = gin.Params{{Key: "id", Value: userID.String()}}
	c.Set("user_id", userID)

	handler.UpdateProfile(c)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestProfileHandler_UpdateProfile_InvalidBirthdate(t *testing.T) {
	gin.SetMode(gin.TestMode)

	userRepo := repository.NewUserRepository()
	userService := services.NewUserService(userRepo)
	handler := NewProfileHandler(userService)

	userID := uuid.New()
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
		IsActive: true,
	}

	err := userRepo.Create(user)
	assert.NoError(t, err)

	// Birthdate that makes user less than 13 years old
	reqBody := map[string]interface{}{
		"birthdate": time.Now().AddDate(-10, 0, 0).Format("2006-01-02"),
	}

	body, _ := json.Marshal(reqBody)
	req := httptest.NewRequest(http.MethodPut, "/api/v1/users/"+userID.String()+"/profile", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Params = gin.Params{{Key: "id", Value: userID.String()}}
	c.Set("user_id", userID)

	handler.UpdateProfile(c)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestProfileHandler_UpdateProfile_UsernameTaken(t *testing.T) {
	gin.SetMode(gin.TestMode)

	userRepo := repository.NewUserRepository()
	userService := services.NewUserService(userRepo)
	handler := NewProfileHandler(userService)

	// Create first user
	user1ID := uuid.New()
	user1 := &models.User{
		ID:       user1ID,
		Username: "testuser",
		Email:    "test@example.com",
		IsActive: true,
	}
	err := userRepo.Create(user1)
	assert.NoError(t, err)

	// Create second user
	user2ID := uuid.New()
	user2 := &models.User{
		ID:       user2ID,
		Username: "existinguser",
		Email:    "existing@example.com",
		IsActive: true,
	}
	err = userRepo.Create(user2)
	assert.NoError(t, err)

	// Try to update user1 with user2's username
	reqBody := map[string]interface{}{
		"username": "existinguser",
	}

	body, _ := json.Marshal(reqBody)
	req := httptest.NewRequest(http.MethodPut, "/api/v1/users/"+user1ID.String()+"/profile", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Params = gin.Params{{Key: "id", Value: user1ID.String()}}
	c.Set("user_id", user1ID)

	handler.UpdateProfile(c)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestProfileHandler_UpdateProfile_UsernameChangeLimit(t *testing.T) {
	gin.SetMode(gin.TestMode)

	userRepo := repository.NewUserRepository()
	userService := services.NewUserService(userRepo)
	handler := NewProfileHandler(userService)

	userID := uuid.New()
	recentChange := time.Now().AddDate(0, 0, -15) // 15 days ago
	user := &models.User{
		ID:                userID,
		Username:          "testuser",
		Email:             "test@example.com",
		UsernameChangedAt: &recentChange,
		IsActive:          true,
	}

	err := userRepo.Create(user)
	assert.NoError(t, err)

	reqBody := map[string]interface{}{
		"username": "newusername",
	}

	body, _ := json.Marshal(reqBody)
	req := httptest.NewRequest(http.MethodPut, "/api/v1/users/"+userID.String()+"/profile", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Params = gin.Params{{Key: "id", Value: userID.String()}}
	c.Set("user_id", userID)

	handler.UpdateProfile(c)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestProfileHandler_GetProfileByUsername_Success(t *testing.T) {
	gin.SetMode(gin.TestMode)

	userRepo := repository.NewUserRepository()
	userService := services.NewUserService(userRepo)
	handler := NewProfileHandler(userService)

	userID := uuid.New()
	createdAt := time.Now()
	user := &models.User{
		ID:         userID,
		Username:   "testuser",
		Email:      "test@example.com",
		Avatar:     "https://example.com/avatar.jpg",
		Bio:        "Test bio",
		FirstName:  "Test",
		LastName:   "User",
		Location:   "Paris",
		CreatedAt:  createdAt,
		IsActive:   true,
		IsVerified: true,
		IsPublic:   true,
	}

	err := userRepo.Create(user)
	assert.NoError(t, err)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/users/by-username/testuser", nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Params = gin.Params{{Key: "username", Value: "testuser"}}

	handler.GetProfileByUsername(c)

	assert.Equal(t, http.StatusOK, w.Code)

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

	profile := response["profile"].(map[string]interface{})
	assert.Equal(t, userID.String(), profile["id"])
	assert.Equal(t, "testuser", profile["username"])
	assert.Equal(t, "Test", profile["first_name"])
	assert.Equal(t, "User", profile["last_name"])
	assert.Equal(t, "https://example.com/avatar.jpg", profile["avatar_url"])
	assert.Equal(t, "Test bio", profile["bio"])
	assert.Equal(t, "Paris", profile["location"])
}

func TestProfileHandler_GetProfileByUsername_EmptyUsername(t *testing.T) {
	gin.SetMode(gin.TestMode)

	userRepo := repository.NewUserRepository()
	userService := services.NewUserService(userRepo)
	handler := NewProfileHandler(userService)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/users/by-username/", nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Params = gin.Params{{Key: "username", Value: ""}}

	handler.GetProfileByUsername(c)

	assert.Equal(t, http.StatusBadRequest, w.Code)

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

func TestProfileHandler_GetProfileByUsername_UserNotFound(t *testing.T) {
	gin.SetMode(gin.TestMode)

	userRepo := repository.NewUserRepository()
	userService := services.NewUserService(userRepo)
	handler := NewProfileHandler(userService)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/users/by-username/nonexistent", nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Params = gin.Params{{Key: "username", Value: "nonexistent"}}

	handler.GetProfileByUsername(c)

	assert.Equal(t, http.StatusNotFound, w.Code)

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)
	assert.Contains(t, response, "error")
	assert.Equal(t, "user not found", response["error"])
}

func TestProfileHandler_GetProfileByUsername_PublicFieldsOnly(t *testing.T) {
	gin.SetMode(gin.TestMode)

	userRepo := repository.NewUserRepository()
	userService := services.NewUserService(userRepo)
	handler := NewProfileHandler(userService)

	userID := uuid.New()
	createdAt := time.Now()
	user := &models.User{
		ID:           userID,
		Username:     "testuser",
		Email:        "private@example.com",
		PasswordHash: "hashed_password",
		Avatar:       "https://example.com/avatar.jpg",
		Bio:          "Test bio",
		FirstName:    "Test",
		LastName:     "User",
		Location:     "Paris",
		CreatedAt:    createdAt,
		IsActive:     true,
		IsVerified:   true,
	}

	err := userRepo.Create(user)
	assert.NoError(t, err)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/users/by-username/testuser", nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Params = gin.Params{{Key: "username", Value: "testuser"}}

	handler.GetProfileByUsername(c)

	assert.Equal(t, http.StatusOK, w.Code)

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

	profile := response["profile"].(map[string]interface{})
	// Email should NOT be in public profile
	assert.NotContains(t, profile, "email")
	// PasswordHash should NOT be in public profile
	assert.NotContains(t, profile, "password_hash")
	// Only public fields should be present
	assert.Contains(t, profile, "id")
	assert.Contains(t, profile, "username")
	assert.Contains(t, profile, "first_name")
	assert.Contains(t, profile, "last_name")
	assert.Contains(t, profile, "avatar_url")
	assert.Contains(t, profile, "bio")
	assert.Contains(t, profile, "location")
	assert.Contains(t, profile, "created_at")
}