package handlers

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

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

	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
	"go.uber.org/zap/zaptest"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

type MockUserRepository struct {
	users map[uuid.UUID]*models.User
}

func NewMockUserRepository() *MockUserRepository {
	return &MockUserRepository{
		users: make(map[uuid.UUID]*models.User),
	}
}

func (m *MockUserRepository) CreateUser(ctx context.Context, user *models.User) error {
	m.users[user.ID] = user
	return nil
}
func (m *MockUserRepository) GetUserByID(ctx context.Context, id uuid.UUID) (*models.User, error) {
	user, ok := m.users[id]
	if !ok {
		return nil, gorm.ErrRecordNotFound
	}
	return user, nil
}
func (m *MockUserRepository) GetUserByEmail(ctx context.Context, email string) (*models.User, error) {
	panic("not implemented")
}
func (m *MockUserRepository) GetUserByUsername(ctx context.Context, username string) (*models.User, error) {
	for _, user := range m.users {
		if user.Username == username {
			return user, nil
		}
	}
	return nil, gorm.ErrRecordNotFound
}
func (m *MockUserRepository) UpdateUser(ctx context.Context, user *models.User) error {
	m.users[user.ID] = user
	return nil
}
func (m *MockUserRepository) DeleteUser(ctx context.Context, id uuid.UUID) error {
	panic("not implemented")
}
func (m *MockUserRepository) UpdateLastLoginAt(ctx context.Context, userID uuid.UUID) error {
	panic("not implemented")
}
func (m *MockUserRepository) IncrementTokenVersion(ctx context.Context, userID uuid.UUID) error {
	panic("not implemented")
}

// Compatibility methods for services.UserRepository interface
func (m *MockUserRepository) GetByID(id string) (*models.User, error) {
	idUUID, err := uuid.Parse(id)
	if err != nil {
		return nil, err
	}
	return m.GetUserByID(context.Background(), idUUID)
}
func (m *MockUserRepository) GetByEmail(email string) (*models.User, error) {
	return m.GetUserByEmail(context.Background(), email)
}
func (m *MockUserRepository) GetByUsername(username string) (*models.User, error) {
	return m.GetUserByUsername(context.Background(), username)
}
func (m *MockUserRepository) Create(user *models.User) error {
	return m.CreateUser(context.Background(), user)
}
func (m *MockUserRepository) Update(user *models.User) error {
	return m.UpdateUser(context.Background(), user)
}
func (m *MockUserRepository) Delete(id string) error {
	idUUID, _ := uuid.Parse(id)
	return m.DeleteUser(context.Background(), idUUID)
}

func setupTestChatHandler(_ *testing.T) (*ChatHandler, *gin.Engine, func(), uuid.UUID) {
	gin.SetMode(gin.TestMode)

	logger := zap.NewNop()
	jwtSecret := "supersecretchatkey"

	chatService := services.NewChatService(jwtSecret, logger)

	// Mock UserService
	mockUserRepo := NewMockUserRepository()
	userID := uuid.New()
	mockUser := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
		// ... other fields as needed
	}
	mockUserRepo.CreateUser(context.Background(), mockUser)
	userService := services.NewUserService(mockUserRepo)

	handler := NewChatHandler(chatService, userService, logger)

	r := gin.New()
	// Simulate auth middleware setting user_id
	r.Use(func(c *gin.Context) {
		c.Set("user_id", userID) // Pass UUID object as middleware does
		c.Set("username", "testuser")
		c.Next()
	})
	r.POST("/chat/token", handler.GetToken)

	cleanup := func() {
		// No specific cleanup needed for these tests
	}

	return handler, r, cleanup, userID
}

func TestChatHandler_GetToken_Success(t *testing.T) {
	_, r, cleanup, userID := setupTestChatHandler(t)
	defer cleanup()

	req := httptest.NewRequest(http.MethodPost, "/chat/token", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

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

	var response APIResponse
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)
	assert.True(t, response.Success)
	assert.Nil(t, response.Error)

	// Data should be map/struct. Since it is interface{}, we need to marshal/unmarshal or type assert carefully.
	// API sends ChatTokenResponse struct.
	// Let's re-marshal Data to get ChatTokenResponse
	dataBytes, _ := json.Marshal(response.Data)
	var tokenResponse services.ChatTokenResponse
	err = json.Unmarshal(dataBytes, &tokenResponse)
	assert.NoError(t, err)

	assert.NotEmpty(t, tokenResponse.Token)
	assert.Greater(t, tokenResponse.ExpiresIn, int64(0))
	assert.Equal(t, "/ws", tokenResponse.WSUrl)

	// Optionally, verify token content
	parsedToken, err := jwt.Parse(tokenResponse.Token, func(token *jwt.Token) (interface{}, error) {
		assert.Equal(t, jwt.SigningMethodHS256, token.Method)
		return []byte("supersecretchatkey"), nil
	})
	assert.NoError(t, err)
	claims, ok := parsedToken.Claims.(jwt.MapClaims)
	assert.True(t, ok)
	assert.Equal(t, userID.String(), claims["sub"])
	assert.Equal(t, "testuser", claims["name"])
}

func TestChatHandler_GetToken_Unauthorized(t *testing.T) {
	logger := zap.NewNop()
	jwtSecret := "supersecretchatkey"

	chatService := services.NewChatService(jwtSecret, logger)
	mockUserRepo := NewMockUserRepository()
	userService := services.NewUserService(mockUserRepo)

	handler := NewChatHandler(chatService, userService, logger)

	r := gin.New()
	r.POST("/chat/token", handler.GetToken) // No auth middleware

	req := httptest.NewRequest(http.MethodPost, "/chat/token", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

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

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)
	// API might return standard error JSON or APIResponse depending on middleware
	// The handler uses c.JSON(Unauthorized, gin.H{"error":...}) directly in manual checks
	// See lines 41, 46 in handler.
	assert.Equal(t, "unauthorized", response["error"])
}

// setupTestChatHandlerWithDB creates a test handler with database for GetStats tests
func setupTestChatHandlerWithDB(t *testing.T) (*ChatHandler, *gin.Engine, *gorm.DB, func()) {
	gin.SetMode(gin.TestMode)
	logger := zaptest.NewLogger(t)

	// Setup in-memory SQLite database
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	db.Exec("PRAGMA foreign_keys = ON")

	// Auto-migrate models
	err = db.AutoMigrate(
		&models.User{},
		&models.Room{},
		&models.Message{},
	)
	require.NoError(t, err)

	jwtSecret := "supersecretchatkey"
	chatService := services.NewChatServiceWithDB(jwtSecret, db, logger)

	// Mock UserService
	mockUserRepo := NewMockUserRepository()
	userService := services.NewUserService(mockUserRepo)

	handler := NewChatHandler(chatService, userService, logger)

	r := gin.New()
	r.GET("/chat/stats", handler.GetStats)

	cleanup := func() {
		// Database cleanup handled by test
	}

	return handler, r, db, cleanup
}

// TestChatHandler_GetStats_Success tests successful chat stats retrieval
func TestChatHandler_GetStats_Success(t *testing.T) {
	_, r, db, cleanup := setupTestChatHandlerWithDB(t)
	defer cleanup()

	// Create test data
	userID := uuid.New()
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
		IsActive: true,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	roomID := uuid.New()
	room := &models.Room{
		ID:        roomID,
		Name:      "Test Room",
		CreatedBy: userID,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	err = db.Create(room).Error
	require.NoError(t, err)

	// Create test messages
	for i := 0; i < 3; i++ {
		message := &models.Message{
			ID:        uuid.New(),
			RoomID:    roomID,
			UserID:    userID,
			Content:   fmt.Sprintf("Test message %d", i+1),
			Type:      "text",
			IsDeleted: false,
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
		}
		err = db.Create(message).Error
		require.NoError(t, err)
	}

	req := httptest.NewRequest(http.MethodGet, "/chat/stats", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response APIResponse
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response.Success)
	assert.Nil(t, response.Error)

	// Verify stats data
	dataBytes, _ := json.Marshal(response.Data)
	var stats services.ChatStats
	err = json.Unmarshal(dataBytes, &stats)
	require.NoError(t, err)
	assert.GreaterOrEqual(t, stats.TotalMessages, int64(3))
	assert.GreaterOrEqual(t, stats.ActiveUsers, int64(1))
	assert.GreaterOrEqual(t, stats.RoomsActive, int64(1))
}

// TestChatHandler_GetStats_NoMessages tests stats when there are no messages
func TestChatHandler_GetStats_NoMessages(t *testing.T) {
	_, r, _, cleanup := setupTestChatHandlerWithDB(t)
	defer cleanup()

	req := httptest.NewRequest(http.MethodGet, "/chat/stats", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response APIResponse
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response.Success)

	// Verify stats are zero
	dataBytes, _ := json.Marshal(response.Data)
	var stats services.ChatStats
	err = json.Unmarshal(dataBytes, &stats)
	require.NoError(t, err)
	assert.Equal(t, int64(0), stats.TotalMessages)
	assert.Equal(t, int64(0), stats.ActiveUsers)
	assert.Equal(t, int64(0), stats.RoomsActive)
}

// TestChatHandler_GetToken_InvalidUserID tests GetToken with invalid user ID type
func TestChatHandler_GetToken_InvalidUserID(t *testing.T) {
	logger := zap.NewNop()
	jwtSecret := "supersecretchatkey"

	chatService := services.NewChatService(jwtSecret, logger)
	mockUserRepo := NewMockUserRepository()
	userService := services.NewUserService(mockUserRepo)

	handler := NewChatHandler(chatService, userService, logger)

	r := gin.New()
	r.Use(func(c *gin.Context) {
		// Set invalid user_id type (string instead of UUID)
		c.Set("user_id", "invalid-uuid")
		c.Next()
	})
	r.POST("/chat/token", handler.GetToken)

	req := httptest.NewRequest(http.MethodPost, "/chat/token", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

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

// TestChatHandler_GetToken_NilUserID tests GetToken with nil user ID
func TestChatHandler_GetToken_NilUserID(t *testing.T) {
	logger := zap.NewNop()
	jwtSecret := "supersecretchatkey"

	chatService := services.NewChatService(jwtSecret, logger)
	mockUserRepo := NewMockUserRepository()
	userService := services.NewUserService(mockUserRepo)

	handler := NewChatHandler(chatService, userService, logger)

	r := gin.New()
	r.Use(func(c *gin.Context) {
		// Set nil UUID
		c.Set("user_id", uuid.Nil)
		c.Next()
	})
	r.POST("/chat/token", handler.GetToken)

	req := httptest.NewRequest(http.MethodPost, "/chat/token", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

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

// TestChatHandler_GetToken_UserNotFound tests GetToken when user is not found in DB
func TestChatHandler_GetToken_UserNotFound(t *testing.T) {
	logger := zap.NewNop()
	jwtSecret := "supersecretchatkey"

	chatService := services.NewChatService(jwtSecret, logger)
	mockUserRepo := NewMockUserRepository()
	// Don't create any user in the mock repo
	userService := services.NewUserService(mockUserRepo)

	handler := NewChatHandler(chatService, userService, logger)

	r := gin.New()
	userID := uuid.New()
	r.Use(func(c *gin.Context) {
		c.Set("user_id", userID)
		c.Next()
	})
	r.POST("/chat/token", handler.GetToken)

	req := httptest.NewRequest(http.MethodPost, "/chat/token", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	// Should still succeed with fallback username
	assert.Equal(t, http.StatusOK, w.Code)
	var response APIResponse
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response.Success)

	// Verify token has fallback username
	dataBytes, _ := json.Marshal(response.Data)
	var tokenResponse services.ChatTokenResponse
	err = json.Unmarshal(dataBytes, &tokenResponse)
	require.NoError(t, err)
	assert.NotEmpty(t, tokenResponse.Token)

	parsedToken, err := jwt.Parse(tokenResponse.Token, func(token *jwt.Token) (interface{}, error) {
		return []byte(jwtSecret), nil
	})
	require.NoError(t, err)
	claims, ok := parsedToken.Claims.(jwt.MapClaims)
	assert.True(t, ok)
	// Should have fallback username format
	expectedUsername := fmt.Sprintf("user_%s", userID)
	assert.Equal(t, expectedUsername, claims["name"])
}
