package handlers

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

	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"veza-backend-api/internal/models"
	"veza-backend-api/internal/services"
)

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(t *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 services.ChatTokenResponse
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)
	assert.NotEmpty(t, response.Token)
	assert.Greater(t, response.ExpiresIn, int64(0))
	assert.Equal(t, "/ws", response.WSUrl)

	// Optionally, verify token content
	parsedToken, err := jwt.Parse(response.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]string
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)
	assert.Equal(t, "unauthorized", response["error"])
}